Refactor sources and show headings

This commit is contained in:
Rajarshee Chatterjee
2021-07-14 12:27:07 +05:30
parent e7424cea06
commit ba90a6fb84
20 changed files with 712 additions and 1247 deletions
+70 -73
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+8 -2
View File
@@ -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);
}
-2
View File
@@ -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:
+28 -52
View File
@@ -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,
},
});
+5 -27
View File
@@ -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 };
+9 -61
View File
@@ -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>
)}
+85
View File
@@ -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;
+37 -18
View File
@@ -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
View File
@@ -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,
-8
View File
@@ -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(
+4 -3
View File
@@ -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
View File
@@ -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];
};
+359
View File
@@ -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"
}
]