Compare commits

..

23 Commits

Author SHA1 Message Date
Rajarshee Chatterjee 3425892a16 Release v1.1.7
Lint / ESLint (push) Has been cancelled
2021-11-01 13:43:21 +05:30
Rajarshee Chatterjee 97890c1596 Add NovelForest (Closes LNReader/lnreader-sources#102) 2021-11-01 08:41:28 +05:30
Rajarshee Chatterjee c97e1b8e23 Fix VipNovel search 2021-11-01 07:43:20 +05:30
Rajarshee Chatterjee c44537a1c0 Fix default sort order 2021-11-01 07:43:10 +05:30
Rajarshee Chatterjee fd8fb367bd Allow setting default sort order for chapters (Closes #100) 2021-10-31 15:17:16 +05:30
Rajarshee Chatterjee a3d8aa5bcd Fix RanobeHub images (Closes LNReader/lnreader-sources#107) 2021-10-31 14:47:18 +05:30
Rajarshee Chatterjee e7a1f8210f Fix Madara sources 2021-10-30 12:23:53 +05:30
Rajarshee Chatterjee 9423054b10 Add OnlyMTL 2021-10-30 11:07:41 +05:30
Rajarshee Chatterjee d11348d9a3 Add parallel searching in global search and migration 2021-10-30 09:59:25 +05:30
Rajarshee Chatterjee 8ec90ae8f7 Fix NitroScans novelName 2021-10-29 16:01:14 +05:30
Rajarshee Chatterjee 5fde669106 Add NitroScans 2021-10-29 15:58:12 +05:30
Rajarshee Chatterjee c6cdf1da0e Add MTLCorner (Closes LNReader/lnreader-sources#63) 2021-10-29 14:52:39 +05:30
Rajarshee Chatterjee 8cb0a5eb73 Fix RanobeHub search (Closes LNReader/lnreader-sources#101) 2021-10-29 13:57:59 +05:30
Rajarshee Chatterjee 09e15607d5 Fix useEffect cleanup in global search and migration 2021-10-29 13:57:05 +05:30
Rajarshee Chatterjee a9d399ea6d Fix crash when genre is an empty string 2021-10-29 13:54:48 +05:30
Rajarshee Chatterjee b9eddc2cc2 Fix Yushubo (Closes LNReader/lnreader-sources#100) 2021-10-29 13:47:46 +05:30
Rajarshee Chatterjee bb68b2f407 Fix crash when chapter release date is empty string 2021-10-28 17:53:52 +05:30
Rajarshee Chatterjee 5e0d19a9c9 Fix crash when chapter release date is empty string 2021-10-28 17:26:28 +05:30
Rajarshee Chatterjee 0aa78e56c8 Fix duplicate entries in library 2021-10-28 17:23:51 +05:30
Rajarshee Chatterjee 078e311995 Fix RewayatAr (Closes LNReader/lnreader-sources#96) 2021-10-28 17:19:52 +05:30
Rajarshee Chatterjee 9a22eba72c Fix next page not loading in browse 2021-10-28 08:34:14 +05:30
Rajarshee Chatterjee 2b0032a4e4 Fix ArNovel (Closes LNReader/lnreader-sources#95) 2021-10-28 08:19:49 +05:30
Rajarshee Chatterjee 708225c855 Add Chireads (Closes LNReader/lnreader-sources#98) 2021-10-28 07:33:03 +05:30
36 changed files with 1209 additions and 292 deletions
+1 -1
View File
@@ -136,7 +136,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.1.6"
versionName "1.1.7"
}
splits {
abi {
+1 -7
View File
@@ -72,13 +72,7 @@ const InfoItem = ({title, icon, theme}) => (
titleStyle={{color: theme.textColorSecondary, fontSize: 14}}
titleNumberOfLines={5}
left={() =>
icon && (
<PaperList.Icon
color={theme.textColorSecondary}
icon={icon}
style={{marginVertical: 0}}
/>
)
icon && <PaperList.Icon color={theme.textColorSecondary} icon={icon} />
}
/>
);
+1 -1
View File
@@ -29,7 +29,7 @@ const NovelList = ({
}
};
const keyExtractor = useCallback(item => item.novelUrl, []);
const keyExtractor = useCallback(item => item.sourceId + item.novelUrl, []);
return (
<FlatList
+6 -5
View File
@@ -24,7 +24,7 @@ export const useGithubUpdateChecker = () => {
};
const isNewVersion = versionTag => {
let currentVersion = 'v1.1.6';
let currentVersion = 'v1.1.7';
const regex = /[^\\d.]/;
let newVersion = versionTag.replace(regex, '');
@@ -34,10 +34,11 @@ export const useGithubUpdateChecker = () => {
};
useEffect(() => {
checkForRelease().catch(e => {
showToast(`Could not connect to github:\n${`${e}`.split(':')[1].trim()}`);
// console.error(e);
});
checkForRelease();
// .catch(e => {
// showToast(`Could not connect to github:\n${`${e}`.split(':')[1].trim()}`);
// // console.error(e);
// });
}, []);
if (!checking) {
+1 -1
View File
@@ -113,7 +113,7 @@ export const getNovelAction =
* Get novel from db.
*/
novel = await getNovel(sourceId, novelUrl);
novel.chapters = await getChapters(novel.novelId);
novel.chapters = await getChapters(novel.novelId, sort, filter);
dispatch({
type: GET_NOVEL,
+1
View File
@@ -99,6 +99,7 @@ const initialState = {
*/
hideBackdrop: false,
defaultChapterSort: null,
/**
* Reader settings
@@ -89,6 +89,11 @@ const BrowseSettings = ({navigation}) => {
}
/>
</Pressable>
<List.InfoItem
title="Searching a large number of sources may freeze the app till searching is finished."
icon="information-outline"
theme={theme}
/>
<List.Divider theme={theme} />
<List.SubHeader theme={theme}>Browse</List.SubHeader>
<Pressable
+55 -77
View File
@@ -1,9 +1,5 @@
import React, {useEffect, useRef, useState} from 'react';
import {
/* StyleSheet, */ View,
FlatList,
ActivityIndicator,
} from 'react-native';
import {FlatList} from 'react-native';
import {ProgressBar} from 'react-native-paper';
import {useSelector} from 'react-redux';
@@ -14,7 +10,6 @@ import EmptyView from '../../../components/EmptyView';
import {ScreenContainer} from '../../../components/Common';
import GlobalSearchSourceItem from './GlobalSearchSourceItem';
import {showToast} from '../../../hooks/showToast';
import {useLibrary, useSettings, useTheme} from '../../../hooks/reduxHooks';
import {getSource} from '../../../sources/sources';
@@ -38,7 +33,6 @@ const GlobalSearch = ({route, navigation}) => {
);
const {searchAllSources = false} = useSettings();
const [loading, setLoading] = useState(false);
const [searchText, setSearchText] = useState(novelName);
const [searchResults, setSearchResults] = useState([]);
const [progress, setProgress] = useState(0);
@@ -51,58 +45,64 @@ const GlobalSearch = ({route, navigation}) => {
if (novelName) {
onSubmitEditing();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
return () => {
isMounted.current = false;
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const clearSearchbar = () => setSearchText('');
const onChangeText = text => setSearchText(text);
const onSubmitEditing = async () => {
setSearchResults([]);
setProgress(0);
let globalSearchSources = searchAllSources ? sources : pinnedSources;
for (let i = 0; i < globalSearchSources.length; i++) {
if (isMounted.current === false) {
break;
setSearchResults(
globalSearchSources.map(item => ({
sourceId: item.sourceId,
sourceName: item.sourceName,
lang: item.lang,
loading: true,
novels: [],
error: null,
})),
);
globalSearchSources.map(async item => {
if (isMounted.current === true) {
try {
const source = getSource(item.sourceId);
const data = await source.searchNovels(encodeURI(searchText));
setSearchResults(prevState =>
prevState.map(sourceItem =>
sourceItem.sourceId === item.sourceId
? {...sourceItem, novels: data, loading: false}
: {...sourceItem},
),
);
} catch (e) {
setSearchResults(prevState =>
prevState.map(sourceItem =>
sourceItem.sourceId === item.sourceId
? {
...sourceItem,
loading: false,
error: e.message,
}
: sourceItem,
),
);
}
setProgress(before => before + 1 / globalSearchSources.length);
}
try {
setLoading(true);
const source = getSource(globalSearchSources[i].sourceId);
const data = await source.searchNovels(encodeURI(searchText));
setSearchResults(before => [
...before,
{
sourceId: globalSearchSources[i].sourceId,
sourceName: globalSearchSources[i].sourceName,
lang: globalSearchSources[i].lang,
novels: data,
},
]);
setLoading(false);
} catch (error) {
showToast(globalSearchSources[i].sourceName + ': ' + error.message);
setSearchResults(before => [
...before,
{
sourceId: globalSearchSources[i].sourceId,
sourceName: globalSearchSources[i].sourceName,
lang: globalSearchSources[i].lang,
novels: [],
},
]);
setLoading(false);
}
setProgress(before => before + 1 / globalSearchSources.length);
}
});
};
const renderItem = ({item}) => (
@@ -130,42 +130,22 @@ const GlobalSearch = ({route, navigation}) => {
<ProgressBar color={theme.colorAccent} progress={progress} />
)}
<FlatList
contentContainerStyle={{
flexGrow: 1,
padding: 4,
}}
contentContainerStyle={{flexGrow: 1, padding: 4}}
data={searchResults}
keyExtractor={item => item.sourceId.toString()}
renderItem={renderItem}
extraData={pinned}
ListEmptyComponent={
<>
{!loading && (
<EmptyView
icon="__φ(..)"
description={`Search a novel in ${
searchAllSources
? 'all sources'
: pinned.length === 0
? 'pinned sources\n(No sources pinned)'
: 'pinned sources'
}`}
/>
)}
</>
}
ListFooterComponent={
loading && (
<View
style={{
flex: 1,
justifyContent: 'center',
padding: 16,
}}
>
<ActivityIndicator size="large" color={theme.colorAccent} />
</View>
)
<EmptyView
icon="__φ(..)"
description={`Search a novel in ${
searchAllSources
? 'all sources'
: pinned.length === 0
? 'pinned sources\n(No sources pinned)'
: 'pinned sources'
}`}
/>
}
/>
</ScreenContainer>
@@ -173,5 +153,3 @@ const GlobalSearch = ({route, navigation}) => {
};
export default GlobalSearch;
// const styles = StyleSheet.create({});
@@ -1,5 +1,6 @@
import React from 'react';
import {StyleSheet, View, Text, Image, Pressable} from 'react-native';
import {defaultCoverUri} from '../../../sources/helpers/constants';
const GlobalSearchNovelCover = ({
novel,
@@ -10,32 +11,28 @@ const GlobalSearchNovelCover = ({
}) => {
const {novelName, novelCover} = novel;
const uri =
novelCover && !novelCover.startsWith('/') ? novelCover : defaultCoverUri;
return (
<View style={{flex: 1, borderRadius: 6, overflow: 'hidden'}}>
<View style={styles.container}>
<Pressable
android_ripple={{color: theme.rippleColor}}
style={styles.novelContainer}
style={styles.pressable}
onPress={onPress}
onLongPress={onLongPress}
>
<>
<Image
source={{
uri:
novelCover && !novelCover.startsWith('/')
? novelCover
: 'https://github.com/LNReader/lnreader-sources/blob/main/src/coverNotAvailable.jpg?raw=true',
}}
style={[styles.novelCover, inLibrary && {opacity: 0.5}]}
progressiveRenderingEnabled={true}
/>
<Text
numberOfLines={2}
style={[styles.title, {color: theme.textColorPrimary}]}
>
{novelName}
</Text>
</>
<Image
source={{uri}}
style={[styles.novelCover, inLibrary && {opacity: 0.5}]}
progressiveRenderingEnabled={true}
/>
<Text
numberOfLines={2}
style={[styles.title, {color: theme.textColorPrimary}]}
>
{novelName}
</Text>
</Pressable>
</View>
);
@@ -44,10 +41,16 @@ const GlobalSearchNovelCover = ({
export default GlobalSearchNovelCover;
const styles = StyleSheet.create({
novelContainer: {
container: {
flex: 1,
borderRadius: 6,
overflow: 'hidden',
},
pressable: {
paddingHorizontal: 5,
paddingVertical: 4,
borderRadius: 4,
flex: 1,
},
novelCover: {
height: 150,
@@ -29,7 +29,7 @@ const GlobalSearchNovelList = ({data, theme, library, navigation}) => {
contentContainerStyle={styles.listContainer}
horizontal={true}
data={data}
keyExtractor={item => item.novelUrl}
keyExtractor={item => item.sourceId + item.novelUrl}
renderItem={renderItem}
ListEmptyComponent={ListEmptyComponent}
/>
@@ -1,10 +1,14 @@
import React from 'react';
import {StyleSheet, View, Text} from 'react-native';
import {StyleSheet, View, Text, ActivityIndicator} from 'react-native';
import GlobalSearchNovelList from './GlobalSearchNovelList';
const GlobalSearchSourceItem = ({source, library, theme, navigation}) => {
const {sourceName, lang, novels} = source;
const {sourceName, lang, loading, novels, error} = source;
const colorError = {
color: theme.statusBar === 'dark-content' ? '#B3261E' : '#F2B8B5',
};
return (
<>
@@ -14,12 +18,21 @@ const GlobalSearchSourceItem = ({source, library, theme, navigation}) => {
{lang}
</Text>
</View>
<GlobalSearchNovelList
data={novels}
theme={theme}
library={library}
navigation={navigation}
/>
{error ? (
<Text style={[styles.error, colorError]}>{error}</Text>
) : loading ? (
<ActivityIndicator
color={theme.colorAccent}
style={{marginVertical: 16}}
/>
) : (
<GlobalSearchNovelList
data={novels}
theme={theme}
library={library}
navigation={navigation}
/>
)}
</>
);
};
@@ -31,4 +44,8 @@ const styles = StyleSheet.create({
padding: 8,
paddingVertical: 16,
},
error: {
paddingHorizontal: 8,
paddingVertical: 16,
},
});
@@ -48,7 +48,7 @@ const MigrationNovelList = ({data, theme, library, navigation}) => {
contentContainerStyle={styles.flatListCont}
horizontal={true}
data={data}
keyExtractor={item => item.novelUrl}
keyExtractor={item => item.sourceId + item.novelUrl}
renderItem={renderItem}
ListEmptyComponent={
<Text
+79 -76
View File
@@ -1,10 +1,10 @@
import React, {useEffect, useState} from 'react';
import {
/* StyleSheet, */
View,
Text,
FlatList,
ActivityIndicator,
StyleSheet,
} from 'react-native';
import {ProgressBar} from 'react-native-paper';
import {useLibrary, useSettings, useTheme} from '../../../hooks/reduxHooks';
@@ -14,7 +14,6 @@ import EmptyView from '../../../components/EmptyView';
import MigrationNovelList from './MigrationNovelList';
import {Appbar} from '../../../components/Appbar';
import {showToast} from '../../../hooks/showToast';
import {ScreenContainer} from '../../../components/Common';
import {getSource} from '../../../sources/sources';
@@ -25,7 +24,6 @@ const MigrationNovels = ({navigation, route}) => {
const isMounted = React.useRef(true);
const [progress, setProgress] = useState(0);
const [loading, setLoading] = useState(false);
const [searchResults, setSearchResults] = useState('');
const {searchAllSources = false} = useSettings();
let {
@@ -44,110 +42,110 @@ const MigrationNovels = ({navigation, route}) => {
const getSearchResults = async () => {
let migrationSources = searchAllSources ? sources : pinnedSources;
for (let i = 0; i < migrationSources.length; i++) {
if (isMounted.current === false) {
break;
setSearchResults(
migrationSources.map(item => ({
sourceId: item.sourceId,
sourceName: item.sourceName,
lang: item.lang,
loading: true,
novels: [],
error: null,
})),
);
migrationSources.map(async item => {
if (isMounted.current === true) {
try {
const source = getSource(item.sourceId);
const data = await source.searchNovels(novelName);
setSearchResults(prevState =>
prevState.map(sourceItem =>
sourceItem.sourceId === item.sourceId
? {...sourceItem, novels: data, loading: false}
: {...sourceItem},
),
);
} catch (e) {
setSearchResults(prevState =>
prevState.map(sourceItem =>
sourceItem.sourceId === item.sourceId
? {
...sourceItem,
loading: false,
error: e.message,
}
: sourceItem,
),
);
}
setProgress(before => before + 1 / migrationSources.length);
}
try {
setLoading(true);
const source = getSource(migrationSources[i].sourceId);
const data = await source.searchNovels(novelName);
setSearchResults(before => [
...before,
{
sourceId: migrationSources[i].sourceId,
sourceName: migrationSources[i].sourceName,
sourceLanguage: migrationSources[i].lang,
novels: data,
},
]);
setLoading(false);
} catch (error) {
showToast(error.message);
setSearchResults(before => [
...before,
{
sourceId: migrationSources[i].sourceId,
sourceName: migrationSources[i].sourceName,
sourceLanguage: migrationSources[i].lang,
novels: [],
},
]);
setLoading(false);
}
setProgress(before => before + 1 / migrationSources.length);
}
});
};
useEffect(() => {
getSearchResults();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
return () => {
isMounted.current = false;
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const colorError = {
color: theme.statusBar === 'dark-content' ? '#B3261E' : '#F2B8B5',
};
const renderItem = ({item}) => (
<>
<View style={{padding: 8, paddingVertical: 16}}>
<Text style={{color: theme.textColorPrimary}}>{item.sourceName}</Text>
<Text style={{color: theme.textColorSecondary, fontSize: 12}}>
{item.sourceLanguage}
{item.lang}
</Text>
</View>
<MigrationNovelList
data={item.novels}
theme={theme}
library={library}
navigation={navigation}
/>
{item.error ? (
<Text style={[styles.error, colorError]}>{item.error}</Text>
) : item.loading ? (
<ActivityIndicator
color={theme.colorAccent}
style={{marginVertical: 16}}
/>
) : (
<MigrationNovelList
data={item.novels}
theme={theme}
library={library}
navigation={navigation}
/>
)}
</>
);
return (
<ScreenContainer theme={theme}>
<Appbar title={novelName} onBackAction={() => navigation.goBack()} />
<Appbar title={novelName} onBackAction={navigation.goBack} />
{progress > 0 && (
<ProgressBar color={theme.colorAccent} progress={progress} />
)}
<FlatList
contentContainerStyle={{
flexGrow: 1,
padding: 4,
}}
contentContainerStyle={{flexGrow: 1, padding: 4}}
data={searchResults}
keyExtractor={item => item.sourceId.toString()}
renderItem={renderItem}
extraData={pinned}
ListEmptyComponent={
<>
{!loading && (
<EmptyView
icon="__φ(..)"
description={`Search a novel in your pinned sources ${
pinned.length === 0 ? '(No sources pinned)' : ''
}`}
/>
)}
</>
}
ListFooterComponent={
loading && (
<View
style={{
flex: 1,
justifyContent: 'center',
padding: 16,
}}
>
<ActivityIndicator size="large" color={theme.colorAccent} />
</View>
)
<EmptyView
icon="__φ(..)"
description={`Search a novel in your pinned sources ${
pinned.length === 0 ? '(No sources pinned)' : ''
}`}
/>
}
/>
</ScreenContainer>
@@ -156,4 +154,9 @@ const MigrationNovels = ({navigation, route}) => {
export default MigrationNovels;
// const styles = StyleSheet.create({});
const styles = StyleSheet.create({
error: {
paddingHorizontal: 8,
paddingVertical: 16,
},
});
@@ -74,7 +74,7 @@ const LibrarySheet = ({
DESC: 'novels.novelId DESC',
},
{
label: 'Aplhabetically',
label: 'Alphabetically',
ASC: 'novels.novelName ASC',
DESC: 'novels.novelName DESC',
},
+2 -2
View File
@@ -23,14 +23,14 @@ const AboutScreen = ({navigation}) => {
<List.Section>
<List.Item
title="Version"
description="Stable 1.1.6 (26/10/21 09:00 PM)"
description="Stable 1.1.7 (01/11/21 10:00 AM)"
theme={theme}
/>
<List.Item
title="What's new"
onPress={() =>
Linking.openURL(
'https://github.com/LNReader/lnreader/releases/tag/v1.1.6',
'https://github.com/LNReader/lnreader/releases/tag/v1.1.7',
)
}
theme={theme}
+7 -4
View File
@@ -77,15 +77,18 @@ const Novel = ({route, navigation}) => {
const deleteDownloadsSnackbar = useModal();
const {
sort = 'ORDER BY chapterId ASC',
useFabForContinueReading = false,
defaultChapterSort = 'ORDER BY chapterId ASC',
} = useSettings();
const {
sort = defaultChapterSort,
filter = '',
showChapterTitles = false,
} = usePreferences(novel.novelId);
let {lastReadChapter, position} = useContinueReading(chapters, novel.novelId);
const {useFabForContinueReading = lastReadChapter && false} = useSettings();
useEffect(() => {
dispatch(
getNovelAction(followed, sourceId, novelUrl, novelId, sort, filter),
@@ -202,7 +205,7 @@ const Novel = ({route, navigation}) => {
}}
numberOfLines={1}
>
{chapter.releaseDate && '• '}
{chapter.releaseDate ? '• ' : null}
{'Progress ' + position[chapter.chapterId].percentage + '%'}
</Text>
);
+2 -2
View File
@@ -66,7 +66,7 @@ const ChapterItem = ({
marginTop: 5,
}}
>
{releaseDate && (
{releaseDate ? (
<Text
style={{
color: read
@@ -80,7 +80,7 @@ const ChapterItem = ({
>
{releaseDate}
</Text>
)}
) : null}
{showProgressPercentage(chapter)}
</View>
</View>
@@ -237,9 +237,9 @@ const NovelInfoHeader = ({
followed={novel.followed}
theme={theme}
/>
{novel.genre !== null && novel.genre !== '' && (
{novel.genre ? (
<NovelGenres theme={theme} genre={novel.genre} />
)}
) : null}
<ReadButton
novel={novel}
chapters={chapters}
@@ -252,16 +252,14 @@ const NovelInfoHeader = ({
onPress={() => novelBottomSheetRef.current.show()}
android_ripple={{color: theme.rippleColor}}
>
<>
<Text style={[{color: theme.textColorPrimary}, styles.chapters]}>
{`${chapters.length} chapters`}
</Text>
<IconButton
icon="filter-variant"
color={filter ? theme.filterColor : theme.textColorPrimary}
size={24}
/>
</>
<Text style={[{color: theme.textColorPrimary}, styles.chapters]}>
{`${chapters.length} chapters`}
</Text>
<IconButton
icon="filter-variant"
color={filter ? theme.filterColor : theme.textColorPrimary}
size={24}
/>
</Pressable>
</>
)}
@@ -37,7 +37,7 @@ const JumpToChapterModal = ({
});
hideModal();
} else {
setError(`Enter a valid chapter number (<= ${chapters.length})`);
setError(`Enter a valid chapter number (≤ ${chapters.length})`);
}
} else {
const chapter = chapters.find(chap =>
@@ -89,7 +89,7 @@ const JumpToChapterModal = ({
<TextInput
value={text}
placeholder={
mode ? 'Chapter Name' : `Chapter Number (<= ${chapters.length})`
mode ? 'Chapter Name' : `Chapter Number (≤ ${chapters.length})`
}
onChangeText={onChangeText}
onSubmitEditing={onSubmit}
@@ -14,6 +14,7 @@ import {useSettings, useTheme} from '../../hooks/reduxHooks';
import {setAppSettings} from '../../redux/settings/settings.actions';
import {SHOW_LAST_UPDATE_TIME} from '../../redux/updates/updates.types';
import {useModal} from '../../hooks/useModal';
import DefaultChapterSortModal from './components/DefaultChapterSortModal';
const GenralSettings = ({navigation}) => {
const theme = useTheme();
@@ -27,6 +28,7 @@ const GenralSettings = ({navigation}) => {
updateLibraryOnLaunch = false,
downloadNewChapters = false,
onlyUpdateOngoingNovels = false,
defaultChapterSort = 'ORDER BY chapterId ASC',
} = useSettings();
const {showLastUpdateTime = true} = useSelector(
@@ -54,6 +56,8 @@ const GenralSettings = ({navigation}) => {
*/
const gridSizeModalRef = useModal();
const defaultChapterSortModal = useModal();
return (
<ScreenContainer theme={theme}>
<Appbar title="General" onBackAction={navigation.goBack} />
@@ -85,6 +89,18 @@ const GenralSettings = ({navigation}) => {
theme={theme}
/>
<List.Divider theme={theme} />
<List.SubHeader theme={theme}>Novel</List.SubHeader>
<List.Item
title="Default chapter sort"
description={`By source ${
defaultChapterSort === 'ORDER BY chapterId ASC'
? '(Ascending)'
: '(Descending)'
}`}
onPress={defaultChapterSortModal.showModal}
theme={theme}
/>
<List.Divider theme={theme} />
<List.SubHeader theme={theme}>Global update</List.SubHeader>
<SwitchSetting
label="Only update ongoing novels"
@@ -131,6 +147,13 @@ const GenralSettings = ({navigation}) => {
dispatch={dispatch}
theme={theme}
/>
<DefaultChapterSortModal
defaultChapterSort={defaultChapterSort}
displayModalVisible={defaultChapterSortModal.visible}
hideDisplayModal={defaultChapterSortModal.hideModal}
dispatch={dispatch}
theme={theme}
/>
<GridSizeModal
dispatch={dispatch}
novelsPerRow={novelsPerRow}
@@ -0,0 +1,61 @@
import React from 'react';
import {StyleSheet} from 'react-native';
import {Portal, Modal} from 'react-native-paper';
import {SortItem} from '../../../components/Checkbox/Checkbox';
import {setAppSettings} from '../../../redux/settings/settings.actions';
const DefaultChapterSortModal = ({
theme,
dispatch,
defaultChapterSort,
hideDisplayModal,
displayModalVisible,
}) => {
return (
<Portal>
<Modal
visible={displayModalVisible}
onDismiss={hideDisplayModal}
contentContainerStyle={[
styles.containerStyle,
{backgroundColor: theme.colorPrimaryDark},
]}
>
<SortItem
label="By source"
theme={theme}
status={
defaultChapterSort === 'ORDER BY chapterId ASC' ? 'asc' : 'desc'
}
onPress={() =>
defaultChapterSort === 'ORDER BY chapterId ASC'
? dispatch(
setAppSettings(
'defaultChapterSort',
'ORDER BY chapterId DESC',
),
)
: dispatch(
setAppSettings(
'defaultChapterSort',
'ORDER BY chapterId ASC',
),
)
}
/>
</Modal>
</Portal>
);
};
export default DefaultChapterSortModal;
const styles = StyleSheet.create({
containerStyle: {
paddingVertical: 20,
margin: 20,
borderRadius: 6,
},
});
+4 -3
View File
@@ -87,12 +87,13 @@ const SourceScreen = ({navigation, route}) => {
useEffect(() => {
getNovels();
return () => {
isMounted.current = false;
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [page]);
useEffect(() => {
return () => (isMounted.current = false);
}, []);
const getNextPage = () => {
if (!searchText) {
incrementPage();
+10 -23
View File
@@ -7,7 +7,7 @@ const sourceName = 'ArNovel';
const baseUrl = 'https://arnovel.me/';
const popularNovels = async page => {
const totalPages = 21;
const totalPages = 25;
const url = baseUrl + 'novels/page/' + page;
const result = await fetch(url);
@@ -20,11 +20,7 @@ const popularNovels = async page => {
loadedCheerio('.page-item-detail').each(function () {
const novelName = loadedCheerio(this).find('.h5 > a').text();
const novelCover = defaultCoverUri;
let novelUrl = loadedCheerio(this)
.find('.h5 > a')
.attr('href')
.split('/')[4];
const novelUrl = loadedCheerio(this).find('.h5 > a').attr('href');
const novel = {
sourceId,
@@ -40,7 +36,7 @@ const popularNovels = async page => {
};
const parseNovelAndChapters = async novelUrl => {
const url = `${baseUrl}novel/${novelUrl}`;
const url = novelUrl;
const result = await fetch(url);
const body = await result.text();
@@ -83,20 +79,15 @@ const parseNovelAndChapters = async novelUrl => {
let novelChapters = [];
const data = await fetch(`${url}/ajax/chapters/`, {method: 'POST'});
const text = await data.text();
const data = await fetch(`${url}ajax/chapters/`, {method: 'POST'});
const chapterListRaw = await data.text();
loadedCheerio = cheerio.load(text);
loadedCheerio = cheerio.load(chapterListRaw);
loadedCheerio('.wp-manga-chapter').each(function () {
const chapterName = loadedCheerio(this).find('a').text().trim();
const releaseDate = loadedCheerio(this).find('span').text().trim();
let chapterUrl = loadedCheerio(this).find('a').attr('href').split('/');
chapterUrl[6]
? (chapterUrl = chapterUrl[5] + '/' + chapterUrl[6])
: (chapterUrl = chapterUrl[5]);
const releaseDate = null;
const chapterUrl = loadedCheerio(this).find('a').attr('href');
novelChapters.push({chapterName, releaseDate, chapterUrl});
});
@@ -107,7 +98,7 @@ const parseNovelAndChapters = async novelUrl => {
};
const parseChapter = async (novelUrl, chapterUrl) => {
const url = `${baseUrl}novel/${novelUrl}/${chapterUrl}/`;
const url = chapterUrl;
const result = await fetch(url);
const body = await result.text();
@@ -141,11 +132,7 @@ const searchNovels = async searchTerm => {
loadedCheerio('.c-tabs-item__content').each(function () {
const novelName = loadedCheerio(this).find('.h4 > a').text();
const novelCover = defaultCoverUri;
let novelUrl = loadedCheerio(this)
.find('.h4 > a')
.attr('href')
.split('/')[4];
const novelUrl = loadedCheerio(this).find('.h4 > a').attr('href');
const novel = {
sourceId,
+141
View File
@@ -0,0 +1,141 @@
import cheerio from 'react-native-cheerio';
import {Status} from '../helpers/constants';
const sourceId = 54;
const sourceName = 'RewayatAr';
const baseUrl = 'https://rewayat-ar.site/';
const popularNovels = async page => {
const totalPages = 2;
const url = baseUrl + 'novels/topview/page/' + page;
const result = await fetch(url);
const body = await result.text();
const loadedCheerio = cheerio.load(body);
let novels = [];
loadedCheerio('.update_item.list_category').each(function () {
const novelName = loadedCheerio(this).find('h3 > a').text();
const novelCover = loadedCheerio(this).find('img').attr('src');
let novelUrl = loadedCheerio(this).find('h3 > a').attr('href');
const novel = {
sourceId,
novelName,
novelCover,
novelUrl,
};
novels.push(novel);
});
return {totalPages, novels};
};
const parseNovelAndChapters = async novelUrl => {
const url = novelUrl;
const result = await fetch(url);
const body = await result.text();
let loadedCheerio = cheerio.load(body);
let novel = {
sourceId,
sourceName,
url,
novelUrl,
novelName: '',
novelCover: '',
author: '',
status: Status.UNKNOWN,
genre: '',
summary: '',
chapters: [],
};
novel.novelName = loadedCheerio('.truyen_info_right h1').text().trim();
novel.novelCover = loadedCheerio('.info_image img').attr('src');
novel.summary = loadedCheerio('#noidungm').text()?.trim();
novel.author = loadedCheerio(
'#main_body > div.cotgiua > div.truyen_info_wrapper > div.truyen_info > div.entry-header > div.truyen_if_wrap > ul > li:nth-child(2) > a',
)
.text()
?.trim();
let novelChapters = [];
loadedCheerio('.chapter-list div.row').each(function () {
const chapterName = loadedCheerio(this).find('a').text().trim();
const releaseDate = loadedCheerio(this)
.find('span:nth-child(2)')
.text()
.trim();
const chapterUrl = loadedCheerio(this).find('a').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);
const body = await result.text();
let loadedCheerio = cheerio.load(body);
const chapterName = loadedCheerio('h1.name_chapter').text();
const chapterText = loadedCheerio('#vung_doc').html();
const chapter = {
sourceId,
novelUrl,
chapterUrl,
chapterName,
chapterText,
};
return chapter;
};
const searchNovels = async searchTerm => {
const url = `${baseUrl}search/${searchTerm}`;
const result = await fetch(url);
const body = await result.text();
let loadedCheerio = cheerio.load(body);
let novels = [];
loadedCheerio('.update_item.list_category').each(function () {
const novelName = loadedCheerio(this).find('h3 > a').text();
const novelCover = loadedCheerio(this).find('img').attr('src');
let novelUrl = loadedCheerio(this).find('h3 > a').attr('href');
const novel = {sourceId, novelName, novelCover, novelUrl};
novels.push(novel);
});
return novels;
};
const RewayatArScraper = {
popularNovels,
parseNovelAndChapters,
parseChapter,
searchNovels,
};
export default RewayatArScraper;
+3 -3
View File
@@ -89,7 +89,7 @@ const parseNovelAndChapters = async novelUrl => {
let chapters = [];
loadedCheerio('.bookshelf-list #chapter').each(function () {
loadedCheerio('.bookshelf-list #chapter a').each(function () {
let chapterUrl = loadedCheerio(this).attr('href').substring(1);
let chapterName = loadedCheerio(this).text();
let releaseDate = null;
@@ -134,11 +134,11 @@ const parseChapter = async (novelUrl, chapterUrl) => {
};
const searchNovels = async searchTerm => {
const searchUrl = `${baseUrl}/search.html`;
const searchUrl = `${baseUrl}/so.html`;
const result = await fetch(searchUrl, {
method: 'post',
body: JSON.stringify({keyword: searchTerm}),
body: JSON.stringify({ss: searchTerm}),
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",
+161
View File
@@ -0,0 +1,161 @@
import cheerio from 'react-native-cheerio';
import {defaultCoverUri, Status} from '../helpers/constants';
const sourceId = 89;
const sourceName = 'IndoWebNovel';
const baseUrl = 'https://mtlcorner.com/';
const popularNovels = async page => {
const totalPages = 1;
const url = `${baseUrl}series/`;
const result = await fetch(url);
const body = await result.text();
const loadedCheerio = cheerio.load(body);
let novels = [];
loadedCheerio('.wp-block-latest-posts__list li').each(function () {
const novelName = loadedCheerio(this).find('a').text();
const novelCover = defaultCoverUri;
const novelUrl = loadedCheerio(this).find('a').attr('href');
const novel = {sourceId, novelName, novelCover, novelUrl};
novels.push(novel);
});
return {totalPages, novels};
};
const parseNovelAndChapters = async novelUrl => {
const url = novelUrl;
const result = await fetch(url);
const body = await result.text();
let loadedCheerio = cheerio.load(body);
let novel = {
sourceId,
sourceName,
url,
novelUrl,
novelName: '',
novelCover: '',
author: '',
status: '',
genre: '',
summary: '',
chapters: [],
};
const novelName = loadedCheerio('.entry-title').text().trim();
novel.novelName = novelName;
novel.novelCover = loadedCheerio('figure > img').attr('src');
novel.summary = loadedCheerio('figcaption').text().trim();
novel.status = Status.UNKNOWN;
novel.author = loadedCheerio(
' li.post-author.meta-wrapper > span.meta-text > a',
)
.text()
?.trim();
let chapters = [];
loadedCheerio('.lcp_catlist li').each(function () {
let chapterNameSuffix = loadedCheerio(this)
.find('a')
.text()
?.trim()
.match(/([a-zA-Z\s?]+)(\d+)-(\d+)/);
if (chapterNameSuffix) {
let chaptersTemp = [];
let chapterUrlSuffix = loadedCheerio(this).find('a').attr('href');
let i = chapterNameSuffix[2];
while (i <= chapterNameSuffix[3]) {
const chapterName = `${chapterNameSuffix[1].replace(
novelName,
'',
)} ${i}`;
const chapterUrl = `${chapterUrlSuffix}${i - chapterNameSuffix[2] + 1}`;
const releaseDate = null;
chaptersTemp.push({chapterName, releaseDate, chapterUrl});
i++;
}
chapters.push(...chaptersTemp.reverse());
}
});
novel.chapters = chapters.reverse();
return novel;
};
const parseChapter = async (novelUrl, chapterUrl) => {
const url = chapterUrl;
const result = await fetch(url);
const body = await result.text();
let loadedCheerio = cheerio.load(body);
const chapterName = '';
const chapterText = loadedCheerio('.entry-content').html();
const chapter = {
sourceId,
novelUrl,
chapterUrl,
chapterName,
chapterText,
};
return chapter;
};
const searchNovels = async searchTerm => {
const url = `${baseUrl}series/`;
const result = await fetch(url);
const body = await result.text();
const loadedCheerio = cheerio.load(body);
let novels = [];
loadedCheerio('.wp-block-latest-posts__list li').each(function () {
const novelName = loadedCheerio(this).find('a').text();
const novelCover = defaultCoverUri;
const novelUrl = loadedCheerio(this).find('a').attr('href');
const novel = {sourceId, novelName, novelCover, novelUrl};
if (novelName.toLowerCase().includes(searchTerm.toLowerCase())) {
novels.push(novel);
}
});
return novels;
};
const MTLCornerScraper = {
popularNovels,
parseNovelAndChapters,
parseChapter,
searchNovels,
};
export default MTLCornerScraper;
+166
View File
@@ -0,0 +1,166 @@
import cheerio from 'react-native-cheerio';
import {Status} from '../helpers/constants';
const sourceId = 90;
const sourceName = 'NitroScans';
const baseUrl = 'https://nitroscans.com/';
const popularNovels = async page => {
const totalPages = 2;
const url = baseUrl + 'manga-genre/novel/page/' + page;
const result = await fetch(url);
const body = await result.text();
const loadedCheerio = cheerio.load(body);
let novels = [];
loadedCheerio('.page-item-detail').each(function () {
const novelName = loadedCheerio(this).find('h3 > a').text();
const novelCover = loadedCheerio(this).find('img').attr('data-src');
const novelUrl = loadedCheerio(this).find('h3 > a').attr('href');
const novel = {sourceId, novelName, novelCover, novelUrl};
novels.push(novel);
});
return {totalPages, novels};
};
const parseNovelAndChapters = async novelUrl => {
const url = novelUrl;
const result = await fetch(url);
const body = await result.text();
let loadedCheerio = cheerio.load(body);
let novel = {
sourceId,
sourceName,
url,
novelUrl,
novelName: '',
novelCover: '',
author: '',
status: Status.UNKNOWN,
genre: '',
summary: '',
chapters: [],
};
loadedCheerio('.manga-title-badges.custom.novel').remove();
novel.novelName = loadedCheerio('.post-title > h1').text().trim();
novel.novelCover = loadedCheerio('.summary_image')
.find('img')
.attr('data-src');
novel.summary = loadedCheerio('.summary__content').text()?.trim();
loadedCheerio('.post-content_item').each(function () {
const detailName = loadedCheerio(this)
.find('.summary-heading > h5')
.text()
.trim();
const detail = loadedCheerio(this).find('.summary-content').text().trim();
switch (detailName) {
case 'Genre(s)':
novel.genre = detail.replace(/[\t\n]/g, ',');
break;
case 'Author(s)':
novel.author = detail;
break;
case 'Status':
novel.status = detail.includes('OnGoing')
? Status.ONGOING
: Status.COMPLETED;
break;
}
});
let novelChapters = [];
const novelId = loadedCheerio('.rating-post-id').attr('value');
let formData = new FormData();
formData.append('action', 'manga_get_chapters');
formData.append('manga', novelId);
const data = await fetch(baseUrl + 'wp-admin/admin-ajax.php', {
method: 'POST',
body: formData,
});
const text = await data.text();
loadedCheerio = cheerio.load(text);
loadedCheerio('.wp-manga-chapter').each(function () {
const chapterName = loadedCheerio(this).find('a').text().trim();
const releaseDate = loadedCheerio(this).find('span').text().trim();
const chapterUrl = loadedCheerio(this).find('a').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);
const body = await result.text();
let loadedCheerio = cheerio.load(body);
const chapterName = loadedCheerio('h1#chapter-heading').text();
const chapterText = loadedCheerio('.reading-content').html();
const chapter = {
sourceId,
novelUrl,
chapterUrl,
chapterName,
chapterText,
};
return chapter;
};
const searchNovels = async searchTerm => {
const url = `${baseUrl}?s=${searchTerm}&post_type=wp-manga&genre%5B%5D=novel&op=&author=&artist=&release=&adult=`;
const result = await fetch(url);
const body = await result.text();
let loadedCheerio = cheerio.load(body);
let novels = [];
loadedCheerio('.c-tabs-item__content').each(function () {
const novelName = loadedCheerio(this).find('h3 > a').text();
const novelCover = loadedCheerio(this).find('img').attr('data-src');
const novelUrl = loadedCheerio(this).find('h3 > a').attr('href');
const novel = {sourceId, novelName, novelCover, novelUrl};
novels.push(novel);
});
return novels;
};
const NitroScansScraper = {
popularNovels,
parseNovelAndChapters,
parseChapter,
searchNovels,
};
export default NitroScansScraper;
+182
View File
@@ -0,0 +1,182 @@
import cheerio from 'react-native-cheerio';
const sourceId = 92;
const sourceName = 'NovelForest';
const baseUrl = 'https://novelforest.com/';
const popularNovels = async page => {
const totalPages = 25;
const url = `${baseUrl}popular?page=${page}`;
const result = await fetch(url);
const body = await result.text();
const loadedCheerio = cheerio.load(body);
let novels = [];
loadedCheerio('.book-item').each(function () {
const novelName = loadedCheerio(this).find('.title').text();
const novelCover =
'https:' + loadedCheerio(this).find('img').attr('data-src');
const novelUrl =
baseUrl + loadedCheerio(this).find('.title a').attr('href').substring(1);
const novel = {sourceId, novelName, novelCover, novelUrl};
novels.push(novel);
});
return {totalPages, novels};
};
const parseNovelAndChapters = async novelUrl => {
const url = novelUrl;
const result = await fetch(url);
const body = await result.text();
let loadedCheerio = cheerio.load(body);
let novel = {
sourceId,
sourceName,
url,
novelUrl,
novelName: '',
novelCover: '',
author: '',
status: '',
genre: '',
summary: '',
chapters: [],
};
novel.novelName = loadedCheerio('.name h1').text().trim();
novel.novelCover =
'https:' + loadedCheerio('.img-cover img').attr('data-src');
novel.summary = loadedCheerio('#info > p').text().trim();
novel.author = loadedCheerio(
'body > div.layout > div.main-container.book-details > div > div.row.no-gutters > div.col-lg-8 > div.book-info > div.detail > div.meta.box.mt-1.p-10 > p:nth-child(1) > a > span',
)
.text()
?.trim();
novel.status = loadedCheerio(
'body > div.layout > div.main-container.book-details > div > div.row.no-gutters > div.col-lg-8 > div.book-info > div.detail > div.meta.box.mt-1.p-10 > p:nth-child(2) > a > span',
)
.text()
?.trim();
novel.genre = loadedCheerio(
'body > div.layout > div.main-container.book-details > div > div.row.no-gutters > div.col-lg-8 > div.book-info > div.detail > div.meta.box.mt-1.p-10 > p:nth-child(3)',
)
.text()
?.replace('Genres :', '')
.replace(/[\s\n]+/g, ' ')
.trim();
let chapters = [];
const delay = ms => new Promise(res => setTimeout(res, ms));
const lastPage = loadedCheerio('label:contains("❯❯")').attr('id');
for (let i = 1; i <= lastPage; i++) {
const chaptersUrl =
novelUrl.replace(baseUrl, 'https://novelforest.com/api/novels/') +
'/chapters?page=' +
i;
const chaptersRequest = await fetch(chaptersUrl);
const chaptersJSON = await chaptersRequest.json();
loadedCheerio = cheerio.load(chaptersJSON.chapters);
loadedCheerio('li').each(function () {
const chapterName = loadedCheerio(this)
.find('.chapter-title')
.text()
.trim();
const releaseDate = loadedCheerio(this)
.find('.chapter-update')
.text()
.trim();
const chapterUrl =
baseUrl + loadedCheerio(this).find('a').attr('href').substring(1);
chapters.push({chapterName, releaseDate, chapterUrl});
delay(1000);
});
}
novel.chapters = chapters;
return novel;
};
const parseChapter = async (novelUrl, chapterUrl) => {
const url = chapterUrl;
const result = await fetch(url);
const body = await result.text();
let loadedCheerio = cheerio.load(body);
loadedCheerio('#listen-chapter').remove();
loadedCheerio('#google_translate_element').remove();
const chapterName = loadedCheerio('#chapter__content > h1').text();
const chapterText = loadedCheerio('.chapter__content').html();
const chapter = {
sourceId,
novelUrl,
chapterUrl,
chapterName,
chapterText,
};
return chapter;
};
const searchNovels = async searchTerm => {
const url = `${baseUrl}search?q=${searchTerm}`;
const result = await fetch(url);
const body = await result.text();
let loadedCheerio = cheerio.load(body);
let novels = [];
loadedCheerio('.book-item').each(function () {
const novelName = loadedCheerio(this).find('.title').text();
const novelCover =
'https:' + loadedCheerio(this).find('img').attr('data-src');
const novelUrl =
baseUrl + loadedCheerio(this).find('.title a').attr('href').substring(1);
const novel = {sourceId, novelName, novelCover, novelUrl};
novels.push(novel);
});
return novels;
};
const NovelForestScraper = {
popularNovels,
parseNovelAndChapters,
parseChapter,
searchNovels,
};
export default NovelForestScraper;
+6 -1
View File
@@ -39,7 +39,7 @@ const parseNovelAndChapters = async novelUrl => {
const result = await fetch(url);
const body = await result.text();
const loadedCheerio = cheerio.load(body);
let loadedCheerio = cheerio.load(body);
let novel = {};
@@ -93,6 +93,11 @@ const parseNovelAndChapters = async novelUrl => {
let novelChapters = [];
const data = await fetch(`${url}ajax/chapters/`, {method: 'POST'});
const text = await data.text();
loadedCheerio = cheerio.load(text);
loadedCheerio('.wp-manga-chapter').each(function () {
const chapterName = loadedCheerio(this)
.find('a')
+8 -3
View File
@@ -41,7 +41,7 @@ const parseNovelAndChapters = async novelUrl => {
const result = await fetch(url);
const body = await result.text();
const loadedCheerio = cheerio.load(body);
let loadedCheerio = cheerio.load(body);
let novel = {};
@@ -96,6 +96,11 @@ const parseNovelAndChapters = async novelUrl => {
let novelChapters = [];
const data = await fetch(`${url}ajax/chapters/`, {method: 'POST'});
const text = await data.text();
loadedCheerio = cheerio.load(text);
loadedCheerio('.wp-manga-chapter').each(function () {
const chapterName = loadedCheerio(this)
.find('a')
@@ -165,10 +170,10 @@ const searchNovels = async searchTerm => {
let novels = [];
loadedCheerio('.c-tabs-item__content').each(function () {
const novelName = loadedCheerio(this).find('h4 > a').text();
const novelName = loadedCheerio(this).find('.post-title a').text();
const novelCover = loadedCheerio(this).find('img').attr('src');
let novelUrl = loadedCheerio(this).find('h4 > a').attr('href');
let novelUrl = loadedCheerio(this).find('.post-title a').attr('href');
novelUrl = novelUrl.replace(`${baseUrl}vipnovel/`, '');
const novel = {
+131
View File
@@ -0,0 +1,131 @@
import cheerio from 'react-native-cheerio';
const sourceId = 88;
const sourceName = 'Chireads';
const baseUrl = 'https://chireads.com/';
const popularNovels = async page => {
const totalPages = 2;
const url = `${baseUrl}category/translatedtales/page/${page}/`;
const result = await fetch(url);
const body = await result.text();
const loadedCheerio = cheerio.load(body);
let novels = [];
loadedCheerio('#content li').each(function () {
const novelName = loadedCheerio(this).find('.news-list-tit h5').text();
const novelCover = loadedCheerio(this).find('img').attr('src');
const novelUrl = loadedCheerio(this)
.find('.news-list-tit h5 a')
.attr('href');
const novel = {sourceId, novelName, novelCover, novelUrl};
novels.push(novel);
});
return {totalPages, novels};
};
const parseNovelAndChapters = async novelUrl => {
const url = novelUrl;
const result = await fetch(url);
const body = await result.text();
let loadedCheerio = cheerio.load(body);
let novel = {
sourceId,
sourceName,
url,
novelUrl,
novelName: '',
novelCover: '',
author: '',
status: '',
genre: '',
summary: '',
chapters: [],
};
novel.novelName = loadedCheerio('.inform-title').text().trim();
novel.novelCover = loadedCheerio('.inform-product img').attr('src');
novel.summary = loadedCheerio('.inform-inform-txt').text().trim();
let chapters = [];
loadedCheerio('.chapitre-table a').each(function () {
const chapterName = loadedCheerio(this).text().trim();
const releaseDate = null;
const chapterUrl = loadedCheerio(this).attr('href');
chapters.push({chapterName, releaseDate, chapterUrl});
});
novel.chapters = chapters;
return novel;
};
const parseChapter = async (novelUrl, chapterUrl) => {
const url = chapterUrl;
const result = await fetch(url);
const body = await result.text();
let loadedCheerio = cheerio.load(body);
const chapterName = loadedCheerio('.article-title').text();
const chapterText = loadedCheerio('#content').html();
const chapter = {
sourceId,
novelUrl,
chapterUrl,
chapterName,
chapterText,
};
return chapter;
};
const searchNovels = async searchTerm => {
const url = `${baseUrl}search?x=0&y=0&name=${searchTerm}`;
const result = await fetch(url);
const body = await result.text();
let loadedCheerio = cheerio.load(body);
let novels = [];
loadedCheerio('#content li').each(function () {
const novelName = loadedCheerio(this).find('.news-list-tit h5').text();
const novelCover = loadedCheerio(this).find('img').attr('src');
const novelUrl = loadedCheerio(this)
.find('.news-list-tit h5 a')
.attr('href');
const novel = {sourceId, novelName, novelCover, novelUrl};
novels.push(novel);
});
return novels;
};
const ChireadsScraper = {
popularNovels,
parseNovelAndChapters,
parseChapter,
searchNovels,
};
export default ChireadsScraper;
+11 -2
View File
@@ -23,6 +23,7 @@ const getPath = extensionId => {
63: {novels: 'novel', novel: 'novel', chapter: 'novel'},
75: {novels: 'series', novel: 'series', chapter: 'series'},
80: {novels: 'manga', novel: 'manga', chapter: 'manga'},
91: {novels: 'manga', novel: 'manga', chapter: 'manga'},
};
return (
@@ -134,6 +135,7 @@ export const LightNovelHeavenScraper = new MadaraScraper(
'https://lightnovelheaven.com/',
'LightNovelHeaven',
getPath(57),
true,
);
export const LightNovelsHubScraper = new MadaraScraper(
@@ -162,8 +164,8 @@ export const WebNovelLoverScraper = new MadaraScraper(
export const BoxNovelOnlineScraper = new MadaraScraper(
63,
'https://boxnovel.online/',
'BoxNovel.online',
'https://tipnovel.com/',
'TipNovel',
getPath(63),
true,
);
@@ -211,3 +213,10 @@ export const MoreNovelScraper = new MadaraScraper(
getPath(84),
true,
);
export const OnlyMTLScraper = new MadaraScraper(
91,
'https://www.onlymtl.com/',
'OnlyMTL',
getPath(91),
);
@@ -5,9 +5,3 @@ export const KolNovelScraper = new WPMangaStreamScraper(
'https://alightnovel.com/',
'KolNovel',
);
export const RewayatArScraper = new WPMangaStreamScraper(
54,
'https://rewayat-ar.site/',
'RewayatAr',
);
+30 -25
View File
@@ -90,24 +90,27 @@ const parseChapter = async (novelUrl, chapterUrl) => {
let chapterName = loadedCheerio(
'body > div.pusher.container_main > div:nth-child(5) > div:nth-child(1) > div.title-wrapper > h1',
).text();
loadedCheerio(
'body > div.pusher.container_main > div:nth-child(5) > div:nth-child(1) img',
).each(function () {
if (!loadedCheerio(this).attr('src')?.startsWith('http')) {
const dataMediaId = loadedCheerio(this).attr('data-media-id');
const newSrc = `https://ranobehub.org/api/media/${dataMediaId}`;
loadedCheerio(this).attr('src', newSrc);
}
});
let chapterText = loadedCheerio(
'body > div.pusher.container_main > div:nth-child(5) > div:nth-child(1)',
).html();
if (chapterText) {
/**
* Convert relative url to absolute
*/
chapterText = chapterText
.replace(/<\s*script[^>]*>[\s\S]*?<\/script>/gim, '')
.replace(
/data-src="(.*)"/g,
`src="https://ranobehub.org/img/ranobe/content/${chapterId}/$1.jpg"`,
);
}
// console.log(chapterText);
// Remove script tags
chapterText = chapterText?.replace(
/<\s*script[^>]*>[\s\S]*?<\/script>/gim,
'',
);
const chapter = {
sourceId,
@@ -121,23 +124,25 @@ const parseChapter = async (novelUrl, chapterUrl) => {
};
const searchNovels = async searchTerm => {
const url = 'https://ranobehub.org/api/fulltext/ranobe?query=' + searchTerm;
const url = `https://ranobehub.org/api/fulltext/global?query=${searchTerm}&take=5`;
const result = await fetch(url);
const data = await result.json();
let novels = [];
data.data.map(novel =>
novels.push({
sourceId,
novelName: novel.names.rus,
novelUrl: novel.url.match(
/https:\/\/ranobehub\.org\/ranobe\/(.*?)\?utm_source=search_name&utm_medium=search&utm_campaign=search_using/,
)[1],
novelCover: novel.image,
}),
);
data
.find(item => item.meta.key === 'ranobe')
?.data.map(novel =>
novels.push({
sourceId,
novelName: novel.names.rus,
novelUrl: novel.url.match(
/https:\/\/ranobehub\.org\/ranobe\/(.*?)\?utm_source=search_name&utm_medium=search&utm_campaign=search_using/,
)[1],
novelCover: novel.image,
}),
);
return novels;
};
+12 -4
View File
@@ -50,6 +50,7 @@ import {
NovelMultiverseScraper,
NovelsRockScraper,
NovelTranslateScraper,
OnlyMTLScraper,
ReadWebNovelsScraper,
SkyNovelScraper,
SleepyTranslationsScraper,
@@ -63,10 +64,7 @@ import WuxiaCityScraper from './en/wuxiacity';
import NovelUpdatesScraper from './en/novelupdates';
import RanobesScraper from './en/ranobes';
import YushuboScraper from './ch/yushubo';
import {
KolNovelScraper,
RewayatArScraper,
} from './multisrc/wpmangastream/WPMangaStreamGenerator';
import {KolNovelScraper} from './multisrc/wpmangastream/WPMangaStreamGenerator';
import WLNUpdatesScraper from './en/wlnupdates';
import ReaperScansScraper from './en/reaperscans';
import ReadwnScraper from './en/readwn';
@@ -88,6 +86,11 @@ import MyLoveNovelScraper from './en/mylovenovel';
import RiwayatScraper from './ar/riwayat';
import NovelRinganScraper from './id/novelringan';
import IndoWebNovelScraper from './id/indowebnovel';
import ChireadsScraper from './fr/chireads';
import RewayatArScraper from './ar/rewayatar';
import MTLCornerScraper from './en/mtlcorner';
import NitroScansScraper from './en/nitroscans';
import NovelForestScraper from './en/novelforest';
export const getSource = sourceId => {
const scrapers = {
@@ -177,6 +180,11 @@ export const getSource = sourceId => {
85: RiwayatScraper,
86: NovelRinganScraper,
87: IndoWebNovelScraper,
88: ChireadsScraper,
89: MTLCornerScraper,
90: NitroScansScraper,
91: OnlyMTLScraper,
92: NovelForestScraper,
};
return scrapers[sourceId];
+35
View File
@@ -600,5 +600,40 @@
"sourceName": "IndoWebNovel",
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/id/indowebnovel/icon.png?raw=true",
"lang": "Indonesian"
},
{
"sourceId": 88,
"url": "https://chireads.com/",
"sourceName": "Chireads",
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/fr/chireads/icon.png?raw=true",
"lang": "French"
},
{
"sourceId": 89,
"url": "https://mtlcorner.com/",
"sourceName": "MTLCorner",
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/mtlcorner/icon.png?raw=true",
"lang": "English"
},
{
"sourceId": 90,
"url": "https://nitroscans.com/",
"sourceName": "NitroScans",
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/nitroscans/icon.png?raw=true",
"lang": "English"
},
{
"sourceId": 91,
"url": "https://www.onlymtl.com/",
"sourceName": "OnlyMTL",
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/multisrc/madara/icons/onlymtl.png?raw=true",
"lang": "English"
},
{
"sourceId": 92,
"url": "https://novelforest.com/",
"sourceName": "NovelForest",
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/en/novelforest/icon.png?raw=true",
"lang": "English"
}
]