Refactor sources and show headings
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
|
||||
import { createMaterialBottomTabNavigator } from "@react-navigation/material-bottom-tabs";
|
||||
import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
@@ -18,78 +17,76 @@ const BottomNavigator = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, backgroundColor: theme.colorPrimaryDark }}>
|
||||
<Tab.Navigator
|
||||
barStyle={{ backgroundColor: theme.colorPrimary }}
|
||||
activeColor={theme.colorAccent}
|
||||
>
|
||||
<Tab.Screen
|
||||
name="Library"
|
||||
component={Library}
|
||||
options={{
|
||||
tabBarIcon: ({ color }) => (
|
||||
<MaterialCommunityIcons
|
||||
name="book-variant-multiple"
|
||||
color={color}
|
||||
size={24}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name="Updates"
|
||||
component={Updates}
|
||||
options={{
|
||||
tabBarIcon: ({ color }) => (
|
||||
<MaterialCommunityIcons
|
||||
name="alert-decagram-outline"
|
||||
color={color}
|
||||
size={24}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name="History"
|
||||
component={History}
|
||||
options={{
|
||||
tabBarIcon: ({ color }) => (
|
||||
<MaterialCommunityIcons
|
||||
name="history"
|
||||
color={color}
|
||||
size={24}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name="Browse"
|
||||
component={Browse}
|
||||
options={{
|
||||
tabBarIcon: ({ color }) => (
|
||||
<MaterialCommunityIcons
|
||||
name="compass-outline"
|
||||
color={color}
|
||||
size={24}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name="More"
|
||||
component={More}
|
||||
options={{
|
||||
tabBarIcon: ({ color }) => (
|
||||
<MaterialCommunityIcons
|
||||
name="dots-horizontal"
|
||||
color={color}
|
||||
size={24}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Tab.Navigator>
|
||||
</View>
|
||||
<Tab.Navigator
|
||||
barStyle={{ backgroundColor: theme.colorPrimary }}
|
||||
activeColor={theme.colorAccent}
|
||||
>
|
||||
<Tab.Screen
|
||||
name="Library"
|
||||
component={Library}
|
||||
options={{
|
||||
tabBarIcon: ({ color }) => (
|
||||
<MaterialCommunityIcons
|
||||
name="book-variant-multiple"
|
||||
color={color}
|
||||
size={24}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name="Updates"
|
||||
component={Updates}
|
||||
options={{
|
||||
tabBarIcon: ({ color }) => (
|
||||
<MaterialCommunityIcons
|
||||
name="alert-decagram-outline"
|
||||
color={color}
|
||||
size={24}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name="History"
|
||||
component={History}
|
||||
options={{
|
||||
tabBarIcon: ({ color }) => (
|
||||
<MaterialCommunityIcons
|
||||
name="history"
|
||||
color={color}
|
||||
size={24}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name="Browse"
|
||||
component={Browse}
|
||||
options={{
|
||||
tabBarIcon: ({ color }) => (
|
||||
<MaterialCommunityIcons
|
||||
name="compass-outline"
|
||||
color={color}
|
||||
size={24}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name="More"
|
||||
component={More}
|
||||
options={{
|
||||
tabBarIcon: ({ color }) => (
|
||||
<MaterialCommunityIcons
|
||||
name="dots-horizontal"
|
||||
color={color}
|
||||
size={24}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Tab.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
+12
-19
@@ -1,10 +1,17 @@
|
||||
import React, { useEffect } from "react";
|
||||
import {
|
||||
createStackNavigator,
|
||||
TransitionPresets,
|
||||
} from "@react-navigation/stack";
|
||||
import { View } from "react-native";
|
||||
|
||||
import { NavigationContainer } from "@react-navigation/native";
|
||||
import { createStackNavigator } from "@react-navigation/stack";
|
||||
|
||||
import { setBarColor } from "../theme/utils/setBarColor";
|
||||
import { useTheme } from "../hooks/reduxHooks";
|
||||
import { githubUpdateChecker } from "../hooks/githubUpdateChecker";
|
||||
|
||||
// Navigators
|
||||
import BottomNavigator from "./BottomNavigator";
|
||||
|
||||
// Screens
|
||||
import Novel from "../screens/Novel/Novel";
|
||||
import Chapter from "../screens/Chapter/Chapter";
|
||||
import Extension from "../screens/Extension/Extension";
|
||||
@@ -14,21 +21,11 @@ import SourceNovels from "../screens/Browse/SourceNovels";
|
||||
import MigrateNovel from "../screens/Browse/migration/MigrationNovels";
|
||||
import MoreStack from "./More";
|
||||
import MalTopNovels from "../screens/Browse/discover/MalTopNovels";
|
||||
import { View } from "react-native";
|
||||
import { useTheme } from "../hooks/reduxHooks";
|
||||
import { githubUpdateChecker } from "../hooks/githubUpdateChecker";
|
||||
import NewUpdateDialog from "../components/NewUpdateDialog";
|
||||
import { NavigationContainer } from "@react-navigation/native";
|
||||
import BrowseNovelUpdates from "../screens/Browse/discover/BrowseNovelUpdates";
|
||||
import BrowseSettings from "../screens/Browse/BrowseSettings";
|
||||
import { setBarColor } from "../theme/utils/setBarColor";
|
||||
|
||||
const Stack = createStackNavigator();
|
||||
|
||||
const stackNavigatorConfig = {
|
||||
headerShown: false,
|
||||
};
|
||||
|
||||
const MainNavigator = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
@@ -44,7 +41,7 @@ const MainNavigator = () => {
|
||||
>
|
||||
<View style={{ flex: 1, backgroundColor: theme.colorPrimaryDark }}>
|
||||
{isNewVersion && <NewUpdateDialog newVersion={latestRelease} />}
|
||||
<Stack.Navigator screenOptions={stackNavigatorConfig}>
|
||||
<Stack.Navigator screenOptions={{ headerShown: false }}>
|
||||
<Stack.Screen
|
||||
name="BottomNavigator"
|
||||
component={BottomNavigator}
|
||||
@@ -58,10 +55,6 @@ const MainNavigator = () => {
|
||||
name="BrowseSettings"
|
||||
component={BrowseSettings}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="BrowseNovelUpdates"
|
||||
component={BrowseNovelUpdates}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="GlobalSearch"
|
||||
component={GlobalSearch}
|
||||
|
||||
+18
-46
@@ -1,9 +1,8 @@
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { useTheme } from "../hooks/reduxHooks";
|
||||
|
||||
import { createStackNavigator } from "@react-navigation/stack";
|
||||
|
||||
// Screens
|
||||
import About from "../screens/More/About";
|
||||
import Settings from "../screens/More/Settings";
|
||||
import TrackerSettings from "../screens/More/TrackerSettings";
|
||||
@@ -17,50 +16,23 @@ const Stack = createStackNavigator();
|
||||
|
||||
const stackNavigatorConfig = { headerShown: false };
|
||||
|
||||
const SettingsStack = () => {
|
||||
const theme = useTheme();
|
||||
const SettingsStack = () => (
|
||||
<Stack.Navigator screenOptions={stackNavigatorConfig}>
|
||||
<Stack.Screen name="Settings" component={Settings} />
|
||||
<Stack.Screen name="GeneralSettings" component={GeneralSettings} />
|
||||
<Stack.Screen name="ReaderSettings" component={ReaderSettings} />
|
||||
<Stack.Screen name="TrackerSettings" component={TrackerSettings} />
|
||||
<Stack.Screen name="BackupSettings" component={BackupSettings} />
|
||||
<Stack.Screen name="AdvancedSettings" component={AdvancedSettings} />
|
||||
</Stack.Navigator>
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, backgroundColor: theme.colorPrimaryDark }}>
|
||||
<Stack.Navigator screenOptions={stackNavigatorConfig}>
|
||||
<Stack.Screen name="Settings" component={Settings} />
|
||||
<Stack.Screen
|
||||
name="GeneralSettings"
|
||||
component={GeneralSettings}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="ReaderSettings"
|
||||
component={ReaderSettings}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="TrackerSettings"
|
||||
component={TrackerSettings}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="BackupSettings"
|
||||
component={BackupSettings}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="AdvancedSettings"
|
||||
component={AdvancedSettings}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const MoreStack = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, backgroundColor: theme.colorPrimaryDark }}>
|
||||
<Stack.Navigator screenOptions={stackNavigatorConfig}>
|
||||
<Stack.Screen name="SettingsStack" component={SettingsStack} />
|
||||
<Stack.Screen name="About" component={About} />
|
||||
<Stack.Screen name="DownloadQueue" component={DownloadQueue} />
|
||||
</Stack.Navigator>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
const MoreStack = () => (
|
||||
<Stack.Navigator screenOptions={stackNavigatorConfig}>
|
||||
<Stack.Screen name="SettingsStack" component={SettingsStack} />
|
||||
<Stack.Screen name="About" component={About} />
|
||||
<Stack.Screen name="DownloadQueue" component={DownloadQueue} />
|
||||
</Stack.Navigator>
|
||||
);
|
||||
|
||||
export default MoreStack;
|
||||
|
||||
@@ -6,15 +6,21 @@ import {
|
||||
SEARCH_SOURCES,
|
||||
} from "./source.types";
|
||||
import { showToast } from "../../hooks/showToast";
|
||||
import { sources } from "../../sources/sources";
|
||||
import sources from "../../sources/sources.json";
|
||||
|
||||
export const getSourcesAction = () => async (dispatch) => {
|
||||
try {
|
||||
/**
|
||||
* Sort sources aplhabetically
|
||||
*/
|
||||
let sortedSources = sources.sort((a, b) =>
|
||||
a.sourceName.localeCompare(b.sourceName)
|
||||
);
|
||||
|
||||
dispatch({ type: GET_SOURCES, payload: sortedSources });
|
||||
dispatch({
|
||||
type: GET_SOURCES,
|
||||
payload: sortedSources,
|
||||
});
|
||||
} catch (error) {
|
||||
showToast(error.message);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ const initialState = {
|
||||
search: [],
|
||||
pinned: [],
|
||||
filters: [],
|
||||
showNovelUpdates: true,
|
||||
showMyAnimeList: true,
|
||||
loading: true,
|
||||
};
|
||||
@@ -44,7 +43,6 @@ const sourceReducer = (state = initialState, action) => {
|
||||
if (!state.filters) {
|
||||
state.filters = [];
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
filters:
|
||||
|
||||
@@ -7,17 +7,18 @@ import {
|
||||
ActivityIndicator,
|
||||
RefreshControl,
|
||||
} from "react-native";
|
||||
import { useTheme } from "../../hooks/reduxHooks";
|
||||
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
import {
|
||||
getSourcesAction,
|
||||
searchSourcesAction,
|
||||
} from "../../redux/source/source.actions";
|
||||
|
||||
import SourceCard from "./components/SourceCard";
|
||||
import { useTheme } from "../../hooks/reduxHooks";
|
||||
import { showToast } from "../../hooks/showToast";
|
||||
|
||||
import { Searchbar } from "../../components/Searchbar/Searchbar";
|
||||
import SourceCard from "./components/SourceCard";
|
||||
import EmptyView from "../../components/EmptyView";
|
||||
import DiscoverCard from "./discover/DiscoverCard";
|
||||
|
||||
@@ -31,7 +32,6 @@ const Browse = ({ navigation }) => {
|
||||
loading,
|
||||
pinned,
|
||||
filters = [],
|
||||
showNovelUpdates = true,
|
||||
showMyAnimeList = true,
|
||||
} = useSelector((state) => state.sourceReducer);
|
||||
const theme = useTheme();
|
||||
@@ -42,6 +42,8 @@ const Browse = ({ navigation }) => {
|
||||
|
||||
sources = sources.filter((source) => filters.indexOf(source.lang) === -1);
|
||||
|
||||
let pinnedSources = sources.filter((source) => isPinned(source.sourceId));
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getSourcesAction());
|
||||
}, [getSourcesAction]);
|
||||
@@ -72,6 +74,14 @@ const Browse = ({ navigation }) => {
|
||||
/>
|
||||
);
|
||||
|
||||
const SourceHeader = ({ title }) => (
|
||||
<Text
|
||||
style={[styles.sourcesHeader, { color: theme.textColorSecondary }]}
|
||||
>
|
||||
{title}
|
||||
</Text>
|
||||
);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
@@ -107,32 +117,12 @@ const Browse = ({ navigation }) => {
|
||||
data={!searchText ? sources : search}
|
||||
keyExtractor={(item) => item.sourceId.toString()}
|
||||
renderItem={renderItem}
|
||||
extraData={pinned}
|
||||
extraData={pinnedSources}
|
||||
ListHeaderComponent={
|
||||
<View>
|
||||
{showMyAnimeList && (
|
||||
<>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
}}
|
||||
>
|
||||
Discover
|
||||
</Text>
|
||||
{/* {showNovelUpdates && (
|
||||
<DiscoverCard
|
||||
label="Novel Updates"
|
||||
onPress={() =>
|
||||
navigation.navigate(
|
||||
"BrowseNovelUpdates"
|
||||
)
|
||||
}
|
||||
icon={require("../../../assets/novelupdates.png")}
|
||||
theme={theme}
|
||||
/>
|
||||
)} */}
|
||||
<SourceHeader title="Discover" />
|
||||
{showMyAnimeList && (
|
||||
<DiscoverCard
|
||||
label="MyAnimeList"
|
||||
@@ -145,41 +135,21 @@ const Browse = ({ navigation }) => {
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{pinned.length > 0 && (
|
||||
{pinnedSources.length > 0 && (
|
||||
<FlatList
|
||||
contentContainerStyle={{ paddingBottom: 16 }}
|
||||
data={sources.filter((source) =>
|
||||
isPinned(source.sourceId)
|
||||
)}
|
||||
data={pinnedSources}
|
||||
keyExtractor={(item) =>
|
||||
item.sourceId.toString()
|
||||
}
|
||||
renderItem={renderItem}
|
||||
extraData={pinned}
|
||||
extraData={pinnedSources}
|
||||
ListHeaderComponent={
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
}}
|
||||
>
|
||||
Pinned
|
||||
</Text>
|
||||
<SourceHeader title="Pinned" />
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{sources.length > 0 && (
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
}}
|
||||
>
|
||||
Sources
|
||||
</Text>
|
||||
)}
|
||||
{sources.length > 0 && <SourceHeader title="Sources" />}
|
||||
</View>
|
||||
}
|
||||
ListEmptyComponent={
|
||||
@@ -214,5 +184,11 @@ const Browse = ({ navigation }) => {
|
||||
export default Browse;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1 },
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
sourcesHeader: {
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -18,12 +18,10 @@ import {
|
||||
const BrowseSettings = ({ navigation }) => {
|
||||
const theme = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
const {
|
||||
filters = [],
|
||||
showNovelUpdates = true,
|
||||
showMyAnimeList = true,
|
||||
} = useSelector((state) => state.sourceReducer);
|
||||
const languages = ["English", "Spanish", "Japanese"];
|
||||
const { filters = [], showMyAnimeList = true } = useSelector(
|
||||
(state) => state.sourceReducer
|
||||
);
|
||||
const languages = ["English", "Spanish", "Japanese", "Chinese"];
|
||||
const { searchAllSources = false } = useSettings();
|
||||
|
||||
const renderItem = ({ item }) => {
|
||||
@@ -87,27 +85,7 @@ const BrowseSettings = ({ navigation }) => {
|
||||
/>
|
||||
</Pressable>
|
||||
<ListSubHeader theme={theme}>Discover</ListSubHeader>
|
||||
{/* <Pressable
|
||||
android_ripple={{ color: theme.rippleColor }}
|
||||
style={{
|
||||
padding: 16,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
onPress={() => dispatch(enableDiscover("showNovelUpdates"))}
|
||||
>
|
||||
<Text style={{ color: theme.textColorPrimary }}>
|
||||
Novel Updates
|
||||
</Text>
|
||||
<Switch
|
||||
color={theme.colorAccent}
|
||||
value={showNovelUpdates}
|
||||
onValueChange={() =>
|
||||
dispatch(enableDiscover("showNovelUpdates"))
|
||||
}
|
||||
/>
|
||||
</Pressable> */}
|
||||
|
||||
<Pressable
|
||||
android_ripple={{ color: theme.rippleColor }}
|
||||
style={{
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Button,
|
||||
FlatList,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
} from "react-native";
|
||||
|
||||
import * as WebBrowser from "expo-web-browser";
|
||||
|
||||
import { ScreenContainer } from "../../../components/Common";
|
||||
import { Searchbar } from "../../../components/Searchbar/Searchbar";
|
||||
|
||||
import { useTheme } from "../../../hooks/reduxHooks";
|
||||
import { scrapeSearchResults, scrapeTopNovels } from "./NovelUpdatesScraper";
|
||||
import NovelUpdatesNovelCard from "./NovelUpdatesNovelCard";
|
||||
|
||||
const BrowseNovelUpdates = ({ navigation }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const [novels, setNovels] = useState([]);
|
||||
const [pageNo, setPageNo] = useState(1);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const [searchText, setSearchText] = useState("");
|
||||
|
||||
const getNovels = async (pageNo) => {
|
||||
const data = await scrapeTopNovels(pageNo);
|
||||
|
||||
setNovels((novels) => novels.concat(data));
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getNovels(1);
|
||||
}, []);
|
||||
|
||||
const getSearchResults = async () => {
|
||||
setLoading(true);
|
||||
|
||||
const data = await scrapeSearchResults(searchText);
|
||||
setNovels(data);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const clearSearchbar = () => {
|
||||
getNovels();
|
||||
setLoading(true);
|
||||
setSearchText("");
|
||||
};
|
||||
|
||||
const isCloseToBottom = ({
|
||||
layoutMeasurement,
|
||||
contentOffset,
|
||||
contentSize,
|
||||
}) => {
|
||||
const paddingToBottom = 20;
|
||||
return (
|
||||
layoutMeasurement.height + contentOffset.y >=
|
||||
contentSize.height - paddingToBottom
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<ScreenContainer theme={theme}>
|
||||
<Searchbar
|
||||
theme={theme}
|
||||
placeholder="Search Novel Updates"
|
||||
backAction="arrow-left"
|
||||
onBackAction={navigation.goBack}
|
||||
searchText={searchText}
|
||||
onChangeText={(text) => setSearchText(text)}
|
||||
onSubmitEditing={getSearchResults}
|
||||
clearSearchbar={clearSearchbar}
|
||||
actions={[
|
||||
{
|
||||
icon: "earth",
|
||||
onPress: () =>
|
||||
WebBrowser.openBrowserAsync(
|
||||
"https://www.novelupdates.com/"
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
{loading ? (
|
||||
<View style={{ flex: 1, justifyContent: "center" }}>
|
||||
<ActivityIndicator size={60} color={theme.colorAccent} />
|
||||
</View>
|
||||
) : (
|
||||
<FlatList
|
||||
data={novels}
|
||||
keyExtractor={(item) => item.novelName.toString()}
|
||||
renderItem={({ item }) => (
|
||||
<NovelUpdatesNovelCard
|
||||
novel={item}
|
||||
theme={theme}
|
||||
onPress={() =>
|
||||
navigation.navigate("GlobalSearch", {
|
||||
novelName: item.novelName,
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
onScroll={({ nativeEvent }) => {
|
||||
if (!searchText && isCloseToBottom(nativeEvent)) {
|
||||
getNovels(pageNo + 1);
|
||||
setPageNo((pageNo) => pageNo + 1);
|
||||
}
|
||||
}}
|
||||
ListFooterComponent={
|
||||
!searchText && (
|
||||
<View style={{ paddingVertical: 16 }}>
|
||||
<ActivityIndicator color={theme.colorAccent} />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</ScreenContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default BrowseNovelUpdates;
|
||||
|
||||
const styles = StyleSheet.create({});
|
||||
@@ -1,5 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
const NovelUpdatesBottomSheet = () => {};
|
||||
|
||||
export default NovelUpdatesBottomSheet;
|
||||
@@ -1,122 +0,0 @@
|
||||
import React from "react";
|
||||
import { StyleSheet, Pressable, Text, View, FlatList } from "react-native";
|
||||
import FastImage from "react-native-fast-image";
|
||||
import { Row } from "../../../components/Common";
|
||||
|
||||
const GenreChip = ({ children, theme }) => (
|
||||
<View style={{ flexDirection: "row" }}>
|
||||
<Text
|
||||
style={[styles.genreChip, { color: theme.textColorSecondary }]}
|
||||
numberOfLines={2}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.genreChip,
|
||||
{
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 14,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{`${"Genre "}`}
|
||||
</Text>
|
||||
{children}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
const NovelUpdatesNovelCard = ({ novel, theme, onPress }) => {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: theme.colorPrimary,
|
||||
borderRadius: 8,
|
||||
elevation: 1,
|
||||
marginHorizontal: 12,
|
||||
marginVertical: 4,
|
||||
}}
|
||||
>
|
||||
<Pressable
|
||||
style={styles.novelUpdatesCard}
|
||||
onPress={onPress}
|
||||
android_ripple={{ color: theme.rippleColor, borderless: true }}
|
||||
>
|
||||
<FastImage
|
||||
source={{ uri: novel.novelCover }}
|
||||
style={{
|
||||
width: 130,
|
||||
height: 180,
|
||||
borderTopLeftRadius: 8,
|
||||
borderBottomLeftRadius: 8,
|
||||
}}
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 8,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{ color: theme.textColorPrimary, fontSize: 17 }}
|
||||
>
|
||||
{novel.novelName}
|
||||
</Text>
|
||||
|
||||
<GenreChip theme={theme}>{novel.genres}</GenreChip>
|
||||
<View style={{ flexDirection: "row", marginBottom: 4 }}>
|
||||
<Text
|
||||
style={[
|
||||
styles.genreChip,
|
||||
{ color: theme.textColorSecondary },
|
||||
]}
|
||||
numberOfLines={2}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.genreChip,
|
||||
{
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 14,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{`${"Chapter Count "}`}
|
||||
</Text>
|
||||
{novel.chapterCount}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 12,
|
||||
color: theme.textColorSecondary,
|
||||
marginTop: 8,
|
||||
}}
|
||||
numberOfLines={4}
|
||||
>
|
||||
{novel.novelSummary}
|
||||
</Text>
|
||||
</View>
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default NovelUpdatesNovelCard;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
novelUpdatesCard: {
|
||||
flex: 1,
|
||||
flexDirection: "row",
|
||||
},
|
||||
contentContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
genreChip: {
|
||||
marginTop: 4,
|
||||
fontSize: 13,
|
||||
// fontStyle: "italic",
|
||||
},
|
||||
});
|
||||
@@ -1,130 +0,0 @@
|
||||
import cheerio from "react-native-cheerio";
|
||||
|
||||
const scrapeTopNovels = async (pageNo) => {
|
||||
const url =
|
||||
"https://www.novelupdates.com/series-ranking/?rank=sixmonths&pg=" +
|
||||
pageNo;
|
||||
|
||||
let headers = new Headers({
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent":
|
||||
"'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36",
|
||||
});
|
||||
|
||||
const res = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: headers,
|
||||
});
|
||||
const body = await res.text();
|
||||
|
||||
const $ = cheerio.load(body);
|
||||
|
||||
const novels = [];
|
||||
|
||||
$("div.search_main_box_nu").each(function (res) {
|
||||
const novelCover = $(this).find("img").attr("src");
|
||||
const novelName = $(this).find(".search_title > a").text();
|
||||
|
||||
const genres = $(this)
|
||||
.find(".search_genre")
|
||||
.text()
|
||||
.replace(/\s/g, ", ");
|
||||
|
||||
const novelSummary =
|
||||
$(this).find(".search_genre")[0].nextSibling.nodeValue;
|
||||
|
||||
let chapterCount = "";
|
||||
|
||||
$(this)
|
||||
.find(".search_stats > span")
|
||||
.each(function () {
|
||||
let detail = $(this).text();
|
||||
|
||||
if (detail.includes("Chapters")) {
|
||||
chapterCount = detail.replace(" Chapters", "");
|
||||
}
|
||||
});
|
||||
|
||||
// const chapterCount = $(this)
|
||||
// .find('i[title="Chapter Count"]')
|
||||
// const updateFrequency = $(this)
|
||||
// .find('i[title="Updates Frequency"]')
|
||||
|
||||
const novel = {
|
||||
novelName,
|
||||
novelCover,
|
||||
genres,
|
||||
novelSummary,
|
||||
chapterCount,
|
||||
// updateFrequency,
|
||||
};
|
||||
|
||||
novels.push(novel);
|
||||
});
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
const scrapeSearchResults = async (searchTerm) => {
|
||||
const url =
|
||||
"https://www.novelupdates.com/?s=" +
|
||||
searchTerm +
|
||||
"&post_type=seriesplans";
|
||||
|
||||
let headers = new Headers({
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent":
|
||||
"'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36",
|
||||
});
|
||||
|
||||
const res = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: headers,
|
||||
});
|
||||
const body = await res.text();
|
||||
|
||||
const $ = cheerio.load(body);
|
||||
|
||||
const novels = [];
|
||||
|
||||
$("div.search_main_box_nu").each(function (res) {
|
||||
const novelCover = $(this).find("img").attr("src");
|
||||
const novelName = $(this).find(".search_title > a").text();
|
||||
|
||||
const genres = $(this)
|
||||
.find(".search_genre")
|
||||
.text()
|
||||
.replace(/\s/g, ", ");
|
||||
|
||||
const novelSummary =
|
||||
$(this).find(".search_genre")[0].nextSibling.nodeValue;
|
||||
|
||||
let chapterCount = "";
|
||||
|
||||
$(this)
|
||||
.find(".search_stats > span")
|
||||
.each(function () {
|
||||
let detail = $(this).text();
|
||||
|
||||
if (detail.includes("Chapters")) {
|
||||
chapterCount = detail.replace(" Chapters", "");
|
||||
}
|
||||
});
|
||||
|
||||
const novel = {
|
||||
novelName,
|
||||
novelCover,
|
||||
genres,
|
||||
novelSummary,
|
||||
chapterCount,
|
||||
};
|
||||
|
||||
novels.push(novel);
|
||||
});
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
export { scrapeTopNovels, scrapeSearchResults };
|
||||
@@ -51,6 +51,7 @@ import FitImage from "react-native-fit-image";
|
||||
import ChapterFooter from "./components/ChapterFooter";
|
||||
import VerticalScrollbar from "./components/VerticalScrollbar";
|
||||
import GestureRecognizer from "react-native-swipe-gestures";
|
||||
import TextToComponents from "./TextToComponent";
|
||||
|
||||
const Chapter = ({ route, navigation }) => {
|
||||
const {
|
||||
@@ -201,8 +202,7 @@ const Chapter = ({ route, navigation }) => {
|
||||
};
|
||||
|
||||
const scrollToSavedProgress = useCallback((event) => {
|
||||
StatusBar.setHidden(true);
|
||||
hideNavigationBar();
|
||||
setImmersiveMode();
|
||||
if (position && firstLayout) {
|
||||
position.percentage < 100 &&
|
||||
scrollViewRef.current.scrollTo({
|
||||
@@ -215,10 +215,7 @@ const Chapter = ({ route, navigation }) => {
|
||||
}, []);
|
||||
|
||||
const hideHeader = () => {
|
||||
if (!hidden) {
|
||||
StatusBar.setHidden(true);
|
||||
hideNavigationBar();
|
||||
}
|
||||
!hidden && setImmersiveMode();
|
||||
setHidden(!hidden);
|
||||
};
|
||||
|
||||
@@ -343,61 +340,12 @@ const Chapter = ({ route, navigation }) => {
|
||||
onLayout={scrollToSavedProgress}
|
||||
onPress={hideHeader}
|
||||
>
|
||||
{chapter.chapterText
|
||||
.trim()
|
||||
.split(/(\[https.*?\])|(_.*?_)/g)
|
||||
.map((part, index) => {
|
||||
if (part) {
|
||||
const isItalic =
|
||||
part.match(/_(.*?)_/);
|
||||
const isImage =
|
||||
part.match(/\[https.*?\]/);
|
||||
|
||||
if (isImage) {
|
||||
return (
|
||||
<FitImage
|
||||
source={{
|
||||
uri: part.slice(
|
||||
1,
|
||||
-1
|
||||
),
|
||||
}}
|
||||
key={index}
|
||||
/>
|
||||
);
|
||||
} else if (isItalic) {
|
||||
return (
|
||||
<Text
|
||||
style={[
|
||||
readerStyles,
|
||||
{
|
||||
fontStyle:
|
||||
"italic",
|
||||
},
|
||||
]}
|
||||
selectable={
|
||||
textSelectable
|
||||
}
|
||||
key={index}
|
||||
>
|
||||
{isItalic[1]}
|
||||
</Text>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Text
|
||||
style={readerStyles}
|
||||
selectable={
|
||||
textSelectable
|
||||
}
|
||||
key={index}
|
||||
>
|
||||
{part}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
}
|
||||
})}
|
||||
<TextToComponents
|
||||
text={chapter.chapterText}
|
||||
textStyle={readerStyles}
|
||||
textSize={reader.textSize}
|
||||
textSelectable={textSelectable}
|
||||
/>
|
||||
</Pressable>
|
||||
</GestureRecognizer>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import React from "react";
|
||||
import { Text } from "react-native";
|
||||
|
||||
import FitImage from "react-native-fit-image";
|
||||
|
||||
const getHeadingSize = (size) => {
|
||||
const headings = {
|
||||
h1: 6,
|
||||
h2: 5,
|
||||
h3: 4,
|
||||
h4: 3,
|
||||
h5: 2,
|
||||
h6: 1,
|
||||
};
|
||||
|
||||
return headings[size];
|
||||
};
|
||||
|
||||
const TextToComponents = ({ text, textStyle, textSelectable, textSize }) => {
|
||||
// let imagePattern = /(\[Image\]\(http.*?\))/;
|
||||
// let italicPattern = /(_.*?_)/;
|
||||
// let headingPattern = /(\[h[1-6]\]\(.*?\))/;
|
||||
|
||||
// patterns = [imagePattern, headingPattern, italicPattern];
|
||||
// regex = new RegExp(patterns.join("|"), "g");
|
||||
|
||||
// console.log(regex);
|
||||
|
||||
regex = /(\[Image\]\(http.*?\))|(\[h[1-6]\]\(.*?\))|(_.*?_)/;
|
||||
|
||||
let parseText = text.trim().split(regex);
|
||||
|
||||
// console.log(parseText);
|
||||
|
||||
return parseText.map((part, index) => {
|
||||
if (part) {
|
||||
const isItalic = part.match(/_(.*?)_/);
|
||||
const isImage = part.match(/\[Image\]\((http.*?)\)/);
|
||||
const isHeading = part.match(/\[(h[1-6])\]\((.*?)\)/);
|
||||
|
||||
if (isImage) {
|
||||
return <FitImage source={{ uri: isImage[1] }} key={index} />;
|
||||
} else if (isItalic) {
|
||||
return (
|
||||
<Text
|
||||
style={[textStyle, { fontStyle: "italic" }]}
|
||||
selectable={textSelectable}
|
||||
key={index}
|
||||
>
|
||||
{isItalic[1]}
|
||||
</Text>
|
||||
);
|
||||
} else if (isHeading) {
|
||||
return (
|
||||
<Text
|
||||
style={[
|
||||
textStyle,
|
||||
{
|
||||
fontSize:
|
||||
textSize + getHeadingSize(isHeading[1]),
|
||||
fontWeight: "bold",
|
||||
},
|
||||
]}
|
||||
selectable={textSelectable}
|
||||
key={index}
|
||||
>
|
||||
{isHeading[2]}
|
||||
</Text>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Text
|
||||
style={textStyle}
|
||||
selectable={textSelectable}
|
||||
key={index}
|
||||
>
|
||||
{part}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default TextToComponents;
|
||||
@@ -1,11 +1,10 @@
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import React, { useState, useCallback } from "react";
|
||||
import {
|
||||
StyleSheet,
|
||||
View,
|
||||
FlatList,
|
||||
ActivityIndicator,
|
||||
RefreshControl,
|
||||
Button,
|
||||
Text,
|
||||
} from "react-native";
|
||||
import { useFocusEffect } from "@react-navigation/native";
|
||||
@@ -15,7 +14,6 @@ import {
|
||||
getUpdatesAction,
|
||||
updateLibraryAction,
|
||||
} from "../../redux/updates/updates.actions";
|
||||
import { Appbar } from "./components/Appbar";
|
||||
import UpdatesItem from "./components/UpdatesItem";
|
||||
import { useTheme } from "../../hooks/reduxHooks";
|
||||
import { Searchbar } from "../../components/Searchbar/Searchbar";
|
||||
@@ -44,20 +42,39 @@ const Updates = ({ navigation }) => {
|
||||
setRefreshing(false);
|
||||
};
|
||||
|
||||
const onPress = (item) =>
|
||||
navigation.navigate("Chapter", {
|
||||
chapterId: item.chapterId,
|
||||
chapterUrl: item.chapterUrl,
|
||||
sourceId: item.sourceId,
|
||||
novelUrl: item.novelUrl,
|
||||
chapterName: item.chapterName,
|
||||
novelId: item.novelId,
|
||||
novelName: item.novelName,
|
||||
bookmark: item.bookmark,
|
||||
});
|
||||
const onPress = useCallback(
|
||||
(item) =>
|
||||
navigation.navigate("Chapter", {
|
||||
chapterId: item.chapterId,
|
||||
chapterUrl: item.chapterUrl,
|
||||
sourceId: item.sourceId,
|
||||
novelUrl: item.novelUrl,
|
||||
chapterName: item.chapterName,
|
||||
novelId: item.novelId,
|
||||
novelName: item.novelName,
|
||||
bookmark: item.bookmark,
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
const onPressCover = useCallback(
|
||||
(item) =>
|
||||
navigation.navigate("Novel", {
|
||||
sourceId: item.sourceId,
|
||||
novelUrl: item.novelUrl,
|
||||
novelName: item.novelName,
|
||||
novelCover: item.novelCover,
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
const renderItem = ({ item }) => (
|
||||
<UpdatesItem item={item} theme={theme} onPress={() => onPress(item)} />
|
||||
<UpdatesItem
|
||||
item={item}
|
||||
theme={theme}
|
||||
onPress={() => onPress(item)}
|
||||
onPressCover={() => onPressCover(item)}
|
||||
/>
|
||||
);
|
||||
|
||||
const ListFooterComponent = () =>
|
||||
@@ -103,6 +120,8 @@ const Updates = ({ navigation }) => {
|
||||
setSearchResults(results);
|
||||
};
|
||||
|
||||
const keyExtractor = useCallback((item) => item.updateId.toString(), []);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
@@ -143,12 +162,12 @@ const Updates = ({ navigation }) => {
|
||||
</Text>
|
||||
}
|
||||
data={item.chapters}
|
||||
keyExtractor={(item) => item.updateId.toString()}
|
||||
keyExtractor={keyExtractor}
|
||||
renderItem={renderItem}
|
||||
/>
|
||||
)}
|
||||
ListFooterComponent={ListFooterComponent()}
|
||||
ListEmptyComponent={ListEmptyComponent()}
|
||||
ListFooterComponent={<ListFooterComponent />}
|
||||
ListEmptyComponent={<ListEmptyComponent />}
|
||||
refreshControl={refreshControl()}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from "react";
|
||||
import { View, Text, StyleSheet, Pressable } from "react-native";
|
||||
import FastImage from "react-native-fast-image";
|
||||
|
||||
const UpdateCard = ({ item, theme, onPress }) => {
|
||||
const UpdateCard = ({ item, theme, onPress, onPressCover }) => {
|
||||
return (
|
||||
<Pressable
|
||||
style={styles.updateCard}
|
||||
@@ -10,10 +10,12 @@ const UpdateCard = ({ item, theme, onPress }) => {
|
||||
android_ripple={{ color: theme.rippleColor }}
|
||||
>
|
||||
<>
|
||||
<FastImage
|
||||
source={{ uri: item.novelCover }}
|
||||
style={styles.updateIcon}
|
||||
/>
|
||||
<Pressable onPress={onPressCover}>
|
||||
<FastImage
|
||||
source={{ uri: item.novelCover }}
|
||||
style={styles.updateIcon}
|
||||
/>
|
||||
</Pressable>
|
||||
<View style={styles.chapterDetails}>
|
||||
<Text
|
||||
style={[
|
||||
|
||||
+15
-15
@@ -59,12 +59,12 @@ export const updateNovel = async (sourceId, novelUrl, novelId) => {
|
||||
"INSERT OR IGNORE INTO updates (chapterId, novelId, updateTime) values (?, ?, (datetime('now','localtime')))",
|
||||
[insertId, novelId],
|
||||
(txObj, res) => {
|
||||
console.log(
|
||||
"Inserted Chapter Id -> " +
|
||||
insertId +
|
||||
" Novel Id " +
|
||||
novelId
|
||||
);
|
||||
// console.log(
|
||||
// "Inserted Chapter Id -> " +
|
||||
// insertId +
|
||||
// " Novel Id " +
|
||||
// novelId
|
||||
// );
|
||||
},
|
||||
(txObj, error) => console.log("Error ", error)
|
||||
);
|
||||
@@ -91,14 +91,14 @@ export const updateNovelChapters = async (sourceId, novelUrl, novelId) => {
|
||||
],
|
||||
(txObj, { insertId }) => {
|
||||
if (insertId !== -1) {
|
||||
console.log(
|
||||
"Inserted Chapter " +
|
||||
chapter.chapterUrl +
|
||||
" Chapter Name " +
|
||||
chapter.chapterName +
|
||||
" Novel Id " +
|
||||
novelId
|
||||
);
|
||||
// console.log(
|
||||
// "Inserted Chapter " +
|
||||
// chapter.chapterUrl +
|
||||
// " Chapter Name " +
|
||||
// chapter.chapterName +
|
||||
// " Novel Id " +
|
||||
// novelId
|
||||
// );
|
||||
tx.executeSql(
|
||||
"INSERT OR IGNORE INTO updates (chapterId, novelId, updateTime) values (?, ?, (datetime('now','localtime')))",
|
||||
[insertId, novelId],
|
||||
@@ -151,7 +151,7 @@ export const updateAllNovels = async () => {
|
||||
libraryNovels[i].novelUrl,
|
||||
libraryNovels[i].novelId
|
||||
);
|
||||
console.log(libraryNovels[i].novelName + " Updated");
|
||||
// console.log(libraryNovels[i].novelName + " Updated");
|
||||
await BackgroundService.updateNotification({
|
||||
taskTitle: libraryNovels[i].novelName,
|
||||
taskDesc: i + 1 + "/" + libraryNovels.length,
|
||||
|
||||
@@ -143,17 +143,9 @@ const parseChapter = async (novelUrl, chapterUrl) => {
|
||||
|
||||
const chapterName = $(".block-title > h1").text().replace(" - ", "");
|
||||
|
||||
$(".desc")
|
||||
.find("hr")
|
||||
.each(function (result) {
|
||||
$(this).remove();
|
||||
});
|
||||
|
||||
$(".alert").remove();
|
||||
$(".hidden").remove();
|
||||
|
||||
var re = new RegExp(String.fromCharCode(160), "g");
|
||||
|
||||
let chapterText = $(".desc").html();
|
||||
|
||||
chapterText = htmlToText(chapterText).replace(
|
||||
|
||||
@@ -23,13 +23,14 @@ const htmlToText = (html, options = {}) => {
|
||||
// /<\s*h(\d)[^>]*>(.*?)<\/h(\d)>/gim,
|
||||
// `${"#".repeat("$1")}$2${"#".repeat("$1")}`
|
||||
// )
|
||||
// .replace(/<strong>(.*?)<\/strong>/gi, "*$1*")
|
||||
|
||||
.replace(/<(h[1-6])>(.*?)<\/h[1-6]>/gi, "[$1]($2)")
|
||||
// .replace(/<strong>(.*?)<\/strong>/gi, "*$1*")
|
||||
.replace(
|
||||
/<\s*a[^>]*href=['"](.*?)['"][^>]*>([\s\S]*?)<\/\s*a\s*>/gi,
|
||||
"$2"
|
||||
"[Link: $2]($1)"
|
||||
)
|
||||
.replace(/<\s*img[^>]*src=['"](.*?)['"][^>]*>/gi, "[$1]")
|
||||
.replace(/<\s*img[^>]*src=['"](.*?)['"][^>]*>/gi, "[Image]($1)")
|
||||
// Remove all remaining tags.
|
||||
.replace(/(<([^>]+)>)/gi, "")
|
||||
// Make sure there are never more than two
|
||||
|
||||
+55
-532
@@ -52,537 +52,60 @@ import NovelUpdatesScraper from "./en/novelupdates";
|
||||
import RanobesScraper from "./en/ranobes";
|
||||
import YushuboScraper from "./ch/yushubo";
|
||||
|
||||
export const sources = [
|
||||
/**
|
||||
* English
|
||||
*/
|
||||
{
|
||||
sourceId: 50,
|
||||
sourceUrl: "https://www.novelupdates.com/",
|
||||
sourceName: "Novel Updates",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/en/novelupdates/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 1,
|
||||
url: "https://boxnovel.com/",
|
||||
sourceName: "BoxNovel",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/en/boxnovel/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 2,
|
||||
sourceName: "ReadLightNovel",
|
||||
url: "https://www.readlightnovel.org",
|
||||
lang: "English",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/en/readlightnovel/icon.png?raw=true",
|
||||
},
|
||||
{
|
||||
sourceId: 3,
|
||||
url: "https://fastnovel.net/",
|
||||
sourceName: "FastNovel",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/en/fastnovel/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 4,
|
||||
url: "https://readnovelfull.com/",
|
||||
sourceName: "ReadNovelFull",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/en/readnovelfull/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 5,
|
||||
url: "https://www.mtlnovel.com/",
|
||||
sourceName: "MTLNovel",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/en/mtlnovel/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 6,
|
||||
url: "https://www.novelhall.com/",
|
||||
sourceName: "NovelHall",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/en/novelhall/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 7,
|
||||
url: "https://www.wuxiaworld.com/",
|
||||
sourceName: "WuxiaWorld",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/en/wuxiaworld/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 8,
|
||||
url: "https://novelfull.com/",
|
||||
sourceName: "NovelFull",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/en/novelfull/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 9,
|
||||
url: "https://noveltrench.com/",
|
||||
sourceName: "NovelTrench",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/en/noveltrench/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 10,
|
||||
url: "https://vipnovel.com/",
|
||||
sourceName: "VipNovel",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/en/vipnovel/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 11,
|
||||
url: "https://kisslightnovels.info/",
|
||||
sourceName: "KissLightNovels",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/en/kisslightnovels/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 12,
|
||||
url: "https://wuxiaworldsite.co/",
|
||||
sourceName: "WuxiaWorldSite",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/en/wuxiaworldsite/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 13,
|
||||
url: "https://freewebnovel.com/",
|
||||
sourceName: "FreeWebNovel",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/en/freewebnovel/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 14,
|
||||
url: "https://jpmtl.com/",
|
||||
sourceName: "JPMTL",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/en/jpmtl/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 15,
|
||||
url: "https://www.lightnovelpub.com/",
|
||||
sourceName: "LightNovelPub",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/en/lightnovelpub/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 16,
|
||||
url: "https://www.wuxiaworld.co/",
|
||||
sourceName: "WuxiaWorld.co",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/en/wuxiaworldco/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 17,
|
||||
url: "http://www.tapread.com/",
|
||||
sourceName: "TapRead",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/en/tapread/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 18,
|
||||
url: "https://www.novelupdates.cc/",
|
||||
sourceName: "NovelUpdates.cc",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/en/novelupdatescc/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 19,
|
||||
url: "https://m.readlightnovel.cc/",
|
||||
sourceName: "ReadLightNovel.cc",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/en/readlightnovelcc/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 20,
|
||||
url: "http://wuxiaworld.cloud/",
|
||||
sourceName: "WuxiaWorld.cloud",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/en/wuxiaworldcloud/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 21,
|
||||
url: "https://woopread.com/",
|
||||
sourceName: "WoopRead",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/en/woopread/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 22,
|
||||
url: "https://foxaholic.com/",
|
||||
sourceName: "Foxaholic",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/en/foxaholic/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 27,
|
||||
url: "https://comrademao.com/",
|
||||
sourceName: "ComradeMao",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/en/comrademao/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 33,
|
||||
url: "https://www.novelpassion.com/",
|
||||
sourceName: "NovelPassion",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/en/novelpassion/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 34,
|
||||
url: "https://www.royalroad.com/",
|
||||
sourceName: "Royal Road",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/en/royalroad/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
|
||||
{
|
||||
sourceId: 35,
|
||||
url: "https://www.scribblehub.com/",
|
||||
sourceName: "Scribble Hub",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/en/scribblehub/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 37,
|
||||
url: "https://lnmtl.com/",
|
||||
sourceName: "LNMTL",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/en/lnmtl/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 38,
|
||||
sourceUrl: "https://skynovel.org/",
|
||||
sourceName: "SkyNovel",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/multisrc/madara/icons/skynovel.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 39,
|
||||
sourceUrl: "https://novelcake.com/",
|
||||
sourceName: "NovelCake",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/multisrc/madara/icons/novelcake.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 40,
|
||||
sourceUrl: "https://novelsrock.com/",
|
||||
sourceName: "NovelsRock",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/multisrc/madara/icons/novelsrock.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 41,
|
||||
sourceUrl: "https://zinnovel.com/",
|
||||
sourceName: "ZinnNovel",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/multisrc/madara/icons/zinnovel.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 42,
|
||||
sourceUrl: "https://noveltranslate.com/",
|
||||
sourceName: "NovelTranslate",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/multisrc/madara/icons/noveltranslate.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 43,
|
||||
sourceUrl: "https://www.lunarletters.com/",
|
||||
sourceName: "LunarLetters",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/multisrc/madara/icons/lunarletters.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 44,
|
||||
sourceUrl: "https://sleepytranslations.com/",
|
||||
sourceName: "SleepyTranslations",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/multisrc/madara/icons/sleepytranslations.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 45,
|
||||
sourceUrl: "https://freenovel.me/",
|
||||
sourceName: "FreeNovelMe",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/multisrc/madara/icons/freenovel.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 46,
|
||||
sourceUrl: "https://1stkissnovel.love/",
|
||||
sourceName: "1stKissNovel",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/multisrc/madara/icons/1stkissnovel.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 47,
|
||||
sourceUrl: "https://daonovel.com/",
|
||||
sourceName: "DaoNovel",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/multisrc/madara/icons/daonovel.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 48,
|
||||
sourceUrl: "https://www.wuxia.blog/",
|
||||
sourceName: "Wuxia.Blog",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/en/wuxiablog/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 49,
|
||||
sourceUrl: "https://wuxia.city/",
|
||||
sourceName: "Wuxia.City",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/en/wuxiacity/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 51,
|
||||
sourceUrl: "https://ranobes.net/",
|
||||
sourceName: "Ranobes",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/en/ranobes/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
|
||||
/**
|
||||
* Spanish
|
||||
*/
|
||||
|
||||
{
|
||||
sourceId: 23,
|
||||
url: "https://tunovelaligera.com/",
|
||||
sourceName: "Tunovelaligera",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/es/tunovelaligera/icon.png?raw=true",
|
||||
lang: "Spanish",
|
||||
},
|
||||
{
|
||||
sourceId: 24,
|
||||
url: "https://www.skynovels.net/",
|
||||
sourceName: "SkyNovels",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/es/skynovels/icon.png?raw=true",
|
||||
lang: "Spanish",
|
||||
},
|
||||
{
|
||||
sourceId: 25,
|
||||
url: "https://einherjarproject.net/",
|
||||
sourceName: "EinherjarProject",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/es/einherjarproject/icon.png?raw=true",
|
||||
lang: "Spanish",
|
||||
},
|
||||
{
|
||||
sourceId: 26,
|
||||
url: "https://novelasligera.com/",
|
||||
sourceName: "NovelasLigera",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/es/novelasligera/icon.png?raw=true",
|
||||
lang: "Spanish",
|
||||
},
|
||||
{
|
||||
sourceId: 28,
|
||||
url: "https://yuukitls.com/",
|
||||
sourceName: "YuukiTls",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/es/yuukitls/icon.png?raw=true",
|
||||
lang: "Spanish",
|
||||
},
|
||||
{
|
||||
sourceId: 29,
|
||||
url: "https://hasutl.wordpress.com/",
|
||||
sourceName: "Hasu Translations",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/es/hasutl/icon.png?raw=true",
|
||||
lang: "Spanish",
|
||||
},
|
||||
{
|
||||
sourceId: 30,
|
||||
url: "https://oasistranslations.wordpress.com/",
|
||||
sourceName: "Oasis Translations",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/es/oasistranslations/icon.png?raw=true",
|
||||
lang: "Spanish",
|
||||
},
|
||||
{
|
||||
sourceId: 31,
|
||||
url: "http://www.novelawuxia.com/",
|
||||
sourceName: "Novela Wuxia",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/es/novelawuxia/icon.png?raw=true",
|
||||
lang: "Spanish",
|
||||
},
|
||||
|
||||
/**
|
||||
* Japanese
|
||||
*/
|
||||
{
|
||||
sourceId: 36,
|
||||
url: "https://syosetu.com",
|
||||
sourceName: "Syosetu",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/jp/syosetu/icon.png?raw=true",
|
||||
lang: "Japanese",
|
||||
},
|
||||
|
||||
/**
|
||||
* Chinese
|
||||
*/
|
||||
{
|
||||
sourceId: 52,
|
||||
url: "https://m.yushubo.com",
|
||||
sourceName: "Yushubo",
|
||||
icon: "https://github.com/LNReader/lnreader-sources/blob/main/src/ch/yushubo/icon.png?raw=true",
|
||||
lang: "Chinese",
|
||||
},
|
||||
];
|
||||
|
||||
export const getSource = (sourceId) => {
|
||||
switch (sourceId) {
|
||||
case 1: {
|
||||
return BoxNovelScraper;
|
||||
}
|
||||
case 2: {
|
||||
return ReadLightNovelScraper;
|
||||
}
|
||||
case 3: {
|
||||
return fastNovelScraper;
|
||||
}
|
||||
case 4: {
|
||||
return readNovelFullScraper;
|
||||
}
|
||||
case 5: {
|
||||
return mtlNovelScraper;
|
||||
}
|
||||
case 6: {
|
||||
return novelhallScraper;
|
||||
}
|
||||
case 7: {
|
||||
return WuxiaWorldScraper;
|
||||
}
|
||||
case 8: {
|
||||
return novelFullScraper;
|
||||
}
|
||||
case 9: {
|
||||
return novelTrenchScraper;
|
||||
}
|
||||
case 10: {
|
||||
return vipNovelScraper;
|
||||
}
|
||||
case 11: {
|
||||
return kissLightNovelScraper;
|
||||
}
|
||||
case 12: {
|
||||
return WuxiaWorldSiteScraper;
|
||||
}
|
||||
case 13: {
|
||||
return FreeWebNovelScraper;
|
||||
}
|
||||
case 14: {
|
||||
return JPMTLScraper;
|
||||
}
|
||||
case 15: {
|
||||
return lightNovelPubScraper;
|
||||
}
|
||||
case 16: {
|
||||
return WuxiaWorldCoScraper;
|
||||
}
|
||||
case 17: {
|
||||
return tapReadScraper;
|
||||
}
|
||||
case 18: {
|
||||
return novelUpdatesCcScraper;
|
||||
}
|
||||
case 19: {
|
||||
return readLightNovelCcScraper;
|
||||
}
|
||||
case 20: {
|
||||
return WuxiaWorldCloudScraper;
|
||||
}
|
||||
case 21: {
|
||||
return WoopReadScraper;
|
||||
}
|
||||
case 22: {
|
||||
return FoxaHolicScraper;
|
||||
}
|
||||
case 23: {
|
||||
return TuNovelaLigeraScraper;
|
||||
}
|
||||
case 24: {
|
||||
return SkyNovelsScraper;
|
||||
}
|
||||
case 25: {
|
||||
return EinharjarProjectScraper;
|
||||
}
|
||||
case 26: {
|
||||
return NovelasLigeraScraper;
|
||||
}
|
||||
case 27: {
|
||||
return ComradeMaoScraper;
|
||||
}
|
||||
case 28: {
|
||||
return YuukiTlsScraper;
|
||||
}
|
||||
case 29: {
|
||||
return HasuTlScraper;
|
||||
}
|
||||
case 30: {
|
||||
return OasisTranslationsScraper;
|
||||
}
|
||||
case 31: {
|
||||
return NovelaWuxiaScraper;
|
||||
}
|
||||
case 33: {
|
||||
return NovelPassionScraper;
|
||||
}
|
||||
case 34: {
|
||||
return RoyalRoadScraper;
|
||||
}
|
||||
case 35: {
|
||||
return ScribbleHubScraper;
|
||||
}
|
||||
case 36: {
|
||||
return SyosetuScraper;
|
||||
}
|
||||
case 37: {
|
||||
return LNMTLScraper;
|
||||
}
|
||||
case 38: {
|
||||
return SkyNovelScraper;
|
||||
}
|
||||
case 39: {
|
||||
return NovelCakeScraper;
|
||||
}
|
||||
case 40: {
|
||||
return NovelsRockScraper;
|
||||
}
|
||||
case 41: {
|
||||
return ZinnNovelScraper;
|
||||
}
|
||||
case 42: {
|
||||
return NovelTranslateScraper;
|
||||
}
|
||||
case 43: {
|
||||
return LunarLettersScraper;
|
||||
}
|
||||
case 44: {
|
||||
return SleepyTranslationsScraper;
|
||||
}
|
||||
case 45: {
|
||||
return FreeNovelScraper;
|
||||
}
|
||||
case 46: {
|
||||
return FirstKissNovelScraper;
|
||||
}
|
||||
case 47: {
|
||||
return DaoNovelScraper;
|
||||
}
|
||||
case 48: {
|
||||
return WuxiaBlogScraper;
|
||||
}
|
||||
case 49: {
|
||||
return WuxiaCityScraper;
|
||||
}
|
||||
case 50: {
|
||||
return NovelUpdatesScraper;
|
||||
}
|
||||
case 51: {
|
||||
return RanobesScraper;
|
||||
}
|
||||
case 52: {
|
||||
return YushuboScraper;
|
||||
}
|
||||
}
|
||||
const scrapers = {
|
||||
1: BoxNovelScraper,
|
||||
2: ReadLightNovelScraper,
|
||||
3: fastNovelScraper,
|
||||
4: readNovelFullScraper,
|
||||
5: mtlNovelScraper,
|
||||
6: novelhallScraper,
|
||||
7: WuxiaWorldScraper,
|
||||
8: novelFullScraper,
|
||||
9: novelTrenchScraper,
|
||||
10: vipNovelScraper,
|
||||
11: kissLightNovelScraper,
|
||||
12: WuxiaWorldSiteScraper,
|
||||
13: FreeWebNovelScraper,
|
||||
14: JPMTLScraper,
|
||||
15: lightNovelPubScraper,
|
||||
16: WuxiaWorldCoScraper,
|
||||
17: tapReadScraper,
|
||||
18: novelUpdatesCcScraper,
|
||||
19: readLightNovelCcScraper,
|
||||
20: WuxiaWorldCloudScraper,
|
||||
21: WoopReadScraper,
|
||||
22: FoxaHolicScraper,
|
||||
23: TuNovelaLigeraScraper,
|
||||
24: SkyNovelsScraper,
|
||||
25: EinharjarProjectScraper,
|
||||
26: NovelasLigeraScraper,
|
||||
27: ComradeMaoScraper,
|
||||
28: YuukiTlsScraper,
|
||||
29: HasuTlScraper,
|
||||
30: OasisTranslationsScraper,
|
||||
31: NovelaWuxiaScraper,
|
||||
33: NovelPassionScraper,
|
||||
34: RoyalRoadScraper,
|
||||
35: ScribbleHubScraper,
|
||||
36: SyosetuScraper,
|
||||
37: LNMTLScraper,
|
||||
38: SkyNovelScraper,
|
||||
39: NovelCakeScraper,
|
||||
40: NovelsRockScraper,
|
||||
41: ZinnNovelScraper,
|
||||
42: NovelTranslateScraper,
|
||||
43: LunarLettersScraper,
|
||||
44: SleepyTranslationsScraper,
|
||||
45: FreeNovelScraper,
|
||||
46: FirstKissNovelScraper,
|
||||
47: DaoNovelScraper,
|
||||
48: WuxiaBlogScraper,
|
||||
49: WuxiaCityScraper,
|
||||
50: NovelUpdatesScraper,
|
||||
51: RanobesScraper,
|
||||
52: YushuboScraper,
|
||||
};
|
||||
|
||||
return scrapers[sourceId];
|
||||
};
|
||||
|
||||
@@ -0,0 +1,359 @@
|
||||
[
|
||||
{
|
||||
"sourceId": 1,
|
||||
"url": "https://boxnovel.com/",
|
||||
"sourceName": "BoxNovel",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/boxnovel/icon.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 2,
|
||||
"sourceName": "ReadLightNovel",
|
||||
"url": "https://www.readlightnovel.org",
|
||||
"lang": "English",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/readlightnovel/icon.png?raw=true"
|
||||
},
|
||||
{
|
||||
"sourceId": 3,
|
||||
"url": "https://fastnovel.net/",
|
||||
"sourceName": "FastNovel",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/fastnovel/icon.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 4,
|
||||
"url": "https://readnovelfull.com/",
|
||||
"sourceName": "ReadNovelFull",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/readnovelfull/icon.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 5,
|
||||
"url": "https://www.mtlnovel.com/",
|
||||
"sourceName": "MTLNovel",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/mtlnovel/icon.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 6,
|
||||
"url": "https://www.novelhall.com/",
|
||||
"sourceName": "NovelHall",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/novelhall/icon.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 7,
|
||||
"url": "https://www.wuxiaworld.com/",
|
||||
"sourceName": "WuxiaWorld",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/wuxiaworld/icon.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 8,
|
||||
"url": "https://novelfull.com/",
|
||||
"sourceName": "NovelFull",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/novelfull/icon.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 9,
|
||||
"url": "https://noveltrench.com/",
|
||||
"sourceName": "NovelTrench",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/noveltrench/icon.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 10,
|
||||
"url": "https://vipnovel.com/",
|
||||
"sourceName": "VipNovel",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/vipnovel/icon.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 11,
|
||||
"url": "https://kisslightnovels.info/",
|
||||
"sourceName": "KissLightNovels",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/kisslightnovels/icon.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 12,
|
||||
"url": "https://wuxiaworldsite.co/",
|
||||
"sourceName": "WuxiaWorldSite",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/wuxiaworldsite/icon.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 13,
|
||||
"url": "https://freewebnovel.com/",
|
||||
"sourceName": "FreeWebNovel",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/freewebnovel/icon.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 14,
|
||||
"url": "https://jpmtl.com/",
|
||||
"sourceName": "JPMTL",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/jpmtl/icon.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 15,
|
||||
"url": "https://www.lightnovelpub.com/",
|
||||
"sourceName": "LightNovelPub",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/lightnovelpub/icon.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 16,
|
||||
"url": "https://www.wuxiaworld.co/",
|
||||
"sourceName": "WuxiaWorld.co",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/wuxiaworldco/icon.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 17,
|
||||
"url": "http://www.tapread.com/",
|
||||
"sourceName": "TapRead",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/tapread/icon.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 18,
|
||||
"url": "https://www.novelupdates.cc/",
|
||||
"sourceName": "NovelUpdates.cc",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/novelupdatescc/icon.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 19,
|
||||
"url": "https://m.readlightnovel.cc/",
|
||||
"sourceName": "ReadLightNovel.cc",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/readlightnovelcc/icon.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 20,
|
||||
"url": "http://wuxiaworld.cloud/",
|
||||
"sourceName": "WuxiaWorld.cloud",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/wuxiaworldcloud/icon.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 21,
|
||||
"url": "https://woopread.com/",
|
||||
"sourceName": "WoopRead",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/woopread/icon.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 22,
|
||||
"url": "https://foxaholic.com/",
|
||||
"sourceName": "Foxaholic",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/foxaholic/icon.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 23,
|
||||
"url": "https://tunovelaligera.com/",
|
||||
"sourceName": "Tunovelaligera",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/es/tunovelaligera/icon.png?raw=true",
|
||||
"lang": "Spanish"
|
||||
},
|
||||
{
|
||||
"sourceId": 24,
|
||||
"url": "https://www.skynovels.net/",
|
||||
"sourceName": "SkyNovels",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/es/skynovels/icon.png?raw=true",
|
||||
"lang": "Spanish"
|
||||
},
|
||||
{
|
||||
"sourceId": 25,
|
||||
"url": "https://einherjarproject.net/",
|
||||
"sourceName": "EinherjarProject",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/es/einherjarproject/icon.png?raw=true",
|
||||
"lang": "Spanish"
|
||||
},
|
||||
{
|
||||
"sourceId": 26,
|
||||
"url": "https://novelasligera.com/",
|
||||
"sourceName": "NovelasLigera",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/es/novelasligera/icon.png?raw=true",
|
||||
"lang": "Spanish"
|
||||
},
|
||||
{
|
||||
"sourceId": 27,
|
||||
"url": "https://comrademao.com/",
|
||||
"sourceName": "ComradeMao",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/comrademao/icon.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 28,
|
||||
"url": "https://yuukitls.com/",
|
||||
"sourceName": "YuukiTls",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/es/yuukitls/icon.png?raw=true",
|
||||
"lang": "Spanish"
|
||||
},
|
||||
{
|
||||
"sourceId": 29,
|
||||
"url": "https://hasutl.wordpress.com/",
|
||||
"sourceName": "Hasu Translations",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/es/hasutl/icon.png?raw=true",
|
||||
"lang": "Spanish"
|
||||
},
|
||||
{
|
||||
"sourceId": 30,
|
||||
"url": "https://oasistranslations.wordpress.com/",
|
||||
"sourceName": "Oasis Translations",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/es/oasistranslations/icon.png?raw=true",
|
||||
"lang": "Spanish"
|
||||
},
|
||||
{
|
||||
"sourceId": 31,
|
||||
"url": "http://www.novelawuxia.com/",
|
||||
"sourceName": "Novela Wuxia",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/es/novelawuxia/icon.png?raw=true",
|
||||
"lang": "Spanish"
|
||||
},
|
||||
{
|
||||
"sourceId": 33,
|
||||
"url": "https://www.novelpassion.com/",
|
||||
"sourceName": "NovelPassion",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/novelpassion/icon.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 34,
|
||||
"url": "https://www.royalroad.com/",
|
||||
"sourceName": "Royal Road",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/royalroad/icon.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 35,
|
||||
"url": "https://www.scribblehub.com/",
|
||||
"sourceName": "Scribble Hub",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/scribblehub/icon.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 36,
|
||||
"url": "https://syosetu.com",
|
||||
"sourceName": "Syosetu",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/jp/syosetu/icon.png?raw=true",
|
||||
"lang": "Japanese"
|
||||
},
|
||||
{
|
||||
"sourceId": 37,
|
||||
"url": "https://lnmtl.com/",
|
||||
"sourceName": "LNMTL",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/lnmtl/icon.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 38,
|
||||
"sourceUrl": "https://skynovel.org/",
|
||||
"sourceName": "SkyNovel",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/multisrc/madara/icons/skynovel.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 39,
|
||||
"sourceUrl": "https://novelcake.com/",
|
||||
"sourceName": "NovelCake",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/multisrc/madara/icons/novelcake.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 40,
|
||||
"sourceUrl": "https://novelsrock.com/",
|
||||
"sourceName": "NovelsRock",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/multisrc/madara/icons/novelsrock.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 41,
|
||||
"sourceUrl": "https://zinnovel.com/",
|
||||
"sourceName": "ZinnNovel",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/multisrc/madara/icons/zinnovel.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 42,
|
||||
"sourceUrl": "https://noveltranslate.com/",
|
||||
"sourceName": "NovelTranslate",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/multisrc/madara/icons/noveltranslate.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 43,
|
||||
"sourceUrl": "https://www.lunarletters.com/",
|
||||
"sourceName": "LunarLetters",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/multisrc/madara/icons/lunarletters.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 44,
|
||||
"sourceUrl": "https://sleepytranslations.com/",
|
||||
"sourceName": "SleepyTranslations",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/multisrc/madara/icons/sleepytranslations.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 45,
|
||||
"sourceUrl": "https://freenovel.me/",
|
||||
"sourceName": "FreeNovelMe",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/multisrc/madara/icons/freenovel.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 46,
|
||||
"sourceUrl": "https://1stkissnovel.love/",
|
||||
"sourceName": "1stKissNovel",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/multisrc/madara/icons/1stkissnovel.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 47,
|
||||
"sourceUrl": "https://daonovel.com/",
|
||||
"sourceName": "DaoNovel",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/multisrc/madara/icons/daonovel.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 48,
|
||||
"sourceUrl": "https://www.wuxia.blog/",
|
||||
"sourceName": "Wuxia.Blog",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/wuxiablog/icon.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 49,
|
||||
"sourceUrl": "https://wuxia.city/",
|
||||
"sourceName": "Wuxia.City",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/wuxiacity/icon.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 50,
|
||||
"sourceUrl": "https://www.novelupdates.com/",
|
||||
"sourceName": "Novel Updates",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/novelupdates/icon.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 51,
|
||||
"sourceUrl": "https://ranobes.net/",
|
||||
"sourceName": "Ranobes",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/ranobes/icon.png?raw=true",
|
||||
"lang": "English"
|
||||
},
|
||||
{
|
||||
"sourceId": 52,
|
||||
"url": "https://m.yushubo.com",
|
||||
"sourceName": "Yushubo",
|
||||
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/ch/yushubo/icon.png?raw=true",
|
||||
"lang": "Chinese"
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user