Add Novel Updates
This commit is contained in:
@@ -200,6 +200,7 @@ const styles = StyleSheet.create({
|
||||
paddingHorizontal: 4.5,
|
||||
paddingVertical: 4,
|
||||
borderRadius: 4,
|
||||
flex: 1,
|
||||
},
|
||||
extensionIcon: {
|
||||
width: 42,
|
||||
|
||||
@@ -110,7 +110,7 @@ const Browse = ({ navigation }) => {
|
||||
extraData={pinned}
|
||||
ListHeaderComponent={
|
||||
<View>
|
||||
{(showNovelUpdates || showMyAnimeList) && (
|
||||
{showMyAnimeList && (
|
||||
<>
|
||||
<Text
|
||||
style={{
|
||||
@@ -121,7 +121,7 @@ const Browse = ({ navigation }) => {
|
||||
>
|
||||
Discover
|
||||
</Text>
|
||||
{showNovelUpdates && (
|
||||
{/* {showNovelUpdates && (
|
||||
<DiscoverCard
|
||||
label="Novel Updates"
|
||||
onPress={() =>
|
||||
@@ -132,7 +132,7 @@ const Browse = ({ navigation }) => {
|
||||
icon={require("../../../assets/novelupdates.png")}
|
||||
theme={theme}
|
||||
/>
|
||||
)}
|
||||
)} */}
|
||||
{showMyAnimeList && (
|
||||
<DiscoverCard
|
||||
label="MyAnimeList"
|
||||
|
||||
@@ -87,7 +87,7 @@ const BrowseSettings = ({ navigation }) => {
|
||||
/>
|
||||
</Pressable>
|
||||
<ListSubHeader theme={theme}>Discover</ListSubHeader>
|
||||
<Pressable
|
||||
{/* <Pressable
|
||||
android_ripple={{ color: theme.rippleColor }}
|
||||
style={{
|
||||
padding: 16,
|
||||
@@ -107,7 +107,7 @@ const BrowseSettings = ({ navigation }) => {
|
||||
dispatch(enableDiscover("showNovelUpdates"))
|
||||
}
|
||||
/>
|
||||
</Pressable>
|
||||
</Pressable> */}
|
||||
<Pressable
|
||||
android_ripple={{ color: theme.rippleColor }}
|
||||
style={{
|
||||
|
||||
+155
-119
@@ -8,6 +8,7 @@ import {
|
||||
ScrollView,
|
||||
} from "react-native";
|
||||
|
||||
import { WebView } from "react-native-webview";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { IconButton, Portal } from "react-native-paper";
|
||||
import {
|
||||
@@ -207,6 +208,11 @@ const Chapter = ({ route, navigation }) => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const setImmersiveMode = () => {
|
||||
StatusBar.setHidden(true);
|
||||
hideNavigationBar();
|
||||
};
|
||||
|
||||
const scrollToSavedProgress = useCallback((event) => {
|
||||
StatusBar.setHidden(true);
|
||||
hideNavigationBar();
|
||||
@@ -279,133 +285,163 @@ const Chapter = ({ route, navigation }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<ChapterAppbar
|
||||
novelName={novelName}
|
||||
chapterName={chapterName}
|
||||
chapterId={chapterId}
|
||||
bookmark={bookmark}
|
||||
readerSheetRef={readerSheetRef}
|
||||
hide={hidden}
|
||||
navigation={navigation}
|
||||
dispatch={dispatch}
|
||||
/>
|
||||
<ScrollView
|
||||
ref={(ref) => (scrollViewRef.current = ref)}
|
||||
contentContainerStyle={[
|
||||
styles.screenContainer,
|
||||
{ backgroundColor: readerBackground(reader.theme) },
|
||||
]}
|
||||
onScroll={onScroll}
|
||||
onContentSizeChange={(x, y) => setContentSize(y)}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<GestureRecognizer
|
||||
onSwipeRight={swipeGestures && navigateToPrevChapter}
|
||||
onSwipeLeft={swipeGestures && navigateToNextChapter}
|
||||
config={config}
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
{error ? (
|
||||
<View style={{ flex: 1, justifyContent: "center" }}>
|
||||
<EmptyView
|
||||
icon="(-_-;)・・・"
|
||||
description={error}
|
||||
style={{ color: errorTextColor(reader.theme) }}
|
||||
>
|
||||
<IconButton
|
||||
icon="reload"
|
||||
size={25}
|
||||
style={{ margin: 0, marginTop: 16 }}
|
||||
color={errorTextColor(reader.theme)}
|
||||
onPress={() => {
|
||||
getChapter(chapterId);
|
||||
setLoading(true);
|
||||
setError();
|
||||
}}
|
||||
/>
|
||||
<Text
|
||||
{sourceId === 50 ? (
|
||||
loading ? (
|
||||
<View style={{ flex: 1, justifyContent: "center" }}>
|
||||
<ActivityIndicator
|
||||
size={50}
|
||||
color={theme.colorAccent}
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
<WebView
|
||||
originWhitelist={["*"]}
|
||||
source={{
|
||||
html: chapter.chapterText,
|
||||
}}
|
||||
onLayout={setImmersiveMode}
|
||||
onScroll={onScroll}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<>
|
||||
<ChapterAppbar
|
||||
novelName={novelName}
|
||||
chapterName={chapterName}
|
||||
chapterId={chapterId}
|
||||
bookmark={bookmark}
|
||||
readerSheetRef={readerSheetRef}
|
||||
hide={hidden}
|
||||
navigation={navigation}
|
||||
dispatch={dispatch}
|
||||
/>
|
||||
<ScrollView
|
||||
ref={(ref) => (scrollViewRef.current = ref)}
|
||||
contentContainerStyle={[
|
||||
styles.screenContainer,
|
||||
{ backgroundColor: readerBackground(reader.theme) },
|
||||
]}
|
||||
onScroll={onScroll}
|
||||
onContentSizeChange={(x, y) => setContentSize(y)}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{error ? (
|
||||
<View style={{ flex: 1, justifyContent: "center" }}>
|
||||
<EmptyView
|
||||
icon="(-_-;)・・・"
|
||||
description={error}
|
||||
style={{
|
||||
color: errorTextColor(reader.theme),
|
||||
}}
|
||||
>
|
||||
Retry
|
||||
</Text>
|
||||
</EmptyView>
|
||||
</View>
|
||||
) : loading ? (
|
||||
<View style={{ flex: 1, justifyContent: "center" }}>
|
||||
<ActivityIndicator
|
||||
size={50}
|
||||
color={theme.colorAccent}
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
<View style={{ flex: 1 }}>
|
||||
{images.length > 0 &&
|
||||
images.map((image, index) => (
|
||||
<FitImage
|
||||
source={{ uri: image }}
|
||||
key={index}
|
||||
<IconButton
|
||||
icon="reload"
|
||||
size={25}
|
||||
style={{ margin: 0, marginTop: 16 }}
|
||||
color={errorTextColor(reader.theme)}
|
||||
onPress={() => {
|
||||
getChapter(chapterId);
|
||||
setLoading(true);
|
||||
setError();
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
<Text
|
||||
style={readerStyles}
|
||||
onLayout={scrollToSavedProgress}
|
||||
selectable={textSelectable}
|
||||
onPress={hideHeader}
|
||||
<Text
|
||||
style={{
|
||||
color: errorTextColor(reader.theme),
|
||||
}}
|
||||
>
|
||||
Retry
|
||||
</Text>
|
||||
</EmptyView>
|
||||
</View>
|
||||
) : loading ? (
|
||||
<View style={{ flex: 1, justifyContent: "center" }}>
|
||||
<ActivityIndicator
|
||||
size={50}
|
||||
color={theme.colorAccent}
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
<GestureRecognizer
|
||||
onSwipeRight={
|
||||
swipeGestures && navigateToPrevChapter
|
||||
}
|
||||
onSwipeLeft={
|
||||
swipeGestures && navigateToNextChapter
|
||||
}
|
||||
config={config}
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
{chapter.chapterText
|
||||
.trim()
|
||||
.replace(/\[https.*?\]/g, "")}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</GestureRecognizer>
|
||||
<Portal>
|
||||
<ReaderSheet
|
||||
<View style={{ flex: 1 }}>
|
||||
{images.length > 0 &&
|
||||
images.map((image, index) => (
|
||||
<FitImage
|
||||
source={{ uri: image }}
|
||||
key={index}
|
||||
/>
|
||||
))}
|
||||
|
||||
<Text
|
||||
style={readerStyles}
|
||||
onLayout={scrollToSavedProgress}
|
||||
selectable={textSelectable}
|
||||
onPress={hideHeader}
|
||||
>
|
||||
{chapter.chapterText
|
||||
.trim()
|
||||
.replace(/\[https.*?\]/g, "")}
|
||||
</Text>
|
||||
</View>
|
||||
</GestureRecognizer>
|
||||
)}
|
||||
<Portal>
|
||||
<ReaderSheet
|
||||
theme={theme}
|
||||
reader={reader}
|
||||
dispatch={dispatch}
|
||||
bottomSheetRef={readerSheetRef}
|
||||
selectText={textSelectable}
|
||||
showScrollPercentage={showScrollPercentage}
|
||||
/>
|
||||
</Portal>
|
||||
</ScrollView>
|
||||
<VerticalScrollbar
|
||||
theme={theme}
|
||||
reader={reader}
|
||||
dispatch={dispatch}
|
||||
bottomSheetRef={readerSheetRef}
|
||||
selectText={textSelectable}
|
||||
showScrollPercentage={showScrollPercentage}
|
||||
hide={hidden}
|
||||
setLoading={setLoading}
|
||||
contentSize={contentSize}
|
||||
scrollViewRef={scrollViewRef}
|
||||
scrollPercentage={scrollPercentage}
|
||||
setScrollPercentage={setScrollPercentage}
|
||||
/>
|
||||
</Portal>
|
||||
</ScrollView>
|
||||
<VerticalScrollbar
|
||||
theme={theme}
|
||||
hide={hidden}
|
||||
setLoading={setLoading}
|
||||
contentSize={contentSize}
|
||||
scrollViewRef={scrollViewRef}
|
||||
scrollPercentage={scrollPercentage}
|
||||
setScrollPercentage={setScrollPercentage}
|
||||
/>
|
||||
<ChapterFooter
|
||||
theme={theme}
|
||||
swipeGestures={swipeGestures}
|
||||
dispatch={dispatch}
|
||||
nextChapter={nextChapter}
|
||||
prevChapter={prevChapter}
|
||||
hide={hidden}
|
||||
navigateToNextChapter={navigateToNextChapter}
|
||||
navigateToPrevChapter={navigateToPrevChapter}
|
||||
readerSheetRef={readerSheetRef}
|
||||
scrollViewRef={scrollViewRef}
|
||||
/>
|
||||
{showScrollPercentage && (
|
||||
<Text
|
||||
style={[
|
||||
styles.scrollPercentage,
|
||||
{
|
||||
color: reader.textColor,
|
||||
backgroundColor: readerBackground(reader.theme),
|
||||
},
|
||||
]}
|
||||
>
|
||||
{scrollPercentage + "%"}
|
||||
</Text>
|
||||
<ChapterFooter
|
||||
theme={theme}
|
||||
swipeGestures={swipeGestures}
|
||||
dispatch={dispatch}
|
||||
nextChapter={nextChapter}
|
||||
prevChapter={prevChapter}
|
||||
hide={hidden}
|
||||
navigateToNextChapter={navigateToNextChapter}
|
||||
navigateToPrevChapter={navigateToPrevChapter}
|
||||
readerSheetRef={readerSheetRef}
|
||||
scrollViewRef={scrollViewRef}
|
||||
/>
|
||||
{showScrollPercentage && (
|
||||
<Text
|
||||
style={[
|
||||
styles.scrollPercentage,
|
||||
{
|
||||
color: reader.textColor,
|
||||
backgroundColor: readerBackground(
|
||||
reader.theme
|
||||
),
|
||||
},
|
||||
]}
|
||||
>
|
||||
{scrollPercentage + "%"}
|
||||
</Text>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import React, { memo } from "react";
|
||||
import { View, Text, StyleSheet, Clipboard, Pressable } from "react-native";
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
StyleSheet,
|
||||
Clipboard,
|
||||
Pressable,
|
||||
Button,
|
||||
} from "react-native";
|
||||
|
||||
import * as WebBrowser from "expo-web-browser";
|
||||
import { TouchableRipple, IconButton } from "react-native-paper";
|
||||
@@ -186,7 +193,7 @@ const NovelInfoHeader = ({
|
||||
followed={novel.followed}
|
||||
theme={theme}
|
||||
/>
|
||||
{novel.genre && novel.genre !== "" && (
|
||||
{novel.genre.length > 0 && novel.genre !== "" && (
|
||||
<NovelGenres theme={theme} genre={novel.genre} />
|
||||
)}
|
||||
<ReadButton
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
import cheerio from "react-native-cheerio";
|
||||
import { htmlToText } from "../helpers/htmlToText";
|
||||
import moment from "moment";
|
||||
|
||||
const sourceId = 50;
|
||||
|
||||
const sourceName = "Novel Updates";
|
||||
|
||||
const baseUrl = "https://www.novelupdates.com/";
|
||||
|
||||
let headers = new Headers({
|
||||
"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 popularNovels = async (page) => {
|
||||
const totalPages = 100;
|
||||
let url = `${baseUrl}series-ranking/?rank=sixmonths&pg=` + page;
|
||||
|
||||
const result = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: headers,
|
||||
});
|
||||
const body = await result.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 novelUrl = $(this)
|
||||
.find(".search_title > a")
|
||||
.attr("href")
|
||||
.split("/")[4];
|
||||
|
||||
const novel = {
|
||||
sourceId,
|
||||
novelName,
|
||||
novelCover,
|
||||
novelUrl,
|
||||
};
|
||||
|
||||
novels.push(novel);
|
||||
});
|
||||
|
||||
return { totalPages, novels };
|
||||
};
|
||||
|
||||
const parseNovelAndChapters = async (novelUrl) => {
|
||||
const url = `${baseUrl}series/${novelUrl}`;
|
||||
|
||||
const result = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: headers,
|
||||
});
|
||||
const body = await result.text();
|
||||
|
||||
$ = cheerio.load(body);
|
||||
|
||||
let novel = {
|
||||
sourceId,
|
||||
sourceName,
|
||||
url,
|
||||
novelUrl,
|
||||
};
|
||||
|
||||
novel.novelName = $(".seriestitlenu").text();
|
||||
|
||||
novel.novelCover = $(".seriesimg > img").attr("src");
|
||||
|
||||
novel.author = $("#showauthors").text().trim();
|
||||
|
||||
novel.genre = $(".book-generes").text().trim();
|
||||
|
||||
novel.status = $("#editstatus").text().includes("Ongoing")
|
||||
? "Ongoing"
|
||||
: "Completed";
|
||||
|
||||
novel.summary = $("#editdescription").text().trim();
|
||||
|
||||
let novelChapters = [];
|
||||
|
||||
const novelId = $("input#mypostid").attr("value");
|
||||
|
||||
let formData = new FormData();
|
||||
formData.append("action", "nd_getchapters");
|
||||
formData.append("mygrr", 0);
|
||||
formData.append("mypostid", parseInt(novelId));
|
||||
|
||||
const data = await fetch(
|
||||
"https://www.novelupdates.com/wp-admin/admin-ajax.php",
|
||||
{
|
||||
method: "POST",
|
||||
headers,
|
||||
body: formData,
|
||||
}
|
||||
);
|
||||
const text = await data.text();
|
||||
|
||||
$ = cheerio.load(text);
|
||||
|
||||
$("li.sp_li_chp").each(function () {
|
||||
const chapterName = $(this).text().trim();
|
||||
|
||||
const releaseDate = null;
|
||||
|
||||
const chapterUrl =
|
||||
"https:" + $(this).find("a").first().next().attr("href");
|
||||
|
||||
novelChapters.push({ chapterName, releaseDate, chapterUrl });
|
||||
});
|
||||
|
||||
novel.chapters = novelChapters.reverse();
|
||||
|
||||
return novel;
|
||||
};
|
||||
|
||||
const parseChapter = async (novelUrl, chapterUrl) => {
|
||||
const url = chapterUrl;
|
||||
|
||||
const result = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: headers,
|
||||
});
|
||||
const body = await result.text();
|
||||
|
||||
$ = cheerio.load(body);
|
||||
|
||||
let chapterName = "";
|
||||
|
||||
let chapterText = body;
|
||||
|
||||
let nextChapter = null;
|
||||
let prevChapter = null;
|
||||
|
||||
const chapter = {
|
||||
sourceId,
|
||||
novelUrl,
|
||||
chapterUrl,
|
||||
chapterName,
|
||||
chapterText,
|
||||
nextChapter,
|
||||
prevChapter,
|
||||
};
|
||||
|
||||
return chapter;
|
||||
};
|
||||
|
||||
const searchNovels = async (searchTerm) => {
|
||||
const url =
|
||||
"https://www.novelupdates.com/?s=" +
|
||||
searchTerm +
|
||||
"&post_type=seriesplans";
|
||||
|
||||
const res = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: headers,
|
||||
});
|
||||
const body = await res.text();
|
||||
|
||||
$ = cheerio.load(body);
|
||||
|
||||
let 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 novelUrl = $(this)
|
||||
.find(".search_title > a")
|
||||
.attr("href")
|
||||
.split("/")[4];
|
||||
|
||||
const novel = {
|
||||
sourceId,
|
||||
novelName,
|
||||
novelCover,
|
||||
novelUrl,
|
||||
};
|
||||
|
||||
novels.push(novel);
|
||||
});
|
||||
return novels;
|
||||
};
|
||||
|
||||
const NovelUpdatesScraper = {
|
||||
popularNovels,
|
||||
parseNovelAndChapters,
|
||||
parseChapter,
|
||||
searchNovels,
|
||||
};
|
||||
|
||||
export default NovelUpdatesScraper;
|
||||
@@ -97,6 +97,35 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
novelChapters.push(chapter);
|
||||
});
|
||||
|
||||
let moreChaptersUrl =
|
||||
baseUrl +
|
||||
"temphtml/_tempChapterList_all_" +
|
||||
$("div#more").attr("data-nid") +
|
||||
".html";
|
||||
|
||||
let moreChapters = await fetch(moreChaptersUrl);
|
||||
let moreChaptersString = await moreChapters.text();
|
||||
|
||||
$ = cheerio.load(moreChaptersString);
|
||||
|
||||
$("a").each(function () {
|
||||
const chapterName = $(this).text();
|
||||
|
||||
const releaseDate = $(this)[0]
|
||||
.nextSibling.nodeValue.replace(/-/g, "/")
|
||||
.trim();
|
||||
|
||||
const chapterUrl = $(this).attr("href").replace(url, "");
|
||||
|
||||
const chapter = {
|
||||
chapterName,
|
||||
releaseDate,
|
||||
chapterUrl,
|
||||
};
|
||||
|
||||
novelChapters.push(chapter);
|
||||
});
|
||||
|
||||
novel.chapters = novelChapters.reverse();
|
||||
|
||||
return novel;
|
||||
|
||||
+11
-1
@@ -48,12 +48,19 @@ import {
|
||||
} from "./multisrc/madara/MadaraGenerator";
|
||||
import WuxiaBlogScraper from "./en/wuxiablog";
|
||||
import WuxiaCityScraper from "./en/wuxiacity";
|
||||
import NovelUpdatesScraper from "./en/novelupdates";
|
||||
|
||||
export const sources = [
|
||||
/**
|
||||
* English
|
||||
*/
|
||||
|
||||
{
|
||||
sourceId: 50,
|
||||
sourceUrl: "https://www.novelupdates.com/",
|
||||
sourceName: "Novel Updates",
|
||||
icon: "https://github.com/rajarsheechatterjee/lnreader-extensions/blob/main/src/en/novelupdates/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 1,
|
||||
url: "https://boxnovel.com/",
|
||||
@@ -548,5 +555,8 @@ export const getSource = (sourceId) => {
|
||||
case 49: {
|
||||
return WuxiaCityScraper;
|
||||
}
|
||||
case 50: {
|
||||
return NovelUpdatesScraper;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user