From ba90a6fb84ee8db5cce98c167f2b313be060eff2 Mon Sep 17 00:00:00 2001 From: Rajarshee Chatterjee Date: Wed, 14 Jul 2021 12:27:07 +0530 Subject: [PATCH] Refactor sources and show headings --- src/navigators/BottomNavigator.js | 143 +++-- src/navigators/Main.js | 31 +- src/navigators/More.js | 64 +- src/redux/source/source.actions.js | 10 +- src/redux/source/source.reducers.js | 2 - src/screens/Browse/Browse.js | 80 +-- src/screens/Browse/BrowseSettings.js | 32 +- .../Browse/discover/BrowseNovelUpdates.js | 127 ---- .../discover/NovelUpdatesBottomsheet.js | 5 - .../Browse/discover/NovelUpdatesNovelCard.js | 122 ---- .../Browse/discover/NovelUpdatesScraper.js | 130 ---- src/screens/Chapter/Chapter.js | 70 +-- src/screens/Chapter/TextToComponent.js | 85 +++ src/screens/Updates/Updates.js | 55 +- src/screens/Updates/components/UpdatesItem.js | 12 +- src/services/updates.js | 30 +- src/sources/en/readlightnovel.js | 8 - src/sources/helpers/htmlToText.js | 7 +- src/sources/sources.js | 587 ++---------------- src/sources/sources.json | 359 +++++++++++ 20 files changed, 712 insertions(+), 1247 deletions(-) delete mode 100644 src/screens/Browse/discover/BrowseNovelUpdates.js delete mode 100644 src/screens/Browse/discover/NovelUpdatesBottomsheet.js delete mode 100644 src/screens/Browse/discover/NovelUpdatesNovelCard.js delete mode 100644 src/screens/Browse/discover/NovelUpdatesScraper.js create mode 100644 src/screens/Chapter/TextToComponent.js create mode 100644 src/sources/sources.json diff --git a/src/navigators/BottomNavigator.js b/src/navigators/BottomNavigator.js index a70ab9d9..3fbad11a 100644 --- a/src/navigators/BottomNavigator.js +++ b/src/navigators/BottomNavigator.js @@ -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 ( - - - ( - - ), - }} - /> - ( - - ), - }} - /> - ( - - ), - }} - /> - ( - - ), - }} - /> - ( - - ), - }} - /> - - + + ( + + ), + }} + /> + ( + + ), + }} + /> + ( + + ), + }} + /> + ( + + ), + }} + /> + ( + + ), + }} + /> + ); }; diff --git a/src/navigators/Main.js b/src/navigators/Main.js index bc97bd75..010ca0fa 100644 --- a/src/navigators/Main.js +++ b/src/navigators/Main.js @@ -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 = () => { > {isNewVersion && } - + { name="BrowseSettings" component={BrowseSettings} /> - { - const theme = useTheme(); +const SettingsStack = () => ( + + + + + + + + +); - return ( - - - - - - - - - - - ); -}; - -const MoreStack = () => { - const theme = useTheme(); - - return ( - - - - - - - - ); -}; +const MoreStack = () => ( + + + + + +); export default MoreStack; diff --git a/src/redux/source/source.actions.js b/src/redux/source/source.actions.js index c4ec14b3..4cb3398d 100644 --- a/src/redux/source/source.actions.js +++ b/src/redux/source/source.actions.js @@ -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); } diff --git a/src/redux/source/source.reducers.js b/src/redux/source/source.reducers.js index bf083511..ffbfd777 100644 --- a/src/redux/source/source.reducers.js +++ b/src/redux/source/source.reducers.js @@ -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: diff --git a/src/screens/Browse/Browse.js b/src/screens/Browse/Browse.js index e86a8ceb..1ed5b390 100644 --- a/src/screens/Browse/Browse.js +++ b/src/screens/Browse/Browse.js @@ -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 }) => ( + + {title} + + ); + return ( { data={!searchText ? sources : search} keyExtractor={(item) => item.sourceId.toString()} renderItem={renderItem} - extraData={pinned} + extraData={pinnedSources} ListHeaderComponent={ {showMyAnimeList && ( <> - - Discover - - {/* {showNovelUpdates && ( - - navigation.navigate( - "BrowseNovelUpdates" - ) - } - icon={require("../../../assets/novelupdates.png")} - theme={theme} - /> - )} */} + {showMyAnimeList && ( { )} )} - {pinned.length > 0 && ( + {pinnedSources.length > 0 && ( - isPinned(source.sourceId) - )} + data={pinnedSources} keyExtractor={(item) => item.sourceId.toString() } renderItem={renderItem} - extraData={pinned} + extraData={pinnedSources} ListHeaderComponent={ - - Pinned - + } /> )} - {sources.length > 0 && ( - - Sources - - )} + {sources.length > 0 && } } 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, + }, }); diff --git a/src/screens/Browse/BrowseSettings.js b/src/screens/Browse/BrowseSettings.js index 191aea10..99e0cd83 100644 --- a/src/screens/Browse/BrowseSettings.js +++ b/src/screens/Browse/BrowseSettings.js @@ -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 }) => { /> Discover - {/* dispatch(enableDiscover("showNovelUpdates"))} - > - - Novel Updates - - - dispatch(enableDiscover("showNovelUpdates")) - } - /> - */} + { - 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 ( - - setSearchText(text)} - onSubmitEditing={getSearchResults} - clearSearchbar={clearSearchbar} - actions={[ - { - icon: "earth", - onPress: () => - WebBrowser.openBrowserAsync( - "https://www.novelupdates.com/" - ), - }, - ]} - /> - {loading ? ( - - - - ) : ( - item.novelName.toString()} - renderItem={({ item }) => ( - - navigation.navigate("GlobalSearch", { - novelName: item.novelName, - }) - } - /> - )} - onScroll={({ nativeEvent }) => { - if (!searchText && isCloseToBottom(nativeEvent)) { - getNovels(pageNo + 1); - setPageNo((pageNo) => pageNo + 1); - } - }} - ListFooterComponent={ - !searchText && ( - - - - ) - } - /> - )} - - ); -}; - -export default BrowseNovelUpdates; - -const styles = StyleSheet.create({}); diff --git a/src/screens/Browse/discover/NovelUpdatesBottomsheet.js b/src/screens/Browse/discover/NovelUpdatesBottomsheet.js deleted file mode 100644 index 9b0870f5..00000000 --- a/src/screens/Browse/discover/NovelUpdatesBottomsheet.js +++ /dev/null @@ -1,5 +0,0 @@ -import React from "react"; - -const NovelUpdatesBottomSheet = () => {}; - -export default NovelUpdatesBottomSheet; diff --git a/src/screens/Browse/discover/NovelUpdatesNovelCard.js b/src/screens/Browse/discover/NovelUpdatesNovelCard.js deleted file mode 100644 index 61d42398..00000000 --- a/src/screens/Browse/discover/NovelUpdatesNovelCard.js +++ /dev/null @@ -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 }) => ( - - - - {`${"Genre "}`} - - {children} - - -); - -const NovelUpdatesNovelCard = ({ novel, theme, onPress }) => { - return ( - - - - - - {novel.novelName} - - - {novel.genres} - - - - {`${"Chapter Count "}`} - - {novel.chapterCount} - - - - - {novel.novelSummary} - - - - - ); -}; - -export default NovelUpdatesNovelCard; - -const styles = StyleSheet.create({ - novelUpdatesCard: { - flex: 1, - flexDirection: "row", - }, - contentContainer: { - flex: 1, - }, - genreChip: { - marginTop: 4, - fontSize: 13, - // fontStyle: "italic", - }, -}); diff --git a/src/screens/Browse/discover/NovelUpdatesScraper.js b/src/screens/Browse/discover/NovelUpdatesScraper.js deleted file mode 100644 index 9afe6708..00000000 --- a/src/screens/Browse/discover/NovelUpdatesScraper.js +++ /dev/null @@ -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 }; diff --git a/src/screens/Chapter/Chapter.js b/src/screens/Chapter/Chapter.js index 13293f79..8acdb467 100644 --- a/src/screens/Chapter/Chapter.js +++ b/src/screens/Chapter/Chapter.js @@ -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 ( - - ); - } else if (isItalic) { - return ( - - {isItalic[1]} - - ); - } else { - return ( - - {part} - - ); - } - } - })} + )} diff --git a/src/screens/Chapter/TextToComponent.js b/src/screens/Chapter/TextToComponent.js new file mode 100644 index 00000000..bed266c5 --- /dev/null +++ b/src/screens/Chapter/TextToComponent.js @@ -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 ; + } else if (isItalic) { + return ( + + {isItalic[1]} + + ); + } else if (isHeading) { + return ( + + {isHeading[2]} + + ); + } else { + return ( + + {part} + + ); + } + } + }); +}; + +export default TextToComponents; diff --git a/src/screens/Updates/Updates.js b/src/screens/Updates/Updates.js index 6b3c3f4e..600c0dbe 100644 --- a/src/screens/Updates/Updates.js +++ b/src/screens/Updates/Updates.js @@ -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 }) => ( - onPress(item)} /> + onPress(item)} + onPressCover={() => onPressCover(item)} + /> ); const ListFooterComponent = () => @@ -103,6 +120,8 @@ const Updates = ({ navigation }) => { setSearchResults(results); }; + const keyExtractor = useCallback((item) => item.updateId.toString(), []); + return ( { } data={item.chapters} - keyExtractor={(item) => item.updateId.toString()} + keyExtractor={keyExtractor} renderItem={renderItem} /> )} - ListFooterComponent={ListFooterComponent()} - ListEmptyComponent={ListEmptyComponent()} + ListFooterComponent={} + ListEmptyComponent={} refreshControl={refreshControl()} /> diff --git a/src/screens/Updates/components/UpdatesItem.js b/src/screens/Updates/components/UpdatesItem.js index 44d3458b..06c79fc8 100644 --- a/src/screens/Updates/components/UpdatesItem.js +++ b/src/screens/Updates/components/UpdatesItem.js @@ -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 ( { android_ripple={{ color: theme.rippleColor }} > <> - + + + { "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, diff --git a/src/sources/en/readlightnovel.js b/src/sources/en/readlightnovel.js index 0d06698b..286ad1ff 100644 --- a/src/sources/en/readlightnovel.js +++ b/src/sources/en/readlightnovel.js @@ -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( diff --git a/src/sources/helpers/htmlToText.js b/src/sources/helpers/htmlToText.js index a5241d8d..f6d9606b 100644 --- a/src/sources/helpers/htmlToText.js +++ b/src/sources/helpers/htmlToText.js @@ -23,13 +23,14 @@ const htmlToText = (html, options = {}) => { // /<\s*h(\d)[^>]*>(.*?)<\/h(\d)>/gim, // `${"#".repeat("$1")}$2${"#".repeat("$1")}` // ) - // .replace(/(.*?)<\/strong>/gi, "*$1*") + .replace(/<(h[1-6])>(.*?)<\/h[1-6]>/gi, "[$1]($2)") + // .replace(/(.*?)<\/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 diff --git a/src/sources/sources.js b/src/sources/sources.js index e3f2fb6b..74b9b5ce 100644 --- a/src/sources/sources.js +++ b/src/sources/sources.js @@ -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]; }; diff --git a/src/sources/sources.json b/src/sources/sources.json new file mode 100644 index 00000000..d695b4be --- /dev/null +++ b/src/sources/sources.json @@ -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" + } +]