diff --git a/src/Components/NovelList.js b/src/Components/NovelList.js index 0ee6c8a5..b7dd937a 100644 --- a/src/Components/NovelList.js +++ b/src/Components/NovelList.js @@ -6,9 +6,11 @@ import { getDeviceOrientation } from "../Services/utils/helpers"; const NovelList = ({ data, + onScroll, renderItem, refreshControl, ListEmptyComponent, + ListFooterComponent, }) => { const { displayMode, novelsPerRow } = useSettings(); @@ -36,6 +38,8 @@ const NovelList = ({ renderItem={renderItem} refreshControl={refreshControl} ListEmptyComponent={ListEmptyComponent} + ListFooterComponent={ListFooterComponent} + onScroll={onScroll} /> ); }; diff --git a/src/Screens/Browse/components/SourceCard.js b/src/Screens/Browse/components/SourceCard.js index 90c62a35..36b391e4 100644 --- a/src/Screens/Browse/components/SourceCard.js +++ b/src/Screens/Browse/components/SourceCard.js @@ -7,7 +7,7 @@ import { pinSourceAction } from "../../../redux/source/source.actions"; import FastImage from "react-native-fast-image"; const ExtensionCard = ({ item, theme, isPinned }) => { - const { sourceId, sourceName, sourceCover, sourceLanguage, status } = item; + const { sourceId, sourceName, icon, lang } = item; const navigation = useNavigation(); const dispatch = useDispatch(); @@ -27,7 +27,7 @@ const ExtensionCard = ({ item, theme, isPinned }) => { > <> @@ -41,19 +41,14 @@ const ExtensionCard = ({ item, theme, isPinned }) => { > {sourceName} - - - {sourceLanguage} - - - {status === 0 && "• Down"} - - + + {lang} + { const { sourceId, sourceName, sourceUrl } = route.params; @@ -22,35 +24,38 @@ const Extension = ({ navigation, route }) => { const { displayMode } = useSettings(); const [loading, setLoading] = useState(true); - const [novels, setNovels] = useState(); + const [novels, setNovels] = useState([]); + const [page, setPage] = useState(1); const [error, setError] = useState(); const [searchText, setSearchText] = useState(""); - const getSourceUrl = () => { - if (sourceId === 1) { - return `https://lnreader-extensions.vercel.app/api/1/novels/1/?o=rating`; - } else { - return `https://lnreader-extensions.vercel.app/api/${sourceId}/novels/`; - } - }; + // const getSourceUrl = () => { + // if (sourceId === 1) { + // return `https://lnreader-extensions.vercel.app/api/1/novels/1/?o=rating`; + // } else { + // return `https://lnreader-extensions.vercel.app/api/${sourceId}/novels/`; + // } + // }; - const getSearchUrl = () => { - if (sourceId === 1) { - return `https://lnreader-extensions.vercel.app/api/1/search/?s=${searchText}&?o=rating`; - } else { - return `https://lnreader-extensions.vercel.app/api/${sourceId}/search/?s=${searchText}`; - } - }; + // const getSearchUrl = () => { + // if (sourceId === 1) { + // return `https://lnreader-extensions.vercel.app/api/1/search/?s=${searchText}&?o=rating`; + // } else { + // return `https://lnreader-extensions.vercel.app/api/${sourceId}/search/?s=${searchText}`; + // } + // }; - const getNovels = async () => { + const getNovels = async (page) => { try { - const url = getSourceUrl(); + const source = getSource(sourceId); - const res = await fetch(url); - const data = await res.json(); + // console.log(page); + const res = await source.popularNovels(page); - setNovels(data); + // console.log(res); + + setNovels((novels) => novels.concat(res)); setLoading(false); } catch (error) { setError(error.message); @@ -88,9 +93,28 @@ const Extension = ({ navigation, route }) => { }; useEffect(() => { - getNovels(); + getNovels(1); }, []); + const isCloseToBottom = ({ + layoutMeasurement, + contentOffset, + contentSize, + }) => { + const paddingToBottom = 20; + return ( + layoutMeasurement.height + contentOffset.y >= + contentSize.height - paddingToBottom + ); + }; + + const onScroll = ({ nativeEvent }) => { + if (!searchText && isCloseToBottom(nativeEvent)) { + getNovels(page + 1); + setPage((page) => page + 1); + } + }; + const renderItem = ({ item }) => ( { novelName: item.novelName, novelCover: item.novelCover, novelUrl: item.novelUrl, - sourceId: item.extensionId, + sourceId, }) } libraryStatus={checkIFInLibrary(item.extensionId, item.novelUrl)} @@ -174,6 +198,14 @@ const Extension = ({ navigation, route }) => { data={novels} renderItem={renderItem} ListEmptyComponent={listEmptyComponent()} + onScroll={onScroll} + ListFooterComponent={ + !searchText && ( + + + + ) + } /> )} diff --git a/src/Services/Source/source.js b/src/Services/Source/source.js index 3d17effa..71b6c843 100644 --- a/src/Services/Source/source.js +++ b/src/Services/Source/source.js @@ -1,4 +1,5 @@ import { showToast } from "../../Hooks/showToast"; +import { getSource } from "../../sources/sources"; export const fetchSources = async () => { const url = `https://lnreader-extensions.vercel.app/api/`; @@ -10,35 +11,35 @@ export const fetchSources = async () => { }; export const fetchNovel = async (sourceId, novelUrl) => { - const url = `https://lnreader-extensions.vercel.app/api/${sourceId}/novel/${novelUrl}`; + const source = getSource(sourceId); - let data = await fetch(url); - let res = await data.json(); + const res = await source.parseNovelAndChapters(novelUrl); const novel = { novelUrl: res.novelUrl, - sourceUrl: res.sourceUrl, + sourceUrl: res.url, source: res.sourceName, - sourceId: res.extensionId, + sourceId: res.sourceId, novelName: res.novelName, novelCover: res.novelCover, - novelSummary: res.novelSummary, - author: res["Author(s)"], - artist: res["Artist(s)"], - status: res.Status, - genre: res["Genre(s)"], + novelSummary: res.summary, + author: res.author, + artist: res.artist, + status: res.status, + genre: res.genre, followed: 0, - chapters: res.novelChapters, + chapters: res.chapters, }; + // console.log(novel); + return novel; }; export const fetchChapter = async (sourceId, novelUrl, chapterUrl) => { - const url = `https://lnreader-extensions.vercel.app/api/${sourceId}/novel/${novelUrl}${chapterUrl}`; + const source = getSource(sourceId); - let res = await fetch(url); - let chapter = await res.json(); + let chapter = await source.parseChapter(novelUrl, chapterUrl); return chapter; }; diff --git a/src/redux/novel/novel.actions.js b/src/redux/novel/novel.actions.js index 429a57c5..b503067b 100644 --- a/src/redux/novel/novel.actions.js +++ b/src/redux/novel/novel.actions.js @@ -57,6 +57,7 @@ export const setNovel = (novel) => async (dispatch) => { export const getNovelAction = (followed, sourceId, novelUrl, novelId, sort, filter) => async (dispatch) => { + console.log(followed, sourceId, novelUrl, novelId, sort, filter); try { dispatch({ type: LOADING_NOVEL }); diff --git a/src/redux/source/source.actions.js b/src/redux/source/source.actions.js index 0f5961fa..8014503d 100644 --- a/src/redux/source/source.actions.js +++ b/src/redux/source/source.actions.js @@ -1,15 +1,11 @@ import { GET_SOURCES, PIN_SOURCES, SEARCH_SOURCES } from "./source.types"; import { fetchSources } from "../../Services/Source/source"; import { showToast } from "../../Hooks/showToast"; +import { sources } from "../../sources/sources"; export const getSourcesAction = () => async (dispatch) => { try { - const res = await fetchSources(); - - dispatch({ - type: GET_SOURCES, - payload: res, - }); + dispatch({ type: GET_SOURCES, payload: sources }); } catch (error) { showToast(error.message); } diff --git a/src/sources/en/readlightnovel.js b/src/sources/en/readlightnovel.js index a04c1553..6778591e 100644 --- a/src/sources/en/readlightnovel.js +++ b/src/sources/en/readlightnovel.js @@ -54,7 +54,7 @@ const parseNovelAndChapters = async (novelUrl) => { const novelCover = $(".novel-cover > a > img").attr("src"); - let author, artist, genre, summary; + let author, artist, genre, summary, status; $(".novel-detail-item").each(function (result) { const detailName = $(this).find(".novel-detail-header > h6").text(); @@ -62,7 +62,7 @@ const parseNovelAndChapters = async (novelUrl) => { switch (detailName) { case "Genre": - genre = detail.replace(/[\t\n]/g, ","); + genre = detail.trim().replace(/[\t\n]/g, ","); break; case "Author(s)": author = detail.trim(); @@ -73,6 +73,9 @@ const parseNovelAndChapters = async (novelUrl) => { case "Description": summary = detail; break; + case "Status": + status = detail; + break; } }); @@ -111,10 +114,12 @@ const parseNovelAndChapters = async (novelUrl) => { sourceId, sourceName, url, + novelUrl, novelName, novelCover, genre, author, + status, artist, summary, chapters, @@ -123,4 +128,67 @@ const parseNovelAndChapters = async (novelUrl) => { return novel; }; -export { popularNovels, parseNovelAndChapters }; +const parseChapter = async (novelUrl, chapterUrl) => { + const url = `${baseUrl}/${novelUrl}/${chapterUrl}/`; + + const result = await fetch(url); + const body = await result.text(); + + $ = cheerio.load(body); + + $(".block-title > h1").find("a").remove(); + + const chapterName = $(".block-title > h1").text().replace(" - ", ""); + + $(".desc") + .find("hr") + .each(function (result) { + $(this).remove(); + }); + + $(".alert").remove(); + $(".hidden").remove(); + + let chapterText = $(".desc").html(); + + // chapterText = parseHtml(chapterText).replace( + // /\n\nSponsored Content\n\n|If audio player doesn't work, press Stop then Play button again/g, + // "" + // ); + + let nextChapter = null; + + if ($("a.next.next-link").length) { + nextChapter = $("a.next.next-link") + .attr("href") + .replace(`${baseUrl}/${novelUrl}/`, ""); + } + + let prevChapter = null; + + if ($("a.prev.prev-link").length) { + prevChapter = $("a.prev.prev-link") + .attr("href") + .replace(`${baseUrl}/${novelUrl}/`, ""); + } + + const chapter = { + extensionId: 2, + novelUrl, + chapterUrl, + chapterName, + chapterText, + nextChapter, + prevChapter, + }; + + return chapter; +}; + +const ReadLightNovelScraper = { + popularNovels, + parseNovelAndChapters, + parseChapter, +}; + +export default ReadLightNovelScraper; diff --git a/src/sources/sources.js b/src/sources/sources.js index e69de29b..1a888cd1 100644 --- a/src/sources/sources.js +++ b/src/sources/sources.js @@ -0,0 +1,19 @@ +import ReadLightNovelScraper from "./en/readlightnovel"; + +export const sources = [ + { + sourceId: 2, + sourceName: "ReadLightNovel", + url: "https://www.readlightnovel.org", + lang: "English", + icon: "https://github.com/rajarsheechatterjee/lnreader-extensions/blob/main/src/en/readlightnovel/icon.png?raw=true", + }, +]; + +export const getSource = (sourceId) => { + switch (sourceId) { + case 2: { + return ReadLightNovelScraper; + } + } +};