Compare commits

..

12 Commits

Author SHA1 Message Date
Rajarshee Chatterjee 625acdb585 Release v1.1.4 2021-08-15 13:37:13 +05:30
Rajarshee Chatterjee 93617e60bd feat(source): Add Light Novel Updates 2021-08-14 08:56:59 +05:30
Rajarshee Chatterjee d75c607f67 feat(source): Add NovelOnlineFull.com 2021-08-14 08:04:31 +05:30
Marcin Majewski dd087ff485 fix(chapterlist): Bookmark not updating when added (#64)
When a chapter was bookmarked the list wasn't updating
2021-08-14 07:34:04 +05:30
Rajarshee Chatterjee 5da3e05bba feat: Show last update time 2021-08-13 10:12:26 +05:30
Rajarshee Chatterjee e628c3cfd6 fix: Remove text shadow in comfortable grid 2021-08-13 09:32:13 +05:30
Rajarshee Chatterjee deba021384 Readwn.com: Add user agents 2021-08-12 18:23:43 +05:30
Rajarshee Chatterjee c6d2ba8442 feat: Use tabs for settings and preview in reader settings 2021-08-12 18:21:56 +05:30
Rajarshee Chatterjee 6a3736dced fix: Crash when long pressing a cover in browse 2021-08-12 16:21:22 +05:30
Rajarshee Chatterjee f63ae573ba fix: Mark chapter as unread 2021-08-12 16:19:41 +05:30
Rajarshee Chatterjee 8f3020a544 Release v1.1.3 2021-08-11 16:25:05 +05:30
Rajarshee Chatterjee 04e32a4234 Minor fixes 2021-08-11 16:24:47 +05:30
28 changed files with 1015 additions and 627 deletions
+1 -1
View File
@@ -136,7 +136,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.1.2"
versionName "1.1.4"
}
splits {
abi {
-17
View File
@@ -36,7 +36,6 @@
"react-native-device-info": "^8.1.3",
"react-native-easing-gradient": "^0.1.2",
"react-native-fast-image": "^8.3.4",
"react-native-fit-image": "^1.5.5",
"react-native-gesture-handler": "~1.10.2",
"react-native-navigation-bar-color": "^2.0.1",
"react-native-pager-view": "^5.1.10",
@@ -8355,14 +8354,6 @@
"react-native": ">=0.60.0"
}
},
"node_modules/react-native-fit-image": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/react-native-fit-image/-/react-native-fit-image-1.5.5.tgz",
"integrity": "sha512-Wl3Vq2DQzxgsWKuW4USfck9zS7YzhvLNPpkwUUCF90bL32e1a0zOVQ3WsJILJOwzmPdHfzZmWasiiAUNBkhNkg==",
"dependencies": {
"prop-types": "^15.5.10"
}
},
"node_modules/react-native-gesture-handler": {
"version": "1.10.3",
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-1.10.3.tgz",
@@ -17943,14 +17934,6 @@
"integrity": "sha512-LpzAdjUphihUpVEBn5fEv5AILe55rHav0YiZroPZ1rumKDhAl4u2cG01ku2Pb7l8sayjTsNu7FuURAlXUUDsow==",
"requires": {}
},
"react-native-fit-image": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/react-native-fit-image/-/react-native-fit-image-1.5.5.tgz",
"integrity": "sha512-Wl3Vq2DQzxgsWKuW4USfck9zS7YzhvLNPpkwUUCF90bL32e1a0zOVQ3WsJILJOwzmPdHfzZmWasiiAUNBkhNkg==",
"requires": {
"prop-types": "^15.5.10"
}
},
"react-native-gesture-handler": {
"version": "1.10.3",
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-1.10.3.tgz",
-1
View File
@@ -35,7 +35,6 @@
"react-native-device-info": "^8.1.3",
"react-native-easing-gradient": "^0.1.2",
"react-native-fast-image": "^8.3.4",
"react-native-fit-image": "^1.5.5",
"react-native-gesture-handler": "~1.10.2",
"react-native-navigation-bar-color": "^2.0.1",
"react-native-pager-view": "^5.1.10",
+14 -2
View File
@@ -15,7 +15,13 @@ export const Checkbox = ({ label, status, onPress, theme }) => (
style={styles.pressable}
onPress={onPress}
>
<Text style={{ color: theme.textColorPrimary }}>{label}</Text>
<Text
style={{
color: status ? theme.colorAccent : theme.textColorPrimary,
}}
>
{label}
</Text>
{status && (
<MaterialCommunityIcons
name="check"
@@ -40,7 +46,13 @@ export const SortItem = ({ label, status, onPress, theme }) => (
style={styles.pressable}
onPress={onPress}
>
<Text style={{ color: theme.textColorPrimary }}>{label}</Text>
<Text
style={{
color: status ? theme.colorAccent : theme.textColorPrimary,
}}
>
{label}
</Text>
{status && (
<MaterialCommunityIcons
name={status === "asc" ? "arrow-up" : "arrow-down"}
+10 -5
View File
@@ -39,7 +39,7 @@ const NovelCover = ({
[]
);
const selectNovel = () => onLongPress(item.novelId);
const selectNovel = () => onLongPress && onLongPress(item.novelId);
return displayMode !== 2 ? (
<View
@@ -174,7 +174,15 @@ const CompactTitle = ({ novelName }) => (
>
<Text
numberOfLines={2}
style={[styles.title, { color: "rgba(255,255,255,1)" }]}
style={[
styles.title,
{
color: "rgba(255,255,255,1)",
textShadowColor: "rgba(0, 0, 0, 0.75)",
textShadowOffset: { width: -1, height: 1 },
textShadowRadius: 10,
},
]}
>
{novelName}
</Text>
@@ -253,9 +261,6 @@ const styles = StyleSheet.create({
fontFamily: "pt-sans-bold",
fontSize: 14,
padding: 8,
textShadowColor: "rgba(0, 0, 0, 0.75)",
textShadowOffset: { width: -1, height: 1 },
textShadowRadius: 10,
},
linearGradient: {
borderRadius: 4,
+13
View File
@@ -113,6 +113,19 @@ export const markChapterRead = async (chapterId) => {
});
};
const markChapterUnreadQuery = `UPDATE chapters SET \`read\` = 0 WHERE chapterId = ?`;
export const markChapterUnread = async (chapterId) => {
db.transaction((tx) => {
tx.executeSql(
markChapterUnreadQuery,
[chapterId],
(tx, res) => {},
(tx, error) => console.log(error)
);
});
};
const markAllChaptersReadQuery = `UPDATE chapters SET \`read\` = 1 WHERE novelId = ?`;
export const markAllChaptersRead = async (novelId) => {
+1 -1
View File
@@ -22,7 +22,7 @@ export const githubUpdateChecker = () => {
};
const isNewVersion = (versionTag) => {
let currentVersion = "v1.1.2";
let currentVersion = "v1.1.4";
const regex = /[^\\d.]/;
let newVersion = versionTag.replace(regex, "");
+2 -2
View File
@@ -266,9 +266,9 @@ export const markPreviousChaptersUnreadAction =
};
export const markChapterUnreadAction =
(chapters, novelId) => async (dispatch) => {
(chapters, novelId) => async (dispatch, getState) => {
const { sort = "ORDER BY chapterId ASC", filter = "" } =
store.getState().preferenceReducer.novelSettings[novelId];
getState().preferenceReducer.novelSettings[novelId];
chapters.map((chapter) => markChapterUnread(chapter.chapterId));
+3 -1
View File
@@ -1,4 +1,4 @@
import { GET_UPDATES } from "./updates.types";
import { GET_UPDATES, SET_LAST_UPDATE_TIME } from "./updates.types";
import { getUpdates } from "../../database/queries/UpdateQueries";
import { updateAllNovels } from "../../services/updates";
@@ -36,6 +36,8 @@ export const getUpdatesAction = () => async (dispatch) => {
export const updateLibraryAction = () => async (dispatch, getState) => {
showToast("Updating library");
dispatch({ type: SET_LAST_UPDATE_TIME, payload: Date.now() });
const { downloadNewChapters = false } = getState().settingsReducer;
await updateAllNovels({ downloadNewChapters });
+18 -1
View File
@@ -1,7 +1,14 @@
import { LOAD_UPDATES, GET_UPDATES } from "./updates.types";
import {
LOAD_UPDATES,
GET_UPDATES,
SET_LAST_UPDATE_TIME,
SHOW_LAST_UPDATE_TIME,
} from "./updates.types";
const initialState = {
updates: [],
lastUpdateTime: null,
showLastUpdateTime: true,
loading: true,
};
@@ -13,6 +20,16 @@ const updateReducer = (state = initialState, action) => {
return { ...state, loading: true };
case GET_UPDATES:
return { ...state, updates: payload, loading: false };
case SET_LAST_UPDATE_TIME:
return {
...state,
lastUpdateTime: payload,
};
case SHOW_LAST_UPDATE_TIME:
return {
...state,
showLastUpdateTime: payload,
};
default:
return state;
}
+2
View File
@@ -1,2 +1,4 @@
export const GET_UPDATES = "GET_UPDATES";
export const LOAD_UPDATES = "LOAD_UPDATES";
export const SET_LAST_UPDATE_TIME = "SET_LAST_UPDATE_TIME";
export const SHOW_LAST_UPDATE_TIME = "SHOW_LAST_UPDATE_TIME";
+73 -59
View File
@@ -60,69 +60,83 @@ const BrowseSettings = ({ navigation }) => {
<ScreenContainer theme={theme}>
<Appbar onBackAction={navigation.goBack} title="Sources" />
<View>
<List.SubHeader theme={theme}>Global Search</List.SubHeader>
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={{
padding: 16,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
}}
onPress={() =>
dispatch(
setAppSettings(
"searchAllSources",
!searchAllSources
)
)
}
>
<Text style={{ color: theme.textColorPrimary }}>
Search all sources
</Text>
<Switch
color={theme.colorAccent}
value={searchAllSources}
onValueChange={() =>
dispatch(
setAppSettings(
"searchAllSources",
!searchAllSources
)
)
}
/>
</Pressable>
<List.Divider theme={theme} />
<List.SubHeader theme={theme}>Discover</List.SubHeader>
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={{
padding: 16,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
}}
onPress={() => dispatch(enableDiscover("showMyAnimeList"))}
>
<Text style={{ color: theme.textColorPrimary }}>
MyAnimeList
</Text>
<Switch
color={theme.colorAccent}
value={showMyAnimeList}
onValueChange={() =>
dispatch(enableDiscover("showMyAnimeList"))
}
/>
</Pressable>
<List.Divider theme={theme} />
<List.SubHeader theme={theme}>Languages</List.SubHeader>
<FlatList
data={languages}
keyExtractor={(item) => item}
renderItem={renderItem}
ListHeaderComponent={
<View>
<List.SubHeader theme={theme}>
Global Search
</List.SubHeader>
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={{
padding: 16,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
}}
onPress={() =>
dispatch(
setAppSettings(
"searchAllSources",
!searchAllSources
)
)
}
>
<Text style={{ color: theme.textColorPrimary }}>
Search all sources
</Text>
<Switch
color={theme.colorAccent}
value={searchAllSources}
onValueChange={() =>
dispatch(
setAppSettings(
"searchAllSources",
!searchAllSources
)
)
}
/>
</Pressable>
<List.Divider theme={theme} />
<List.SubHeader theme={theme}>
Discover
</List.SubHeader>
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={{
padding: 16,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
}}
onPress={() =>
dispatch(enableDiscover("showMyAnimeList"))
}
>
<Text style={{ color: theme.textColorPrimary }}>
MyAnimeList
</Text>
<Switch
color={theme.colorAccent}
value={showMyAnimeList}
onValueChange={() =>
dispatch(
enableDiscover("showMyAnimeList")
)
}
/>
</Pressable>
<List.Divider theme={theme} />
<List.SubHeader theme={theme}>
Languages
</List.SubHeader>
</View>
}
/>
</View>
</ScreenContainer>
+3 -1
View File
@@ -419,6 +419,9 @@ const Chapter = ({ route, navigation }) => {
height: auto;
max-width: 100%;
}
</style>
<style>
${reader.customCSS}
@font-face {
font-family: ${reader.fontFamily};
src: url("file:///android_asset/fonts/${
@@ -426,7 +429,6 @@ const Chapter = ({ route, navigation }) => {
}.ttf");
}
${reader.customCSS}
</style>
</head>
<body>
+329 -310
View File
@@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useCallback, useState } from "react";
import {
StyleSheet,
View,
@@ -68,301 +68,293 @@ const ReaderSheet = ({
{ value: "right", icon: "format-align-right" },
];
const FirstRoute = () => (
<View style={{ flex: 1, paddingVertical: 16 }}>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
}}
>
<Text
style={{
color: theme.textColorSecondary,
paddingHorizontal: 16,
textAlign: "center",
}}
>
Text size
</Text>
<Slider
style={{
flex: 1,
height: 40,
}}
value={reader.textSize}
minimumValue={12}
maximumValue={20}
step={1}
minimumTrackTintColor={theme.colorAccent}
maximumTrackTintColor="#000000"
thumbTintColor={theme.colorAccent}
onSlidingComplete={(value) =>
dispatch(setReaderSettings("textSize", value))
}
/>
</View>
<View style={{ paddingLeft: 16, paddingRight: 8 }}>
<Row
const FirstRoute = useCallback(
() => (
<View style={{ flex: 1, paddingVertical: 16 }}>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
marginVertical: 6,
}}
>
<Text style={{ color: theme.textColorSecondary }}>
Color
</Text>
<View style={{ marginLeft: 16 }}>
<ScrollView horizontal={true}>
{presetThemes.map((item, index) => (
<ToggleColorButton
key={index}
selected={reader.theme === item.value}
backgroundColor={item.backgroundColor}
textColor={item.textColor}
theme={theme}
onPress={() => {
dispatch(
setReaderSettings(
"theme",
item.value
)
);
dispatch(
setReaderSettings(
"textColor",
item.textColor
)
);
}}
/>
))}
</ScrollView>
</View>
</Row>
<Row
style={{
justifyContent: "space-between",
marginVertical: 6,
}}
>
<Text style={{ color: theme.textColorSecondary }}>
Text align
</Text>
<Row>
{textAlignments.map((item) => (
<ToggleButton
key={item.value}
selected={item.value === reader.textAlign}
icon={item.icon}
theme={theme}
onPress={() =>
dispatch(
setReaderSettings(
"textAlign",
item.value
)
)
}
/>
))}
</Row>
</Row>
<Row
style={{
justifyContent: "space-between",
marginVertical: 6,
}}
>
<Text style={{ color: theme.textColorSecondary }}>
Padding
</Text>
<Row>
<IconButton
icon="minus"
color={theme.colorAccent}
size={26}
disabled={reader.padding <= 0 ? true : false}
onPress={() =>
dispatch(
setReaderSettings(
"padding",
reader.padding - 1
)
)
}
style={{ marginVertical: 0 }}
/>
<Text
style={{
color: "#FFFFFF",
paddingHorizontal: 24,
}}
>
{`${reader.padding}%`}
</Text>
<IconButton
icon="plus"
color={theme.colorAccent}
size={26}
disabled={reader.padding >= 10 ? true : false}
onPress={() =>
dispatch(
setReaderSettings(
"padding",
reader.padding + 1
)
)
}
style={{ marginVertical: 0 }}
/>
</Row>
</Row>
<Row
style={{
justifyContent: "space-between",
marginVertical: 6,
}}
>
<Text style={{ color: theme.textColorSecondary }}>
Line height
</Text>
<Row>
<IconButton
icon="minus"
color={theme.colorAccent}
size={26}
disabled={reader.lineHeight <= 1.3 ? true : false}
onPress={() =>
dispatch(
setReaderSettings(
"lineHeight",
reader.lineHeight - 0.1
)
)
}
style={{ marginVertical: 0 }}
/>
<Text
style={{
color: "#FFFFFF",
paddingHorizontal: 24,
}}
>
{`${Math.round(reader.lineHeight * 10) / 10}%`}
</Text>
<IconButton
icon="plus"
color={theme.colorAccent}
size={26}
disabled={reader.lineHeight >= 2 ? true : false}
onPress={() =>
dispatch(
setReaderSettings(
"lineHeight",
reader.lineHeight + 0.1
)
)
}
style={{ marginVertical: 0 }}
/>
</Row>
</Row>
<Row
style={{
justifyContent: "space-between",
marginVertical: 6,
alignItems: "center",
}}
>
<Text
style={{
color: theme.textColorSecondary,
marginRight: 16,
paddingHorizontal: 16,
textAlign: "center",
}}
>
Font style
Text size
</Text>
<ScrollView
horizontal={true}
contentContainerStyle={{
alignItems: "center",
<Slider
style={{
flex: 1,
height: 40,
}}
value={reader.textSize}
minimumValue={12}
maximumValue={20}
step={1}
minimumTrackTintColor={theme.colorAccent}
maximumTrackTintColor="#000000"
thumbTintColor={theme.colorAccent}
onSlidingComplete={(value) =>
dispatch(setReaderSettings("textSize", value))
}
/>
</View>
<View style={{ paddingLeft: 16, paddingRight: 8 }}>
<Row
style={{
justifyContent: "space-between",
marginVertical: 6,
}}
>
{fonts.map((font) => (
<View
style={[
{
borderRadius: 8,
overflow: "hidden",
marginHorizontal: 6,
},
reader.fontFamily === font.fontFamily && {
backgroundColor: theme.rippleColor,
},
]}
>
<Pressable
style={{
borderRadius: 8,
paddingHorizontal: 10,
paddingVertical: 4,
}}
<Text style={{ color: theme.textColorSecondary }}>
Color
</Text>
<View style={{ marginLeft: 16 }}>
<ScrollView horizontal={true}>
{presetThemes.map((item, index) => (
<ToggleColorButton
key={index}
selected={reader.theme === item.value}
backgroundColor={item.backgroundColor}
textColor={item.textColor}
theme={theme}
onPress={() => {
dispatch(
setReaderSettings(
"theme",
item.value
)
);
dispatch(
setReaderSettings(
"textColor",
item.textColor
)
);
}}
/>
))}
</ScrollView>
</View>
</Row>
<Row
style={{
justifyContent: "space-between",
marginVertical: 6,
}}
>
<Text style={{ color: theme.textColorSecondary }}>
Text align
</Text>
<Row>
{textAlignments.map((item) => (
<ToggleButton
key={item.value}
selected={item.value === reader.textAlign}
icon={item.icon}
theme={theme}
onPress={() =>
dispatch(
setReaderSettings(
"fontFamily",
font.fontFamily
"textAlign",
item.value
)
)
}
android_ripple={{
color: theme.rippleColor,
}}
/>
))}
</Row>
</Row>
<Row
style={{
justifyContent: "space-between",
marginVertical: 6,
}}
>
<Text style={{ color: theme.textColorSecondary }}>
Padding
</Text>
<Row>
<IconButton
icon="minus"
color={theme.colorAccent}
size={26}
disabled={reader.padding <= 0 ? true : false}
onPress={() =>
dispatch(
setReaderSettings(
"padding",
reader.padding - 1
)
)
}
style={{ marginVertical: 0 }}
/>
<Text
style={{
color: "#FFFFFF",
paddingHorizontal: 24,
}}
>
{`${reader.padding}%`}
</Text>
<IconButton
icon="plus"
color={theme.colorAccent}
size={26}
disabled={reader.padding >= 10 ? true : false}
onPress={() =>
dispatch(
setReaderSettings(
"padding",
reader.padding + 1
)
)
}
style={{ marginVertical: 0 }}
/>
</Row>
</Row>
<Row
style={{
justifyContent: "space-between",
marginVertical: 6,
}}
>
<Text style={{ color: theme.textColorSecondary }}>
Line height
</Text>
<Row>
<IconButton
icon="minus"
color={theme.colorAccent}
size={26}
disabled={
reader.lineHeight <= 1.3 ? true : false
}
onPress={() =>
dispatch(
setReaderSettings(
"lineHeight",
reader.lineHeight - 0.1
)
)
}
style={{ marginVertical: 0 }}
/>
<Text
style={{
color: "#FFFFFF",
paddingHorizontal: 24,
}}
>
{`${Math.round(reader.lineHeight * 10) / 10}%`}
</Text>
<IconButton
icon="plus"
color={theme.colorAccent}
size={26}
disabled={reader.lineHeight >= 2 ? true : false}
onPress={() =>
dispatch(
setReaderSettings(
"lineHeight",
reader.lineHeight + 0.1
)
)
}
style={{ marginVertical: 0 }}
/>
</Row>
</Row>
<Row
style={{
justifyContent: "space-between",
marginVertical: 6,
}}
>
<Text
style={{
color: theme.textColorSecondary,
marginRight: 16,
}}
>
Font style
</Text>
<ScrollView
horizontal={true}
showsHorizontalScrollIndicator={false}
contentContainerStyle={{ alignItems: "center" }}
>
{fonts.map((font) => (
<View
style={[
{
borderRadius: 8,
overflow: "hidden",
marginHorizontal: 6,
},
reader.fontFamily ===
font.fontFamily && {
backgroundColor: theme.rippleColor,
},
]}
>
<Text
<Pressable
style={{
fontSize: 16,
fontFamily: font.fontFamily,
color: theme.textColorPrimary,
borderRadius: 8,
paddingHorizontal: 10,
paddingVertical: 4,
}}
onPress={() =>
dispatch(
setReaderSettings(
"fontFamily",
font.fontFamily
)
)
}
android_ripple={{
color: theme.rippleColor,
}}
>
{font.name}
</Text>
</Pressable>
</View>
))}
</ScrollView>
</Row>
<Text
style={{
fontSize: 16,
fontFamily: font.fontFamily,
color:
reader.fontFamily ===
font.fontFamily
? theme.colorAccent
: theme.textColorPrimary,
}}
>
{font.name}
</Text>
</Pressable>
</View>
))}
</ScrollView>
</Row>
</View>
</View>
</View>
),
[reader]
);
const SecondRoute = () => (
<View style={{ flex: 1 }}>
<Pressable
style={styles.switchStyle}
android_ripple={{ color: theme.rippleColor }}
onPress={() =>
dispatch(
setAppSettings(
"useWebViewForChapter",
!useWebViewForChapter
)
)
}
>
<Text
style={{
color: theme.textColorSecondary,
}}
>
Use WebView
</Text>
<Switch
value={useWebViewForChapter}
onValueChange={() =>
const SecondRoute = useCallback(
() => (
<View style={{ flex: 1 }}>
<Pressable
style={styles.switchStyle}
android_ripple={{ color: theme.rippleColor }}
onPress={() =>
dispatch(
setAppSettings(
"useWebViewForChapter",
@@ -370,31 +362,31 @@ const ReaderSheet = ({
)
)
}
color={theme.colorAccent}
/>
</Pressable>
<Pressable
style={styles.switchStyle}
android_ripple={{ color: theme.rippleColor }}
onPress={() =>
dispatch(
setAppSettings(
"showScrollPercentage",
!showScrollPercentage
)
)
}
>
<Text
style={{
color: theme.textColorSecondary,
}}
>
Show progress percentage
</Text>
<Switch
value={showScrollPercentage}
onValueChange={() =>
<Text
style={{
color: theme.textColorSecondary,
}}
>
Use WebView
</Text>
<Switch
value={useWebViewForChapter}
onValueChange={() =>
dispatch(
setAppSettings(
"useWebViewForChapter",
!useWebViewForChapter
)
)
}
color={theme.colorAccent}
/>
</Pressable>
<Pressable
style={styles.switchStyle}
android_ripple={{ color: theme.rippleColor }}
onPress={() =>
dispatch(
setAppSettings(
"showScrollPercentage",
@@ -402,36 +394,61 @@ const ReaderSheet = ({
)
)
}
color={theme.colorAccent}
/>
</Pressable>
{!useWebViewForChapter && (
<Pressable
style={styles.switchStyle}
android_ripple={{ color: theme.rippleColor }}
onPress={() =>
dispatch(setAppSettings("textSelectable", !selectText))
}
>
<Text
style={{
color: theme.textColorSecondary,
}}
>
Select text
Show progress percentage
</Text>
<Switch
value={selectText}
value={showScrollPercentage}
onValueChange={() =>
dispatch(
setAppSettings("textSelectable", !selectText)
setAppSettings(
"showScrollPercentage",
!showScrollPercentage
)
)
}
color={theme.colorAccent}
/>
</Pressable>
)}
</View>
{!useWebViewForChapter && (
<Pressable
style={styles.switchStyle}
android_ripple={{ color: theme.rippleColor }}
onPress={() =>
dispatch(
setAppSettings("textSelectable", !selectText)
)
}
>
<Text
style={{
color: theme.textColorSecondary,
}}
>
Select text
</Text>
<Switch
value={selectText}
onValueChange={() =>
dispatch(
setAppSettings(
"textSelectable",
!selectText
)
)
}
color={theme.colorAccent}
/>
</Pressable>
)}
</View>
),
[useWebViewForChapter, selectText, showScrollPercentage]
);
const renderScene = SceneMap({
@@ -467,6 +484,8 @@ const ReaderSheet = ({
ref={bottomSheetRef}
draggableRange={{ top: 400, bottom: 0 }}
snappingPoints={[0, 400]}
showBackdrop={true}
backdropOpacity={0}
>
<View
style={[
+2 -2
View File
@@ -21,14 +21,14 @@ const AboutScreen = ({ navigation }) => {
<List.Section>
<List.Item
title="Version"
description="Stable 1.1.2 (17/07/21 08:00 AM)"
description="Stable 1.1.4 (15/08/21 9:00 AM)"
theme={theme}
/>
<List.Item
title="What's new"
onPress={() =>
Linking.openURL(
"https://github.com/LNReader/lnreader/releases/tag/v1.1.2"
"https://github.com/LNReader/lnreader/releases/tag/v1.1.4"
)
}
theme={theme}
+16
View File
@@ -18,6 +18,7 @@ import {
import ColorPickerModal from "../../components/ColorPickerModal";
import SwitchSetting from "../../components/Switch/Switch";
import { ScrollView } from "react-native";
import { SHOW_LAST_UPDATE_TIME } from "../../redux/updates/updates.types";
const GenralSettings = ({ navigation }) => {
const theme = useTheme();
@@ -35,6 +36,10 @@ const GenralSettings = ({ navigation }) => {
showLabelsInNav = false,
} = useSettings();
const { showLastUpdateTime = true } = useSelector(
(state) => state.updatesReducer
);
const displayModeLabel = (displayMode) => {
const label = {
0: "Compact Grid",
@@ -165,6 +170,17 @@ const GenralSettings = ({ navigation }) => {
}
theme={theme}
/>
<SwitchSetting
label="Show last update time"
value={showLastUpdateTime}
onPress={() =>
dispatch({
type: SHOW_LAST_UPDATE_TIME,
payload: !showLastUpdateTime,
})
}
theme={theme}
/>
<List.Divider theme={theme} />
<List.SubHeader theme={theme}>Auto-download</List.SubHeader>
<SwitchSetting
+142 -101
View File
@@ -1,15 +1,18 @@
import React, { useState } from "react";
import React, { useCallback, useState } from "react";
import {
Dimensions,
Pressable,
ScrollView,
StyleSheet,
Text,
TextInput,
useWindowDimensions,
View,
} from "react-native";
import { Button } from "react-native-paper";
import WebView from "react-native-webview";
import { useDispatch } from "react-redux";
import { TabView, SceneMap, TabBar } from "react-native-tab-view";
import { Appbar } from "../../components/Appbar";
import ColorPickerModal from "../../components/ColorPickerModal";
@@ -28,11 +31,25 @@ import {
readerTextColor,
} from "../Chapter/readerStyleController";
const ReaderSettings = ({ navigation }) => {
const theme = useTheme();
const dispatch = useDispatch();
const FirstRoute = React.memo(({ theme, dispatch, reader }) => {
const presetThemes = [
{
value: 1,
backgroundColor: "#000000",
textColor: "rgba(255,255,255,0.7)",
},
{ value: 2, backgroundColor: "#FFFFFF", textColor: "#111111" },
{ value: 3, backgroundColor: "#F7DFC6", textColor: "#593100" },
{ value: 4, backgroundColor: "#292832", textColor: "#CCCCCC" },
{ value: 5, backgroundColor: "#2B2C30", textColor: "#CCCCCC" },
];
const reader = useReaderSettings();
const textAlignments = [
{ value: "left", icon: "format-align-left" },
{ value: "center", icon: "format-align-center" },
{ value: "justify", icon: "format-align-justify" },
{ value: "right", icon: "format-align-right" },
];
/**
* Reader Background Color Modal
@@ -59,100 +76,10 @@ const ReaderSettings = ({ navigation }) => {
dispatch(setReaderSettings("textColor", val ?? reader.textColor));
};
const readerStyles = [
{
paddingVertical: 16,
paddingBottom: 32,
fontSize: reader.textSize,
color: reader.textColor,
lineHeight: readerLineHeight(reader.textSize, reader.lineHeight),
textAlign: reader.textAlign,
paddingHorizontal: `${reader.padding}%`,
},
reader.fontFamily && {
fontFamily: reader.fontFamily,
},
];
const presetThemes = [
{
value: 1,
backgroundColor: "#000000",
textColor: "rgba(255,255,255,0.7)",
},
{ value: 2, backgroundColor: "#FFFFFF", textColor: "#111111" },
{ value: 3, backgroundColor: "#F7DFC6", textColor: "#593100" },
{ value: 4, backgroundColor: "#292832", textColor: "#CCCCCC" },
{ value: 5, backgroundColor: "#2B2C30", textColor: "#CCCCCC" },
];
const textAlignments = [
{ value: "left", icon: "format-align-left" },
{ value: "center", icon: "format-align-center" },
{ value: "justify", icon: "format-align-justify" },
{ value: "right", icon: "format-align-right" },
];
const [customHtmlCSS, setcustomHtmlCSS] = useState(reader.customCSS);
return (
<ScreenContainer theme={theme}>
<Appbar title="Reader" onBackAction={navigation.goBack} />
<View style={{ height: 360 }}>
<WebView
originWhitelist={["*"]}
style={{
backgroundColor: readerBackground(reader.theme),
}}
source={{
html: `
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<style>
@font-face {
font-family: ${reader.fontFamily};
src: url("file:///android_asset/fonts/${reader.fontFamily}.ttf");
}
body {
padding-top: 8px;
padding-bottom: 8px;
padding-left: ${reader.padding}%;
padding-right: ${reader.padding}%;
font-family: ${reader.fontFamily};
text-align: ${reader.textAlign};
line-height: ${reader.lineHeight};
font-size: ${reader.textSize}px;
color: ${reader.textColor};
}
${reader.customCSS}
</style>
</head>
<body>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Ut lobortis nunc id nisl tempor, sed tristique sem
sagittis. Mauris consequat orci egestas volutpat
vestibulum. Nullam convallis, leo in porttitor
fringilla, ex mi finibus felis, non molestie ligula
ligula facilisis ligula. Nam eu est a turpis porttitor
aliquet sed vel risus. Ut ac scelerisque diam. Nulla
fringilla enim non ex rhoncus faucibus. Integer eget
lacus justo.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Ut lobortis nunc id nisl tempor, sed tristique sem
sagittis. Mauris consequat orci egestas volutpat
vestibulum. Nullam convallis, leo in porttitor
fringilla, ex mi finibus felis, non molestie ligula
ligula facilisis ligula.
</body>
</html>
`,
}}
/>
</View>
<View style={{ flex: 1 }}>
<ScrollView>
<List.Section>
<List.SubHeader theme={theme}>Preset</List.SubHeader>
@@ -168,6 +95,7 @@ const ReaderSettings = ({ navigation }) => {
<ScrollView
style={{ marginLeft: 16 }}
horizontal={true}
showsHorizontalScrollIndicator={false}
>
<Row>
{presetThemes.map((theme) => (
@@ -219,10 +147,7 @@ const ReaderSettings = ({ navigation }) => {
/>
<List.Divider theme={theme} />
<List.SubHeader theme={theme}>Text</List.SubHeader>
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={styles.pressableListItem}
>
<Pressable style={styles.pressableListItem}>
<View>
<Text
style={{
@@ -307,7 +232,6 @@ const ReaderSettings = ({ navigation }) => {
</View>
</List.Section>
</ScrollView>
<ColorPickerModal
title="Reader background color"
modalVisible={readerBackgroundColorModal}
@@ -324,6 +248,123 @@ const ReaderSettings = ({ navigation }) => {
theme={theme}
onSubmit={setReaderTextColor}
/>
</View>
);
});
const SecondRoute = React.memo(({ reader }) => (
<View style={{ flex: 1 }}>
<WebView
originWhitelist={["*"]}
style={{
backgroundColor: readerBackground(reader.theme),
}}
source={{
html: `
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<style>
body {
color: ${reader.textColor};
text-align: ${reader.textAlign};
line-height: ${reader.lineHeight};
font-size: ${reader.textSize}px;
padding-top: 8px;
padding-bottom: 8px;
padding-left: ${reader.padding}%;
padding-right: ${reader.padding}%;
font-family: ${reader.fontFamily};
}
</style>
<style>
${reader.customCSS}
@font-face {
font-family: ${reader.fontFamily};
src: url("file:///android_asset/fonts/${reader.fontFamily}.ttf");
}
</style>
</head>
<body>
<p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean lobortis, diam sed malesuada bibendum, nulla libero scelerisque sapien, nec interdum nisl ipsum ac ipsum. In lacinia eros ut quam commodo, in finibus augue ultricies. Vestibulum ex purus, condimentum eget sem at, molestie semper mi. Mauris ac feugiat quam. Pellentesque sagittis bibendum nibh eu lacinia. Aenean rhoncus, velit sit amet mollis egestas, diam turpis ornare velit, a dictum elit velit in erat. Quisque luctus in sem a vulputate.
</p>
<p> Pellentesque id tempus orci, non finibus tortor. Suspendisse in neque non eros eleifend hendrerit vitae a lacus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc interdum magna nulla, eget pharetra arcu dapibus eu. Pellentesque sed lectus sit amet sem porta lacinia. Curabitur consectetur dolor in nibh varius, ac placerat erat pellentesque. Proin in justo condimentum, fermentum lacus eget, ultrices ante. Fusce tempor blandit erat nec mollis. Vivamus in leo et nunc consectetur elementum nec ac lectus.
</p>
<p> In consectetur libero tempor metus interdum, sit amet viverra leo dapibus. Nullam tincidunt justo hendrerit lorem maximus, eu pretium erat fringilla. Nulla scelerisque leo at enim convallis, a elementum velit laoreet. Aliquam quis vestibulum quam. Morbi ut sollicitudin mi, et laoreet elit. Vivamus mattis, nulla at blandit sodales, lectus lacus luctus metus, non euismod neque urna vel nisl. Quisque vitae ante vitae orci tempus egestas quis eu nibh. Phasellus eget nulla non velit tincidunt fringilla eget nec libero. Morbi tempor erat quis rutrum condimentum.
</p>
<p> Cras sem magna, tempus sed urna eget, scelerisque tincidunt tortor. Cras euismod turpis libero, ut condimentum nulla ultrices id. Phasellus faucibus, elit ut imperdiet euismod, enim dui volutpat turpis, in tincidunt ex ligula non felis. Praesent erat erat, ultrices ut imperdiet sed, congue eu leo. Aliquam viverra fringilla ultrices. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Praesent ac malesuada massa. Cras eu augue in dui venenatis laoreet sit amet eget sem. Nullam malesuada sapien felis, et varius neque auctor eu.
</p>
<p> Sed vestibulum facilisis libero, in aliquet mauris lobortis et. In vitae lectus id risus cursus dictum at ac mi. Praesent ac lectus eget nunc elementum pellentesque. Ut at dui magna. Aliquam mattis posuere gravida. Morbi volutpat, dolor quis varius tincidunt, quam nulla vulputate libero, eu sagittis dui odio et magna. Sed luctus lectus ante, sit amet ultrices ligula convallis vitae. Sed efficitur libero nec quam porta, et fringilla turpis egestas. Nulla facilisi. Duis leo tellus, porta non enim eu, tincidunt cursus lorem.
</p>
</body>
</html>
`,
}}
/>
</View>
));
const ReaderSettings = ({ navigation }) => {
const theme = useTheme();
const dispatch = useDispatch();
const reader = useReaderSettings();
const renderScene = ({ route }) => {
switch (route.key) {
case "first":
return (
<FirstRoute
dispatch={dispatch}
theme={theme}
reader={reader}
/>
);
case "second":
return <SecondRoute reader={reader} />;
}
};
const layout = useWindowDimensions();
const [index, setIndex] = useState(0);
const [routes] = useState([
{ key: "first", title: "Settings" },
{ key: "second", title: "Preview" },
]);
const renderTabBar = (props) => (
<TabBar
{...props}
indicatorStyle={{ backgroundColor: theme.colorAccent }}
style={{ backgroundColor: theme.colorPrimary }}
renderLabel={({ route, focused, color }) => (
<Text style={{ color }}>{route.title}</Text>
)}
inactiveColor={theme.textColorSecondary}
activeColor={theme.colorAccent}
pressColor={theme.rippleColor}
/>
);
return (
<ScreenContainer theme={theme}>
<Appbar
title="Reader"
onBackAction={navigation.goBack}
style={{ elevation: 0 }}
/>
<TabView
navigationState={{ index, routes }}
renderTabBar={renderTabBar}
renderScene={renderScene}
onIndexChange={setIndex}
initialLayout={{ width: layout.width }}
swipeEnabled={false}
/>
</ScreenContainer>
);
};
+1 -1
View File
@@ -44,7 +44,7 @@ const ChapterItem = ({
android_ripple={{ color: theme.rippleColor }}
>
<Row style={{ flex: 1, overflow: "hidden" }}>
{bookmark === 1 && <ChapterBookmarkButton theme={theme} />}
{!!bookmark && <ChapterBookmarkButton theme={theme} />}
<View>
<Text
style={{
+2 -2
View File
@@ -99,10 +99,10 @@ const NovelInfoHeader = ({
{!loading && (
<>
<NovelAuthor theme={theme}>
{novel.author ?? "Unknown author"}
{novel.author || "Unknown author"}
</NovelAuthor>
<NovelInfo theme={theme}>
{novel.status ?? "Unknown status"}
{novel.status || "Unknown status"}
</NovelInfo>
<NovelInfo theme={theme}>
{novel.source}
+18 -1
View File
@@ -28,7 +28,12 @@ import {
const Updates = ({ navigation }) => {
const theme = useTheme();
const dispatch = useDispatch();
const { updates, loading } = useSelector((state) => state.updatesReducer);
const {
updates,
lastUpdateTime = null,
showLastUpdateTime = true,
loading,
} = useSelector((state) => state.updatesReducer);
const { downloadQueue } = useSelector((state) => state.downloadsReducer);
const [searchText, setSearchText] = useState("");
@@ -171,6 +176,18 @@ const Updates = ({ navigation }) => {
},
]}
/>
{showLastUpdateTime && lastUpdateTime && (
<Text
style={{
color: theme.textColorSecondary,
paddingHorizontal: 16,
paddingTop: 8,
textAlign: "center",
}}
>
Library last updated: {moment(lastUpdateTime).fromNow()}
</Text>
)}
<FlatList
contentContainerStyle={styles.flatList}
data={searchText ? searchResults : updates}
+2 -3
View File
@@ -110,12 +110,11 @@ const parseNovelAndChapters = async (novelUrl) => {
let novelChapters = [];
let firstChapterNo, totalChapters;
let totalChapters;
totalChapters = $(".header-stats > span").first().text().match(/\d+/)[0];
firstChapterNo = $(".chapter-no").first().text().match(/\d+/)[0];
for (let i = firstChapterNo; i <= totalChapters; i++) {
for (let i = 1; i <= totalChapters; i++) {
const chapterName = "Chapter " + i;
const releaseDate = null;
+159
View File
@@ -0,0 +1,159 @@
import cheerio from "react-native-cheerio";
import NovelUpdatesScraper from "./novelupdates";
const sourceId = 72;
const sourceName = "LightNovelUpdates";
const baseUrl = "https://www.lightnovelupdates.com/";
const popularNovels = async (page) => {
let url = `${baseUrl}novel/page/${page}/?m_orderby=rating`;
let totalPages = 500;
const result = await fetch(url);
const body = await result.text();
$ = cheerio.load(body);
let novels = [];
$(".page-item-detail").each(function () {
const novelName = $(this).find(".h5 > a").text();
const novelCover = $(this).find("img").attr("src");
const novelUrl = $(this).find(".h5 > a").attr("href");
const novel = {
sourceId,
novelName,
novelCover,
novelUrl,
};
novels.push(novel);
});
return { totalPages, novels };
};
const parseNovelAndChapters = async (novelUrl) => {
const url = novelUrl;
console.log(url);
const result = await fetch(url);
const body = await result.text();
$ = cheerio.load(body);
let novel = { sourceId, sourceName, novelUrl, url };
novel.novelName = $(".post-title > h1").text().trim();
novel.novelCover =
$(".summary_image > a > img").attr("src") ||
"https://github.com/LNReader/lnreader-sources/blob/main/src/coverNotAvailable.jpg?raw=true";
$(".post-content_item").each(function (result) {
detailName = $(this).find(".summary-heading > h5").text().trim();
detail = $(this).find(".summary-content").text().trim();
switch (detailName) {
case "Genre(s)":
novel.genre = detail.trim().replace(/[\t\n]/g, ",");
break;
case "Author(s)":
novel.author = detail.trim();
break;
case "Artist(s)":
novel.status = detail.trim();
break;
}
});
novel.summary = $("div.summary__content").text();
let novelChapters = [];
const novelId = $(".rating-post-id").attr("value");
let formData = new FormData();
formData.append("action", "manga_get_chapters");
formData.append("manga", novelId);
const data = await fetch(
"https://www.lightnovelupdates.com/wp-admin/admin-ajax.php",
{
method: "POST",
body: formData,
}
);
const text = await data.text();
$ = cheerio.load(text);
$(".wp-manga-chapter").each(function () {
const chapterName = $(this).find("a").text().trim();
const releaseDate = $(this).find("span").text().trim();
const chapterUrl = $(this).find("a").attr("href");
const chapter = { chapterName, releaseDate, chapterUrl };
novelChapters.push(chapter);
});
novel.chapters = novelChapters.reverse();
return novel;
};
const parseChapter = async (novelUrl, chapterUrl) => {
const chapterName = "";
const chapterText = (
await NovelUpdatesScraper.parseChapter(novelUrl, chapterUrl)
).chapterText;
const chapter = {
sourceId,
novelUrl,
chapterUrl,
chapterName,
chapterText,
};
return chapter;
};
const searchNovels = async (searchTerm) => {
const url = `${baseUrl}?s=${searchTerm}&post_type=wp-manga&m_orderby=rating`;
const result = await fetch(url);
const body = await result.text();
$ = cheerio.load(body);
let novels = [];
$(".c-tabs-item__content").each(function (result) {
const novelName = $(this).find(".h4 > a").text();
const novelCover = $(this).find("img").attr("src");
const novelUrl = $(this).find(".h4 > a").attr("href");
const novel = {
sourceId,
novelName,
novelCover,
novelUrl,
};
novels.push(novel);
});
return novels;
};
const LightNovelUpdatesScraper = {
popularNovels,
parseNovelAndChapters,
parseChapter,
searchNovels,
};
export default LightNovelUpdatesScraper;
+158
View File
@@ -0,0 +1,158 @@
import cheerio from "react-native-cheerio";
import { Status } from "../helpers/constants";
const sourceId = 71;
const sourceName = "NovelOnlineFull.com";
const baseUrl = "https://novelonlinefull.com/";
const popularNovels = async (page) => {
let url =
baseUrl + "novel_list?type=topview&category=all&state=all&page=" + page;
const totalPages = 2155;
const result = await fetch(url);
const body = await result.text();
$ = cheerio.load(body);
let novels = [];
$(".update_item.list_category").each(function () {
const novelName = $(this).find("h3").text();
const novelCover = $(this).find("img").attr("src");
let novelUrl = $(this).find("h3 > a").attr("href");
novelUrl = novelUrl.replace(baseUrl + "novel/", "");
const novel = {
sourceId,
novelName,
novelCover,
novelUrl,
};
novels.push(novel);
});
return { novels, totalPages };
};
const parseNovelAndChapters = async (novelUrl) => {
const url = baseUrl + "novel/" + novelUrl;
const result = await fetch(url);
const body = await result.text();
$ = cheerio.load(body);
let novel = { sourceId, sourceName, url, novelUrl };
novel.novelName = $("h1").text().trim();
novel.novelCover = $(".info_image > img").attr("src");
novel.summary = $("#noidungm").text().trim();
novel.genre = $("div.truyen_if_wrap > ul > li:nth-child(3)")
.text()
.replace("GENRES: ", "")
.replace(/ - /g, ",")
.trim();
console.log(novel.genre);
novel.status =
$(" div.truyen_if_wrap > ul > li:nth-child(4) > a")
.text()
.replace("STATUS", "")
.trim() === "ONGOING"
? Status.ONGOING
: Status.COMPLETED;
novel.author = $("div.truyen_if_wrap > ul > li:nth-child(2)")
.text()
.replace("Author(s):", "")
.trim();
let novelChapters = [];
$(".chapter-list")
.find(".row")
.each(function () {
const releaseDate = $(this).find("span:nth-child(2)").text();
const chapterName = $(this).find("span:nth-child(1) > a").text();
const chapterUrl = $(this).find("a").attr("href");
novelChapters.push({
chapterName,
chapterUrl,
releaseDate,
});
});
novel.chapters = novelChapters.reverse();
return novel;
};
const parseChapter = async (novelUrl, chapterUrl) => {
const url = chapterUrl;
const result = await fetch(url);
const body = await result.text();
$ = cheerio.load(body);
let chapterName = $(".name_chapter").text();
let chapterText = $("#vung_doc").html();
const chapter = {
sourceId,
novelUrl,
chapterUrl,
chapterName,
chapterText,
};
return chapter;
};
const searchNovels = async (searchTerm) => {
const url = baseUrl + "search_novels/" + searchTerm;
const result = await fetch(url);
const body = await result.text();
$ = cheerio.load(body);
let novels = [];
$(".update_item.list_category").each(function () {
const novelName = $(this).find("h3").text();
const novelCover = $(this).find("img").attr("src");
let novelUrl = $(this).find("h3 > a").attr("href");
novelUrl = novelUrl.replace(baseUrl + "novel/", "");
const novel = {
sourceId,
novelName,
novelCover,
novelUrl,
};
novels.push(novel);
});
return novels;
};
const NovelOnlineFullScraper = {
popularNovels,
parseNovelAndChapters,
parseChapter,
searchNovels,
};
export default NovelOnlineFullScraper;
+22 -3
View File
@@ -11,9 +11,16 @@ const popularNovels = async (page) => {
let url = `${baseUrl}list/all/all-onclick-${page - 1}.html`;
const totalPages = 281;
const result = await fetch(url);
const headers = new Headers({
"user-agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36",
});
const result = await fetch(url, { method: "GET", headers });
const body = await result.text();
console.log(body);
$ = cheerio.load(body);
let novels = [];
@@ -40,7 +47,12 @@ const popularNovels = async (page) => {
const parseNovelAndChapters = async (novelUrl) => {
const url = `${baseUrl}novel/${novelUrl}`;
const result = await fetch(url);
const headers = new Headers({
"user-agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36",
});
const result = await fetch(url, { method: "GET", headers });
const body = await result.text();
$ = cheerio.load(body);
@@ -104,7 +116,12 @@ const parseNovelAndChapters = async (novelUrl) => {
const parseChapter = async (novelUrl, chapterUrl) => {
const url = `${baseUrl}novel/${chapterUrl}.html`;
const result = await fetch(url);
const headers = new Headers({
"user-agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36",
});
const result = await fetch(url, { method: "GET", headers });
const body = await result.text();
$ = cheerio.load(body);
@@ -131,6 +148,8 @@ const searchNovels = async (searchTerm) => {
"Content-Type": "application/x-www-form-urlencoded",
Referer: "https://www.readwn.com/search.html",
Origin: "https://www.readwn.com",
"user-agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36",
},
method: "POST",
body: QueryString.stringify({
+5 -112
View File
@@ -1,5 +1,6 @@
import cheerio from "react-native-cheerio";
import { Status } from "../helpers/constants";
import NovelUpdatesScraper from "./novelupdates";
const sourceId = 62;
@@ -118,118 +119,10 @@ const parseNovelAndChapters = async (novelUrl) => {
};
const parseChapter = async (novelUrl, chapterUrl) => {
const url = chapterUrl;
let result, body;
let chapterName = "";
try {
result = await fetch(url, {
method: "GET",
headers: headers,
});
body = await result.text();
$ = cheerio.load(body);
let isWuxiaWorld = result.url.toLowerCase().includes("wuxiaworld");
let isBlogspot = result.url.toLowerCase().includes("blogspot");
let isTumblr = result.url.toLowerCase().includes("tumblr");
/**
* Checks if its a wwordpress site
*/
let isWordPress =
$('meta[name="generator"]').attr("content") || $("footer").text();
if (isWordPress) {
isWordPress =
isWordPress.toLowerCase().includes("wordpress") ||
isWordPress.includes("Site Kit by Google") ||
$(".powered-by").text().toLowerCase().includes("wordpress") ||
$(".entry-content").html() ||
$("section.content").html()
? true
: false;
}
let isRainOfSnow = result.url.toLowerCase().includes("rainofsnow");
let isRoyalRoad = result.url.toLowerCase().includes("royalroad");
let isWebNovel = result.url.toLowerCase().includes("webnovel");
let isHostedNovel = result.url.toLowerCase().includes("hostednovel");
let isScribbleHub = result.url.toLowerCase().includes("scribblehub");
if (isWuxiaWorld) {
chapterText = $("#chapter-content").html();
} else if (isRainOfSnow) {
chapterText = $("div.content").html();
} else if (isTumblr) {
chapterText = $(".post").html();
} else if (isBlogspot) {
chapterText = $(".entry-content").html();
} else if (isHostedNovel) {
chapterText = $(".chapter").html();
} else if (isScribbleHub) {
chapterText = $("div.chp_raw").html();
} else if (isRoyalRoad) {
chapterText = $("div.chapter-content").html();
} else if (isWordPress) {
/**
* Remove wordpress bloat tags
*/
const bloatClasses = [
".c-ads",
"#madara-comments",
"#comments",
".sharedaddy",
".wp-dark-mode-switcher",
".wp-next-post-navi",
".wp-block-buttons",
".wp-block-columns",
".post-cats",
".sidebar",
".author-avatar",
];
bloatClasses.map((tag) => $(tag).remove());
chapterText =
$(".entry-content").html() ||
$(".single_post").html() ||
$(".post-entry").html() ||
$("article.post").html() ||
$(".content").html() ||
$("#content").html();
} else if (isWebNovel) {
chapterText = $(".cha-words").html();
if (!chapterText) {
chapterText = $("._content").html();
}
} else {
/**
* Remove unnecessary tags
*/
const tags = ["nav", "header", "footer", ".hidden"];
tags.map((tag) => $(tag).remove());
chapterText = $("body").html();
}
if (!chapterText) {
chapterText =
"Chapter not available.\n\nReport if it's available in webview.";
}
} catch (error) {
chapterText = `Chapter not available (Error: ${error.message}).\n\nReport if it's available in webview.`;
}
const chapterName = "";
const chapterText = (
await NovelUpdatesScraper.parseChapter(novelUrl, chapterUrl)
).chapterText;
const chapter = {
sourceId,
+1 -1
View File
@@ -3,7 +3,7 @@ export const cleanHtml = (html) => {
text = text
.replace(
/style="((?:color|font-family|line-height|font-size|text-align).*?)"/gi,
/style="((?:font-family|line-height|font-size|text-align).*?)"/gi,
""
)
.replace(/class=".*?"/gi, "")
+4
View File
@@ -71,6 +71,8 @@ import ReadwnScraper from "./en/readwn";
import RanobeHubScraper from "./ru/ranobehub";
import ArNovelScraper from "./ar/arnovel";
import DivineDaoLibraryScraper from "./en/divinedaolibrary";
import NovelOnlineFullScraper from "./en/novelonlinefull";
import LightNovelUpdatesScraper from "./en/lightnovelupdates";
export const getSource = (sourceId) => {
const scrapers = {
@@ -143,6 +145,8 @@ export const getSource = (sourceId) => {
68: ReadwnScraper,
69: RanobeHubScraper,
70: DivineDaoLibraryScraper,
71: NovelOnlineFullScraper,
72: LightNovelUpdatesScraper,
};
return scrapers[sourceId];
+14
View File
@@ -481,5 +481,19 @@
"sourceName": "Divine Dao Library",
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/divinedaolibrary/icon.png?raw=true",
"lang": "English"
},
{
"sourceId": 71,
"url": "https://novelonlinefull.com/",
"sourceName": "NovelOnlineFull.com",
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/novelonlinefull/icon.png?raw=true",
"lang": "English"
},
{
"sourceId": 72,
"url": "https://www.lightnovelupdates.com/",
"sourceName": "LightNovelUpdates",
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/lightnovelupdates/icon.png?raw=true",
"lang": "English"
}
]