Compare commits

..

23 Commits

Author SHA1 Message Date
nguyd1 64ef4bae1f Fix Library Update Freeze (#898)
* fix updating library freeze

* fix updating library freeze

* refactor
2024-01-15 10:49:56 +05:30
Rider21 88a70cbcea Add Source ifreedom (#886)
* update

* fix

* genre

* test

* Update freewebnovel.ts

* status parsing

* test readwn

* fix typo

* Update IfreedomScraper.js

* fix ESlint

* Update ReadwnScraper.js

* deleted the build fix

---------

Co-authored-by: Rajarshee Chatterjee <rajarshee.adm@gmail.com>
2024-01-15 10:48:10 +05:30
Isaac H cfcc4bd875 Update Contributing Guide (#876)
* added a common problems section in CONTRIBUTING.md

* added a common problems section in CONTRIBUTING.md and a new file listing common problems

* Update COMMON_ERRORS.md
2024-01-14 22:55:32 +05:30
nguyd1 02b058dac2 Remove MMKV Cookie Storage (#896)
* fix retry button

* remove cookie storage

* add cookie storage

* fix import

* remove all references of mmkv cookies
2024-01-14 22:52:57 +05:30
Patrick Loser 36cb7fc391 Horizontal Reading Mode (#733)
Closes #29
Closes #149
2024-01-14 22:52:26 +05:30
nguyd1 8613a088d2 Update Python Version in CONTRIBUTING.md (#889) 2024-01-13 14:38:13 +05:30
nguyd1 e600828bc2 Fix Cloudflare on NovelUpdates (#887)
* fix cloudflare

* fix import
2024-01-13 14:31:09 +05:30
K1ngfish3r e4364aea7c Add MTLNovel (ES) (#890)
Closes LNReader/lnreader-sources#682
2024-01-13 14:29:59 +05:30
nguyd1 df8da48c32 Fix NovelUpdates Covers (#880) 2024-01-08 14:17:24 +05:30
nguyd1 a83b4b1a32 Fix Retry Button (#879) 2024-01-08 14:17:03 +05:30
Rajarshee Chatterjee 8a85f4eaf1 Add Setting to Jump to Last Read Chapter 2024-01-07 18:38:57 +05:30
Rajarshee Chatterjee 2c1cce7fbe Reset Progress on Chapter End (Closes #791) 2024-01-07 18:18:40 +05:30
Rajarshee Chatterjee e4ca751fb0 Animated FAB in Novel Screen (Closes #839) 2024-01-07 17:58:46 +05:30
Rajarshee Chatterjee c3543f8186 Add Retry Button on Browse Screen (Closes #667) 2024-01-07 17:29:02 +05:30
Rajarshee Chatterjee 9253249ef6 Add Retry and WebView Button on Chapter Error Screen (Closes #818) 2024-01-07 17:25:02 +05:30
Rajarshee Chatterjee e0e593bf9f Filter Duplicates in Chapter Selection (Closes #847) 2024-01-04 22:58:18 +05:30
Rajarshee Chatterjee a298ed81d7 Add Exclude Downloads Filter (Closes #848) 2024-01-04 22:16:01 +05:30
K1ngfish3r 60cf358e57 Source Updates (#807)
* dev woopread

* check if input breaks chapterText

* AsuraLightNovel dev

* add Titan.wf to novelupdates

* rough fix novelpub

* remove userAgent check

* i forgot one

* lnmtl ChapterText fix

* fix author, genre and status lnmtl

* xperimental

* grounded quotation marks lnmtl

* fix woopread missing chapter

* refactor wuxia.blog

* copypasta lightnovelpub

* until stylesheets are added

* remove console.log

* revert armtl and wuxiablog search delete

* fix eslint check?

* fastnovel to novelbin

* Blossom Translation Dev

* skynovel fix chapter spacing

* oops

* linovelib fix?

* search fix linovelib

* update dictionary linovelib

* update dictionary 2: electric bugaloo

* skillgg dict and multipage chapter + search fix

* fix sakuranovel again

* The_Lazy_Guy Dev + TuNovelaLigera Dev

* TuNovelaLigera, ch cleanup

* under review

* remove green dictionary

* text()

* add delay

* cleanup

* allnovelfull cleanup

* update selector

* update selectors SUPER

* fix chapter name lightnovelpub

* update dictionary 3: revenge of the dict

* indowebnovel
2023-12-27 19:38:16 +05:30
Rajarshee Chatterjee 4548cdf8db chore: Update translations (#816)
* New translations strings.json (Odia)
[ci skip]

* New translations strings.json (Russian)
[ci skip]

* New translations strings.json (Indonesian)
[ci skip]

* New translations strings.json (Italian)
[ci skip]

* New translations strings.json (Indonesian)
[ci skip]

* New translations strings.json (Indonesian)
[ci skip]

* New translations strings.json (Portuguese, Brazilian)
[ci skip]

* New translations strings.json (Portuguese, Brazilian)
[ci skip]
2023-12-27 19:36:54 +05:30
NewbieAnimeManga ee9391bd9f fix(source): NovelOnline: Add Popular Novels (#832)
Closes LNReader/lnreader-sources#638

---------

Co-authored-by: Rajarshee Chatterjee <rajarshee.adm@gmail.com>
2023-11-06 14:07:08 +05:30
Rajarshee Chatterjee f069a67660 feat: Save Covers Locally (Closes #780) 2023-11-01 20:29:36 +05:30
Rajarshee Chatterjee 7ee843f7b8 fix: Add Scrolling in Advanced Settings and More Header
Closes #806
Closes #800
2023-11-01 20:14:46 +05:30
Rajarshee Chatterjee 757c1eae5f chore: Update translations (#695)
* New translations strings.json (Arabic)
[ci skip]

* New translations strings.json (Vietnamese)
[ci skip]

* New translations strings.json (Odia)
[ci skip]

* New translations strings.json (Indonesian)
[ci skip]

* New translations strings.json (Turkish)
[ci skip]

* New translations strings.json (Indonesian)
[ci skip]

* New translations strings.json (Indonesian)
[ci skip]

* New translations strings.json (Indonesian)
[ci skip]

* New translations strings.json (Italian)
[ci skip]

* New translations strings.json (Indonesian)
[ci skip]

* New translations strings.json (French)
[ci skip]

* New translations strings.json (Russian)
[ci skip]

* New translations strings.json (Ukrainian)
[ci skip]

* New translations strings.json (French)
[ci skip]

* New translations strings.json (Russian)
[ci skip]

* New translations strings.json (Ukrainian)
[ci skip]

* New translations strings.json (Romanian)
[ci skip]

* New translations strings.json (Spanish)
[ci skip]

* New translations strings.json (Afrikaans)
[ci skip]

* New translations strings.json (Arabic)
[ci skip]

* New translations strings.json (Catalan)
[ci skip]

* New translations strings.json (Czech)
[ci skip]

* New translations strings.json (Danish)
[ci skip]

* New translations strings.json (German)
[ci skip]

* New translations strings.json (Greek)
[ci skip]

* New translations strings.json (Finnish)
[ci skip]

* New translations strings.json (Hebrew)
[ci skip]

* New translations strings.json (Hungarian)
[ci skip]

* New translations strings.json (Italian)
[ci skip]

* New translations strings.json (Japanese)
[ci skip]

* New translations strings.json (Korean)
[ci skip]

* New translations strings.json (Dutch)
[ci skip]

* New translations strings.json (Norwegian)
[ci skip]

* New translations strings.json (Polish)
[ci skip]

* New translations strings.json (Portuguese)
[ci skip]

* New translations strings.json (Albanian)
[ci skip]

* New translations strings.json (Serbian (Cyrillic))
[ci skip]

* New translations strings.json (Swedish)
[ci skip]

* New translations strings.json (Turkish)
[ci skip]

* New translations strings.json (Chinese Simplified)
[ci skip]

* New translations strings.json (Chinese Traditional)
[ci skip]

* New translations strings.json (Vietnamese)
[ci skip]

* New translations strings.json (Portuguese, Brazilian)
[ci skip]

* New translations strings.json (Indonesian)
[ci skip]

* New translations strings.json (Hindi)
[ci skip]

* New translations strings.json (Odia)
[ci skip]

* New translations strings.json (Turkish)
[ci skip]

* New translations strings.json (Russian)
[ci skip]

* New translations strings.json (Odia)
[ci skip]

* New translations strings.json (French)
[ci skip]

* New translations strings.json (Russian)
[ci skip]

* New translations strings.json (Ukrainian)
[ci skip]

* New translations strings.json (Romanian)
[ci skip]

* New translations strings.json (Spanish)
[ci skip]

* New translations strings.json (Afrikaans)
[ci skip]

* New translations strings.json (Arabic)
[ci skip]

* New translations strings.json (Catalan)
[ci skip]

* New translations strings.json (Czech)
[ci skip]

* New translations strings.json (Danish)
[ci skip]

* New translations strings.json (German)
[ci skip]

* New translations strings.json (Greek)
[ci skip]

* New translations strings.json (Finnish)
[ci skip]

* New translations strings.json (Hebrew)
[ci skip]

* New translations strings.json (Hungarian)
[ci skip]

* New translations strings.json (Italian)
[ci skip]

* New translations strings.json (Japanese)
[ci skip]

* New translations strings.json (Korean)
[ci skip]

* New translations strings.json (Dutch)
[ci skip]

* New translations strings.json (Norwegian)
[ci skip]

* New translations strings.json (Polish)
[ci skip]

* New translations strings.json (Portuguese)
[ci skip]

* New translations strings.json (Albanian)
[ci skip]

* New translations strings.json (Serbian (Cyrillic))
[ci skip]

* New translations strings.json (Swedish)
[ci skip]

* New translations strings.json (Turkish)
[ci skip]

* New translations strings.json (Chinese Simplified)
[ci skip]

* New translations strings.json (Chinese Traditional)
[ci skip]

* New translations strings.json (Vietnamese)
[ci skip]

* New translations strings.json (Portuguese, Brazilian)
[ci skip]

* New translations strings.json (Indonesian)
[ci skip]

* New translations strings.json (Hindi)
[ci skip]

* New translations strings.json (Odia)
[ci skip]

* New translations strings.json (Spanish)
[ci skip]

* New translations strings.json (Turkish)
[ci skip]

* New translations strings.json (Indonesian)
[ci skip]

* New translations strings.json (German)
[ci skip]

* New translations strings.json (Spanish)
[ci skip]

* New translations strings.json (Czech)
[ci skip]

* New translations strings.json (Arabic)
[ci skip]

* New translations strings.json (Czech)
[ci skip]

* New translations strings.json (Chinese Simplified)
[ci skip]

* New translations strings.json (Russian)
[ci skip]

* New translations strings.json (Czech)
[ci skip]

* New translations strings.json (Czech)
[ci skip]

* New translations strings.json (German)
[ci skip]

* New translations strings.json (French)
[ci skip]

* New translations strings.json (Arabic)
[ci skip]

* New translations strings.json (Arabic)
[ci skip]

* New translations strings.json (Chinese Simplified)
[ci skip]
2023-10-14 11:01:54 +05:30
100 changed files with 4030 additions and 2757 deletions
+13
View File
@@ -0,0 +1,13 @@
# Common Problems with First Setup
### SDK Location not found. Define location with an ANDROID_SDK_ROOT environment variable. (expo modules error)
If you're on windows, you'll need to edit your system (system wide)/user (just you) environment variables. You'll want to add an environment variable named **ANDROID_SDK_ROOT** (legacy env variable name) and **ANDROID_HOME** that points to your android studio's sdk location. It'll most likely look something like this:
C:\Users\\**Username**\AppData\Local\Android\Sdk (copy paste this into the box)
Delete your node_modules folder, invalidate caches and restart in android studio and restart your machine. Follow the steps again in the [setup section](./CONTRIBUTING.md#setup).
### '.' is not recognized as an internal or external command, operable program or batch file. (npm run buildRelease error)
Run this line in the terminal instead: ```cd android ; ./gradlew clean ; ./gradlew assembleRelease```
+9 -4
View File
@@ -8,9 +8,10 @@ After forking to your own github org or account, do the following steps to get s
```bash
# prerequisites
node --version <= 16.13.1 (for version management, get nvm [recommended])
java sdk --version <= 11 (for version management, get jenv [optional])
android sdk (https://developer.android.com/studio)
node --version <= 16.13.1 (for version management, get nvm [recommended])
java sdk --version <= 17 (for version management, get jenv [optional])
android sdk (https://developer.android.com/studio)
python --version <= 3.11
# clone your fork to your local machine
git clone https://github.com/<your-account-name>/lnreader.git
@@ -25,6 +26,10 @@ npm install
npm run buildRelease
```
### Common Problems with First Setup
Please refer to this [file](./COMMON_ERRORS.md)
### Developing on Android
You will need an Android device or emulator connected to your computer as well as an IDE of your choice. (eg: vscode)
@@ -56,4 +61,4 @@ codebase, however you can always check to see if the source code is compliant by
```bash
npm run lint
```
```
+721 -845
View File
File diff suppressed because it is too large Load Diff
+45 -4
View File
@@ -1,21 +1,45 @@
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import { useTheme } from '@hooks/useTheme';
interface ErrorScreenProps {
error: string;
onRetry?: () => void;
retryIconColor?: string;
actions?: Array<{
iconName: string;
title: string;
onPress: () => void;
}>;
}
const ErrorScreen: React.FC<ErrorScreenProps> = ({ error }) => {
const ErrorScreen: React.FC<ErrorScreenProps> = ({ error, actions }) => {
const theme = useTheme();
return (
<View style={styles.container}>
<Text style={[styles.icon, { color: theme.outline }]}>ಥ_ಥ</Text>
<Text style={[styles.error, { color: theme.outline }]}>{error}</Text>
{actions?.length ? (
<View style={styles.actionsCtn}>
{actions.map(action => (
<View style={styles.buttonWrapper}>
<Pressable
android_ripple={{ color: theme.rippleColor }}
onPress={action.onPress}
style={styles.buttonCtn}
>
<MaterialCommunityIcons
name={action.iconName}
size={24}
color={theme.outline}
/>
<Text style={{ color: theme.outline }}>{action.title}</Text>
</Pressable>
</View>
))}
</View>
) : null}
</View>
);
};
@@ -36,4 +60,21 @@ const styles = StyleSheet.create({
paddingHorizontal: 16,
textAlign: 'center',
},
buttonWrapper: {
overflow: 'hidden',
borderRadius: 50,
marginHorizontal: 4,
flexDirection: 'row',
flex: 1 / 3,
},
buttonCtn: {
flex: 1,
paddingVertical: 8,
alignItems: 'center',
justifyContent: 'center',
},
actionsCtn: {
marginTop: 20,
flexDirection: 'row',
},
});
+1 -3
View File
@@ -4,7 +4,6 @@ import { StyleSheet, View, Text, Pressable } from 'react-native';
import FastImage from 'react-native-fast-image';
import { coverPlaceholderColor } from '../theme/colors';
import { getSourceStorage } from '@hooks/useSourceStorage';
import { defaultUserAgentString } from '@utils/fetch/fetch';
import color from 'color';
@@ -19,7 +18,6 @@ const ListView = ({
isSelected,
onLongPress,
}) => {
const { cookies = '' } = getSourceStorage(item.sourceId);
return (
<Pressable
android_ripple={{ color: theme.rippleColor }}
@@ -35,7 +33,7 @@ const ListView = ({
<FastImage
source={{
uri: item.novelCover,
headers: { Cookie: cookies, 'User-Agent': defaultUserAgentString },
headers: { 'User-Agent': defaultUserAgentString },
}}
style={[styles.extensionIcon, inLibraryBadge && { opacity: 0.5 }]}
/>
+1 -4
View File
@@ -20,7 +20,6 @@ import { SourceNovelItem } from 'src/sources/types';
import { ThemeColors } from '@theme/types';
import SourceScreenSkeletonLoading from '@screens/browse/loadingAnimation/SourceScreenSkeletonLoading';
import { getSourceStorage } from '@hooks/useSourceStorage';
import { defaultUserAgentString } from '@utils/fetch/fetch';
interface NovelCoverProps {
@@ -68,8 +67,6 @@ const NovelCover: React.FC<NovelCoverProps> = ({
const uri = item.novelCover;
const { cookies = '' } = getSourceStorage(item.sourceId);
return item.sourceId < 0 ? (
<SourceScreenSkeletonLoading theme={theme} completeRow={item.sourceId} />
) : displayMode !== DisplayModes.List ? (
@@ -115,7 +112,7 @@ const NovelCover: React.FC<NovelCoverProps> = ({
<FastImage
source={{
uri,
headers: { Cookie: cookies, 'User-Agent': defaultUserAgentString },
headers: { 'User-Agent': defaultUserAgentString },
}}
style={[
{
-64
View File
@@ -1,64 +0,0 @@
import CookieManager from '@react-native-cookies/cookies';
import { useMMKVObject } from 'react-native-mmkv';
import { MMKVStorage } from '@utils/mmkv/mmkv';
import { showToast } from './showToast';
export const SOURCE_STORAGE = 'SOURCE_STORAGE';
interface SourceStorage {
cookies?: any;
}
export type SourceStorageMap = Record<number, Partial<SourceStorage>>;
const useSourceStorage = ({ sourceId = -1 }: { sourceId?: number }) => {
const [values, setValues] = useMMKVObject<SourceStorageMap>(
SOURCE_STORAGE,
MMKVStorage,
);
const setSourceStorage = (key: keyof SourceStorage, value: any): void => {
setValues({
...values,
[sourceId]: {
...values?.[sourceId],
[key]: value,
},
});
};
const clearCookies = (): void => {
// let tempStorage = {};
// if (values) {
// tempStorage = Object.fromEntries(
// Object.entries(values).map(([id, sourceData]) => {
// delete sourceData.cookies;
// return [id, sourceData];
// }),
// );
// }
CookieManager.clearAll();
CookieManager.clearAll(true); //clears cookies in webkit
setValues({});
showToast('Cookies cleared');
};
const sourceStorage = values?.[sourceId];
return {
...sourceStorage,
setSourceStorage,
clearCookies,
};
};
export default useSourceStorage;
export const getSourceStorage = (sourceId: number) => {
const rawSettings = MMKVStorage.getString(SOURCE_STORAGE) || '{}';
const parsedSettings: Partial<SourceStorageMap> = JSON.parse(rawSettings);
return parsedSettings[sourceId] || {};
};
+1
View File
@@ -67,6 +67,7 @@ export const initialState = {
fullScreenMode: true,
swipeGestures: false,
readerPages: false,
showScrollPercentage: true,
useVolumeButtons: false,
scrollAmount: Math.round(Dimensions.get('window').height),
@@ -52,6 +52,7 @@ const BrowseSourceScreen: React.FC<BrowseSourceScreenProps> = ({ route }) => {
filterValues,
setFilters,
clearFilters,
refetchNovels,
} = useBrowseSource(sourceId, showLatestNovels);
const { defaultCategoryId = 1 } = useCategorySettings();
@@ -126,6 +127,13 @@ const BrowseSourceScreen: React.FC<BrowseSourceScreenProps> = ({ route }) => {
) : errorMessage || novelList.length === 0 ? (
<ErrorScreenV2
error={errorMessage || getString('sourceScreen.noResultsFound')}
actions={[
{
iconName: 'refresh',
title: getString('common.retry'),
onPress: refetchNovels,
},
]}
/>
) : (
<NovelList
@@ -55,6 +55,9 @@ export const useBrowseSource = (
}, [fetchNovels, currentPage]);
const refetchNovels = () => {
isScreenMounted.current = true;
setError('');
setIsLoading(true);
setCurrentPage(1);
fetchNovels(1);
};
@@ -8,7 +8,6 @@ import { SourceNovelItem } from '../../../sources/types';
import { ThemeColors } from '../../../theme/types';
import { getString } from '@strings/translations';
import { getSourceStorage } from '@hooks/useSourceStorage';
import { defaultUserAgentString } from '@utils/fetch/fetch';
interface Props {
@@ -35,8 +34,6 @@ const GlobalSearchNovelItem: React.FC<Props> = ({
[inLibrary],
);
const { cookies = '' } = getSourceStorage(novel.sourceId);
return (
<View style={styles.novelItem}>
<Pressable
@@ -48,7 +45,7 @@ const GlobalSearchNovelItem: React.FC<Props> = ({
<FastImage
source={{
uri: novel.novelCover,
headers: { Cookie: cookies, 'User-Agent': defaultUserAgentString },
headers: { 'User-Agent': defaultUserAgentString },
}}
style={[styles.novelCover, { ...novelItemDimensions }]}
/>
+6 -13
View File
@@ -1,11 +1,10 @@
import React, { useEffect } from 'react';
import React from 'react';
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
import WebView from 'react-native-webview';
import CookieManager from '@react-native-cookies/cookies';
import { Appbar } from '@components';
import { useTheme } from '@hooks/useTheme';
import useSourceStorage from '@hooks/useSourceStorage';
import { defaultUserAgentString } from '@utils/fetch/fetch';
type ReaderScreenRouteProps = RouteProp<{
@@ -21,19 +20,12 @@ const WebviewScreen = () => {
const { goBack } = useNavigation();
const {
params: { name, sourceId, url },
params: { name, url },
} = useRoute<ReaderScreenRouteProps>();
const { setSourceStorage } = useSourceStorage({ sourceId });
useEffect(() => {
CookieManager.get(url, true).then(cookies => {
const cloudflareCookie = cookies?.cf_clearance;
if (cloudflareCookie) {
const cloudflareCookieString = `${cloudflareCookie.name}=${cloudflareCookie.value}`;
setSourceStorage('cookies', cloudflareCookieString);
}
});
}, []);
const syncCookies = () => {
CookieManager.flush();
};
return (
<>
@@ -42,6 +34,7 @@ const WebviewScreen = () => {
startInLoadingState
userAgent={defaultUserAgentString}
source={{ uri: url }}
onNavigationStateChange={syncCookies}
/>
</>
);
@@ -16,7 +16,6 @@ import {
NovelScreenRouteParams,
} from '@utils/NavigationUtils';
import { getSourceStorage } from '@hooks/useSourceStorage';
import { defaultUserAgentString } from '@utils/fetch/fetch';
interface HistoryCardProps {
@@ -59,8 +58,6 @@ const HistoryCard: React.FC<HistoryCardProps> = ({
[chapterName, historyTimeRead],
);
const { cookies = '' } = getSourceStorage(sourceId);
return (
<Pressable
style={styles.container}
@@ -94,7 +91,6 @@ const HistoryCard: React.FC<HistoryCardProps> = ({
source={{
uri: novelCover,
headers: {
Cookie: cookies,
'User-Agent': defaultUserAgentString,
},
}}
+133 -135
View File
@@ -31,144 +31,142 @@ const MoreScreen = ({ navigation }) => {
setLibrarySettings({ incognitoMode: !incognitoMode });
return (
<>
<MoreHeader title="More" navigation={navigation} theme={theme} />
<ScrollView style={{ flex: 1 }}>
<List.Section>
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={{
paddingHorizontal: 16,
paddingVertical: 14,
flexDirection: 'row',
justifyContent: 'space-between',
}}
onPress={enableDownloadedOnlyMode}
>
<View style={{ flexDirection: 'row' }}>
<List.Icon theme={theme} icon="cloud-off-outline" />
<View style={{ marginLeft: 16 }}>
<Text
style={{
color: theme.onSurface,
fontSize: 16,
}}
>
Downloaded only
</Text>
<Text style={{ color: theme.onSurfaceVariant }}>
Filters all novels in your library
</Text>
</View>
<ScrollView>
<MoreHeader navigation={navigation} theme={theme} />
<List.Section>
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={{
paddingHorizontal: 16,
paddingVertical: 14,
flexDirection: 'row',
justifyContent: 'space-between',
}}
onPress={enableDownloadedOnlyMode}
>
<View style={{ flexDirection: 'row' }}>
<List.Icon theme={theme} icon="cloud-off-outline" />
<View style={{ marginLeft: 16 }}>
<Text
style={{
color: theme.onSurface,
fontSize: 16,
}}
>
Downloaded only
</Text>
<Text style={{ color: theme.onSurfaceVariant }}>
Filters all novels in your library
</Text>
</View>
<Switch
value={downloadedOnlyMode}
onValueChange={enableDownloadedOnlyMode}
color={theme.primary}
style={{ marginRight: 8 }}
/>
</Pressable>
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={{
paddingHorizontal: 16,
paddingVertical: 14,
flexDirection: 'row',
justifyContent: 'space-between',
}}
onPress={enableIncognitoMode}
>
<View style={{ flexDirection: 'row' }}>
<List.Icon theme={theme} icon="incognito" />
<View style={{ marginLeft: 16 }}>
<Text
style={{
color: theme.onSurface,
fontSize: 16,
}}
>
Incognito mode
</Text>
<Text style={{ color: theme.onSurfaceVariant }}>
Pauses reading history
</Text>
</View>
</View>
<Switch
value={downloadedOnlyMode}
onValueChange={enableDownloadedOnlyMode}
color={theme.primary}
style={{ marginRight: 8 }}
/>
</Pressable>
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={{
paddingHorizontal: 16,
paddingVertical: 14,
flexDirection: 'row',
justifyContent: 'space-between',
}}
onPress={enableIncognitoMode}
>
<View style={{ flexDirection: 'row' }}>
<List.Icon theme={theme} icon="incognito" />
<View style={{ marginLeft: 16 }}>
<Text
style={{
color: theme.onSurface,
fontSize: 16,
}}
>
Incognito mode
</Text>
<Text style={{ color: theme.onSurfaceVariant }}>
Pauses reading history
</Text>
</View>
<Switch
value={incognitoMode}
onValueChange={enableIncognitoMode}
color={theme.primary}
style={{ marginRight: 8 }}
/>
</Pressable>
<List.Divider theme={theme} />
<List.Item
title="Download queue"
description={
downloadQueue.length > 0 && downloadQueue.length + ' remaining'
}
icon="progress-download"
onPress={() =>
navigation.navigate('MoreStack', {
screen: 'DownloadQueue',
})
}
theme={theme}
</View>
<Switch
value={incognitoMode}
onValueChange={enableIncognitoMode}
color={theme.primary}
style={{ marginRight: 8 }}
/>
<List.Item
title="Downloads"
icon="folder-download"
onPress={() =>
navigation.navigate('MoreStack', {
screen: 'Downloads',
})
}
theme={theme}
/>
<List.Item
title={getString('common.categories')}
icon="label-outline"
onPress={() =>
navigation.navigate('MoreStack', {
screen: 'Categories',
})
}
theme={theme}
/>
<List.Item
title={getString('statsScreen.title')}
icon="chart-line"
onPress={() =>
navigation.navigate('MoreStack', {
screen: 'Statistics',
})
}
theme={theme}
/>
<List.Divider theme={theme} />
<List.Item
title={getString('moreScreen.settings')}
icon="cog-outline"
onPress={() =>
navigation.navigate('MoreStack', {
screen: 'SettingsStack',
})
}
theme={theme}
/>
<List.Item
title="About"
icon="information-outline"
onPress={() =>
navigation.navigate('MoreStack', {
screen: 'About',
})
}
theme={theme}
/>
</List.Section>
</ScrollView>
</>
</Pressable>
<List.Divider theme={theme} />
<List.Item
title="Download queue"
description={
downloadQueue.length > 0 && downloadQueue.length + ' remaining'
}
icon="progress-download"
onPress={() =>
navigation.navigate('MoreStack', {
screen: 'DownloadQueue',
})
}
theme={theme}
/>
<List.Item
title="Downloads"
icon="folder-download"
onPress={() =>
navigation.navigate('MoreStack', {
screen: 'Downloads',
})
}
theme={theme}
/>
<List.Item
title={getString('common.categories')}
icon="label-outline"
onPress={() =>
navigation.navigate('MoreStack', {
screen: 'Categories',
})
}
theme={theme}
/>
<List.Item
title={getString('statsScreen.title')}
icon="chart-line"
onPress={() =>
navigation.navigate('MoreStack', {
screen: 'Statistics',
})
}
theme={theme}
/>
<List.Divider theme={theme} />
<List.Item
title={getString('moreScreen.settings')}
icon="cog-outline"
onPress={() =>
navigation.navigate('MoreStack', {
screen: 'SettingsStack',
})
}
theme={theme}
/>
<List.Item
title="About"
icon="information-outline"
onPress={() =>
navigation.navigate('MoreStack', {
screen: 'About',
})
}
theme={theme}
/>
</List.Section>
</ScrollView>
);
};
+49 -19
View File
@@ -23,8 +23,8 @@ import {
Appbar,
IconButton,
Menu,
FAB,
Snackbar,
AnimatedFAB,
} from 'react-native-paper';
import { useDispatch, useSelector } from 'react-redux';
import * as Haptics from 'expo-haptics';
@@ -68,6 +68,7 @@ import { getChapterScreenRouteParams } from '../../utils/NavigationUtils';
import NovelScreenLoading from './components/LoadingAnimation/NovelScreenLoading';
import { useTrackerReducer } from '@redux/hooks';
import EpubIconButton from './components/EpubIconButton';
import { uniqBy } from 'lodash-es';
const Novel = ({ route, navigation }) => {
const item = route.params;
@@ -85,6 +86,7 @@ const Novel = ({ route, navigation }) => {
const [selected, setSelected] = useState([]);
const [downloadMenu, showDownloadMenu] = useState(false);
const [extraMenu, showExtraMenu] = useState(false);
const [isFabExtended, setIsFabExtended] = useState(true);
let flatlistRef = useRef(null);
let novelBottomSheetRef = useRef(null);
@@ -96,6 +98,7 @@ const Novel = ({ route, navigation }) => {
useFabForContinueReading = false,
defaultChapterSort = 'ORDER BY chapterId ASC',
disableHapticFeedback = false,
jumpToLastReadChapter = false,
} = useSettings();
const {
@@ -125,6 +128,9 @@ const Novel = ({ route, navigation }) => {
const onPageScroll = event => {
const y = event.nativeEvent.contentOffset.y;
headerOpacity.value = withTiming(y > 10 ? 1 : 0, { duration: 200 });
const currentScrollPosition = Math.floor(y) ?? 0;
setIsFabExtended(currentScrollPosition <= 0);
};
useEffect(() => {
@@ -290,25 +296,37 @@ const Novel = ({ route, navigation }) => {
if (lastSelectedChapter.chapterId !== chapter.chapterId) {
if (lastSelectedChapter.chapterId > chapter.chapterId) {
setSelected(sel => [
...sel,
chapter,
...chapters.filter(
chap =>
(chap.chapterId <= chapter.chapterId ||
chap.chapterId >= lastSelectedChapter.chapterId) === false,
setSelected(sel =>
uniqBy(
[
...sel,
chapter,
...chapters.filter(
chap =>
(chap.chapterId <= chapter.chapterId ||
chap.chapterId >= lastSelectedChapter.chapterId) ===
false,
),
],
'chapterId',
),
]);
);
} else {
setSelected(sel => [
...sel,
chapter,
...chapters.filter(
chap =>
(chap.chapterId >= chapter.chapterId ||
chap.chapterId <= lastSelectedChapter.chapterId) === false,
setSelected(sel =>
uniqBy(
[
...sel,
chapter,
...chapters.filter(
chap =>
(chap.chapterId >= chapter.chapterId ||
chap.chapterId <= lastSelectedChapter.chapterId) ===
false,
),
],
'chapterId',
),
]);
);
}
}
}
@@ -634,6 +652,18 @@ const Novel = ({ route, navigation }) => {
</Portal>
<View style={styles.flashlistContainer}>
<FlashList
onLayout={() => {
if (jumpToLastReadChapter) {
const lastReadChapterIdx = chapters.findIndex(
chapter => chapter.chapterId === lastReadChapter.chapterId,
);
flatlistRef.current.scrollToIndex({
animated: true,
index: lastReadChapterIdx,
});
}
}}
ref={flatlistRef}
estimatedItemSize={64}
data={chapters}
@@ -666,12 +696,12 @@ const Novel = ({ route, navigation }) => {
/>
</View>
{useFabForContinueReading && chapters.length > 0 && lastReadChapter && (
<FAB
<AnimatedFAB
style={[
styles.fab,
{ backgroundColor: theme.primary, marginBottom: bottomInset },
]}
small
extended={isFabExtended}
color={theme.onPrimary}
uppercase={false}
label={novel.unread ? 'Start' : 'Resume'}
@@ -8,6 +8,7 @@ import {
Dimensions,
StatusBar,
} from 'react-native';
import RNFetchBlob from 'rn-fetch-blob';
import color from 'color';
import { IconButton, Portal } from 'react-native-paper';
import { LinearGradient } from 'expo-linear-gradient';
@@ -16,6 +17,7 @@ import FastImage from 'react-native-fast-image';
import { TouchableWithoutFeedback } from 'react-native-gesture-handler';
import { Chip } from '../../../../components';
import { coverPlaceholderColor } from '../../../../theme/colors';
import { showToast } from '@hooks/showToast';
const NovelInfoContainer = ({ children }) => (
<View style={styles.novelInfoContainer}>{children}</View>
@@ -69,17 +71,41 @@ const NovelThumbnail = ({ source, theme, setCustomNovelCover }) => {
} else {
return (
<Portal>
<IconButton
icon="pencil-outline"
<View
style={{
position: 'absolute',
top: StatusBar.currentHeight + 10,
right: 10,
zIndex: 10,
flexDirection: 'row',
}}
iconColor={theme.onBackground}
onPress={setCustomNovelCover}
/>
>
<IconButton
icon="pencil-outline"
iconColor={theme.onBackground}
onPress={setCustomNovelCover}
/>
<IconButton
icon="content-save-outline"
iconColor={theme.onBackground}
onPress={() => {
RNFetchBlob.config({
fileCache: true,
addAndroidDownloads: {
useDownloadManager: true,
notification: true,
path:
RNFetchBlob.fs.dirs.DownloadDir +
'/' +
source.uri.split('/').pop(),
description: 'Image',
},
})
.fetch('GET', source.uri)
.then(() => showToast('Cover saved'));
}}
/>
</View>
<Pressable
style={{
position: 'absolute',
@@ -11,7 +11,6 @@ import { followNovelAction } from '../../../../redux/novel/novel.actions';
import { useSettings } from '../../../../hooks/reduxHooks';
import { showToast } from '../../../../hooks/showToast';
import { getSourceStorage } from '@hooks/useSourceStorage';
import { defaultUserAgentString } from '@utils/fetch/fetch';
import {
@@ -57,15 +56,12 @@ const NovelInfoHeader = ({
[],
);
const { cookies = '' } = getSourceStorage(novel.sourceId);
return (
<>
<CoverImage
source={{
uri: novel.novelCover,
headers: {
Cookie: cookies,
'User-Agent': defaultUserAgentString,
},
}}
@@ -77,7 +73,6 @@ const NovelInfoHeader = ({
source={{
uri: novel.novelCover,
headers: {
Cookie: cookies,
'User-Agent': defaultUserAgentString,
},
}}
@@ -34,12 +34,24 @@ const ChaptersSettingsSheet = ({
theme={theme}
label="Downloaded"
color={theme.primary}
status={filter.match('AND downloaded=1')}
onPress={() =>
status={
filter.match('AND downloaded=1')
? filterChapters(filter.replace(' AND downloaded=1', ''))
: filterChapters(filter + ' AND downloaded=1')
? true
: filter.match('AND downloaded=0')
? 'indeterminate'
: false
}
onPress={() => {
if (filter.match('AND downloaded=1')) {
filterChapters(
filter.replace(' AND downloaded=1', ' AND downloaded=0'),
);
} else if (filter.match('AND downloaded=0')) {
filterChapters(filter.replace(' AND downloaded=0', ''));
} else {
filterChapters(filter + ' AND downloaded=1');
}
}}
/>
<Checkbox
theme={theme}
+76 -6
View File
@@ -46,6 +46,8 @@ import { createDrawerNavigator } from '@react-navigation/drawer';
import { htmlToText } from '../../sources/helpers/htmlToText';
import ChapterLoadingScreen from './ChapterLoadingScreen/ChapterLoadingScreen';
import { ErrorScreenV2 } from '@components';
import { getString } from '@strings/translations';
import { useNavigation } from '@react-navigation/native';
const Chapter = ({ route }) => {
const DrawerNav = createDrawerNavigator();
@@ -82,9 +84,11 @@ const ChapterContent = ({ route, navigation }) => {
const theme = useTheme();
const dispatch = useDispatch();
const { navigate } = useNavigation();
const {
swipeGestures = false,
readerPages = false,
useVolumeButtons = false,
autoScroll = false,
autoScrollInterval = 10,
@@ -102,6 +106,7 @@ const ChapterContent = ({ route, navigation }) => {
const position = usePosition(novelId, chapterId);
const minScroll = useRef(0);
const pages = useRef(0);
const { tracker, trackedNovels } = useTrackerReducer();
const isTracked = trackedNovels.find(obj => obj.novelId === novelId);
@@ -211,13 +216,41 @@ const ChapterContent = ({ route, navigation }) => {
updateTracker();
}
});
useEffect(() => {
if (!readerPages) {
webViewRef?.current?.injectJavaScript(
"document.querySelector('chapter').style.transform = 'translate(0%)';",
);
}
}, [readerPages]);
const scrollTo = useCallback(
offsetY => {
offset => {
requestAnimationFrame(() => {
webViewRef?.current?.injectJavaScript(`(()=>{
window.scrollTo({top:${offsetY},behavior:'smooth'})
})()`);
webViewRef?.current?.injectJavaScript(
!readerPages
? `(()=>{
window.scrollTo({top:${offset},behavior:'smooth'})
})()`
: `(()=>{
document.querySelector('chapter').setAttribute('data-page', ${
offset / 100
});
document.querySelector("chapter").style.transform = 'translate(-${offset}%)';
window.ReactNativeWebView.postMessage(
JSON.stringify(
{
type:"scrollend",
data:{
offSetY: ${offset},
percentage: (${offset / 100 / pages.current} > 0 ? ${
offset / 100 / pages.current
} * 100 : 1),
}
}
)
);
})()`,
);
});
},
[webViewRef],
@@ -322,6 +355,14 @@ const ChapterContent = ({ route, navigation }) => {
}
}, []);
useEffect(() => {
return () => {
if (position?.percentage === 100) {
doSaveProgress(0, 0);
}
};
}, [position?.percentage]);
const scrollToSavedProgress = useCallback(
() => scrollTo(position?.position),
[],
@@ -349,7 +390,32 @@ const ChapterContent = ({ route, navigation }) => {
}
if (error) {
return <ErrorScreenV2 error={error} />;
return (
<ErrorScreenV2
error={error}
actions={[
{
iconName: 'refresh',
title: getString('common.retry'),
onPress: () => {
setError('');
setLoading(true);
getChapter(chapterId);
},
},
{
iconName: 'earth',
title: 'WebView',
onPress: () =>
navigate('WebviewScreen', {
sourceId,
name: `${chapterName} | ${novelName}`,
url: chapterUrl,
}),
},
]}
/>
);
}
return (
@@ -359,7 +425,9 @@ const ChapterContent = ({ route, navigation }) => {
html={chapterText}
chapterName={chapter.chapterName || chapterName}
swipeGestures={swipeGestures}
readerPages={readerPages}
minScroll={minScroll}
pages={pages}
nextChapter={nextChapter}
webViewRef={webViewRef}
onLayout={onLayout}
@@ -389,7 +457,9 @@ const ChapterContent = ({ route, navigation }) => {
/>
<ReaderSeekBar
theme={theme}
readerPages={readerPages}
minScroll={minScroll.current}
pages={pages.current}
verticalSeekbar={verticalSeekbar}
percentage={position?.percentage}
scrollTo={scrollTo}
@@ -8,7 +8,7 @@ import {
import React, { Ref, useMemo, useState } from 'react';
import color from 'color';
import { BottomSheetView, BottomSheetModal } from '@gorhom/bottom-sheet';
import { BottomSheetModal, BottomSheetScrollView } from '@gorhom/bottom-sheet';
import { defaultTo } from 'lodash-es';
import BottomSheet from '@components/BottomSheet/BottomSheet';
@@ -25,6 +25,8 @@ import ReaderTextAlignSelector from './ReaderTextAlignSelector';
import ReaderLineHeight from './ReaderLineHeight';
import ReaderFontPicker from './ReaderFontPicker';
import { overlay } from 'react-native-paper';
import { ScrollView } from 'react-native-gesture-handler';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
const ReaderTab: React.FC = () => {
return (
@@ -50,13 +52,14 @@ const GeneralTab: React.FC = () => {
useVolumeButtons = false,
scrollAmount = 200,
swipeGestures = false,
readerPages = false,
removeExtraParagraphSpacing = false,
addChapterNameInReader = false,
bionicReading = false,
} = useSettingsV1();
return (
<View>
<ScrollView>
<ReaderSheetPreferenceItem
label={getString('readerScreen.bottomSheet.fullscreen')}
onPress={() =>
@@ -105,6 +108,12 @@ const GeneralTab: React.FC = () => {
value={swipeGestures}
theme={theme}
/>
<ReaderSheetPreferenceItem
label={getString('readerScreen.bottomSheet.readerPages')}
onPress={() => dispatch(setAppSettings('readerPages', !readerPages))}
value={readerPages}
theme={theme}
/>
<ReaderSheetPreferenceItem
label={getString('readerScreen.bottomSheet.removeExtraSpacing')}
onPress={() =>
@@ -161,7 +170,7 @@ const GeneralTab: React.FC = () => {
value={bionicReading}
theme={theme}
/>
</View>
</ScrollView>
);
};
interface ReaderBottomSheetV2Props {
@@ -198,31 +207,37 @@ const ReaderBottomSheetV2: React.FC<ReaderBottomSheetV2Props> = ({
[],
);
const renderTabBar = (props: any) => (
<TabBar
{...props}
indicatorStyle={{ backgroundColor: theme.primary }}
style={[
{
backgroundColor: tabHeaderColor,
borderBottomColor: theme.outline,
borderBottomWidth: 1,
},
styles.tabBar,
]}
renderLabel={({ route, color }) => (
<Text style={{ color }}>{route.title}</Text>
)}
inactiveColor={theme.onSurfaceVariant}
activeColor={theme.primary}
pressColor={color(theme.primary).alpha(0.12).string()}
/>
);
const renderTabBar = (props: any) => {
return (
<TabBar
{...props}
indicatorStyle={{ backgroundColor: theme.primary }}
style={[
{
backgroundColor: tabHeaderColor,
borderBottomColor: theme.outline,
borderBottomWidth: 1,
},
styles.tabBar,
]}
renderLabel={({ route, color }) => (
<Text style={{ color }}>{route.title}</Text>
)}
inactiveColor={theme.onSurfaceVariant}
activeColor={theme.primary}
pressColor={color(theme.primary).alpha(0.12).string()}
/>
);
};
const { bottom } = useSafeAreaInsets();
return (
<BottomSheet bottomSheetRef={bottomSheetRef} snapPoints={[360, 600]}>
<BottomSheetView
style={[styles.bottomSheetContainer, { backgroundColor }]}
<BottomSheetScrollView
contentContainerStyle={[
styles.bottomSheetContainer,
{ backgroundColor },
{ marginBottom: bottom },
]}
>
<TabView
navigationState={{ index, routes }}
@@ -232,7 +247,7 @@ const ReaderBottomSheetV2: React.FC<ReaderBottomSheetV2Props> = ({
initialLayout={{ width: layout.width }}
style={styles.tabView}
/>
</BottomSheetView>
</BottomSheetScrollView>
</BottomSheet>
);
};
@@ -5,19 +5,39 @@ import color from 'color';
import Slider from '@react-native-community/slider';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useDeviceOrientation } from '@hooks/useDeviceOrientation';
import { ThemeColors } from '@theme/types';
const VerticalScrollbar = ({
interface IScrollbar {
theme: ThemeColors;
minScroll: number;
pages: number;
verticalSeekbar: boolean;
readerPages: boolean;
percentage?: number;
scrollTo: (offset: number) => void;
}
const VerticalScrollbar: React.FC<IScrollbar> = ({
theme,
minScroll = 0,
pages,
verticalSeekbar,
readerPages,
percentage,
scrollTo,
}) => {
const { bottom } = useSafeAreaInsets();
const onSlidingComplete = value => {
let offsetY =
((value - minScroll) * Dimensions.get('window').height) / minScroll;
scrollTo(offsetY);
const onSlidingComplete = (value: number) => {
let offset;
if (readerPages) {
let newPage = Math.round(pages * (value / 100)) * 100;
// pages = { ...pages, current: newPage };
offset = newPage;
} else {
offset =
((value - minScroll) * Dimensions.get('window').height) / minScroll;
}
scrollTo(offset);
};
const screenOrientation = useDeviceOrientation();
@@ -43,7 +63,7 @@ const VerticalScrollbar = ({
flex: 1,
height: 40,
}}
minimumValue={Math.round(minScroll)}
minimumValue={readerPages ? 1 : Math.round(minScroll)}
maximumValue={100}
step={1}
value={percentage || Math.round(minScroll)}
@@ -87,7 +107,7 @@ const VerticalScrollbar = ({
flex: 1,
height: 40,
}}
minimumValue={Math.round(minScroll)}
minimumValue={readerPages ? 1 : Math.round(minScroll)}
maximumValue={100}
step={1}
value={percentage || Math.round(minScroll)}
+65 -126
View File
@@ -10,6 +10,9 @@ import { useReaderSettings, useSettingsV1 } from '@redux/hooks';
import { getString } from '@strings/translations';
import { sourceManager } from '../../../sources/sourceManager';
import { createHorizontalReaderPages } from './stringCreators/horizontalReaderPages';
import { createStyles } from './stringCreators/createStyle';
import { createSwipeGestures } from './stringCreators/createSwipeGestures';
type WebViewPostEvent = {
type: string;
@@ -30,7 +33,9 @@ type WebViewReaderProps = {
html: string;
chapterName: string;
swipeGestures: boolean;
readerPages: boolean;
minScroll: React.MutableRefObject<number>;
pages: React.MutableRefObject<number>;
nextChapter: ChapterItem;
webViewRef: React.MutableRefObject<WebView>;
onPress(): void;
@@ -52,9 +57,11 @@ const WebViewReader: React.FC<WebViewReaderProps> = props => {
html,
chapterName,
swipeGestures,
readerPages,
minScroll,
nextChapter,
webViewRef,
pages,
onPress,
onLayout,
doSaveProgress,
@@ -112,6 +119,7 @@ const WebViewReader: React.FC<WebViewReaderProps> = props => {
if (event.data) {
const offSetY = Number(event.data?.offSetY);
const percentage = Math.round(Number(event.data?.percentage));
doSaveProgress(offSetY, percentage);
}
break;
@@ -121,6 +129,11 @@ const WebViewReader: React.FC<WebViewReaderProps> = props => {
minScroll.current = (layoutHeight / contentHeight) * 100;
}
break;
case 'pages':
if (event.data) {
pages.current = Number(event.data);
}
break;
}
}}
source={{
@@ -128,85 +141,13 @@ const WebViewReader: React.FC<WebViewReaderProps> = props => {
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<style>
html {
scroll-behavior: smooth;
overflow-x: hidden;
padding-top: ${StatusBar.currentHeight};
word-wrap: break-word;
}
body {
padding-left: ${readerSettings.padding}%;
padding-right: ${readerSettings.padding}%;
padding-bottom: 40px;
font-size: ${readerSettings.textSize}px;
color: ${readerSettings.textColor};
text-align: ${readerSettings.textAlign};
line-height: ${readerSettings.lineHeight};
font-family: ${readerSettings.fontFamily};
}
chapter{
display: block;
}
hr {
margin-top: 20px;
margin-bottom: 20px;
}
a {
color: ${theme.primary};
}
img {
display: block;
width: auto;
height: auto;
max-width: 100%;
}
img.load-icon {
display: block;
margin-inline: auto;
animation: rotation 1s infinite linear;
}
@keyframes rotation {
100% {
transform: rotate(360deg);
}
0% {
transform: rotate(0deg);
}
}
.nextButton,
.infoText {
width: 100%;
border-radius: 50px;
border-width: 1;
color: ${theme.onPrimary};
background-color: ${theme.primary};
font-family: ${readerSettings.fontFamily};
font-size: 16px;
border-width: 0;
}
.nextButton {
min-height: 40px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
padding: 0 16px;
}
.infoText {
background-color: transparent;
text-align:center;
border: none;
margin: 0px;
color: inherit;
padding-top: 16px;
padding-bottom: 16px;
}
.chapterCtn {
min-height: ${layoutHeight - 140};
margin-bottom: auto;
}
</style>
${createStyles(
StatusBar.currentHeight ?? 0,
readerSettings,
theme,
layoutHeight,
readerPages,
)}
<style>
${readerSettings.customCSS}
@@ -220,10 +161,22 @@ const WebViewReader: React.FC<WebViewReaderProps> = props => {
</style>
</head>
<body id='sourceId-${chapterInfo.sourceId}'>
<div class="chapterCtn" ${onClickWebViewPostMessage({
type: 'hide',
})}>
<div class="chapterCtn" ${
!readerPages &&
onClickWebViewPostMessage({
type: 'hide',
})
}>
<div id="left"></div>
<div id="right"></div>
<div id="middle" ${
readerPages &&
onClickWebViewPostMessage({
type: 'hide',
})
}></div>
<chapter
data-page=0
data-novel-id='${chapterInfo.novelId}'
data-chapter-id='${chapterInfo.chapterId}'
>
@@ -233,7 +186,29 @@ const WebViewReader: React.FC<WebViewReaderProps> = props => {
: ''
}
${html}
<p id="spacer"></p>
</chapter>
<div id="infoContainer" class="hide">
<div class="infoText">
${getString(
'readerScreen.finished',
)}: ${chapterName?.trim()}
</div>
${
nextChapter
? `<button class="nextButton"
${onClickWebViewPostMessage({
type: 'next',
})}>
Next: ${nextChapter.chapterName}
</button>`
: `<div class="infoText">${getString(
'readerScreen.noNextChapter',
)}</div>`
}
</div>
</div>
<script>
if(!document.querySelector("input[offline]") && ${!!headers}){
@@ -241,9 +216,8 @@ const WebViewReader: React.FC<WebViewReaderProps> = props => {
window.ReactNativeWebView.postMessage(JSON.stringify({type:"imgfile",data:img.getAttribute("delayed-src")}));
});
}
var scrollTimeout;
window.addEventListener("scroll", (event) => {
${!readerPages} && window.addEventListener("scroll", (event) => {
window.clearTimeout( scrollTimeout );
scrollTimeout = setTimeout(() => {
window.ReactNativeWebView.postMessage(
@@ -259,65 +233,31 @@ const WebViewReader: React.FC<WebViewReaderProps> = props => {
);
}, 100);
});
let sendHeightTimeout;
const sendHeight = (timeOut) => {
clearTimeout(sendHeightTimeout);
sendHeightTimeout = setTimeout(
window.ReactNativeWebView.postMessage(
JSON.stringify({type:"height",data:document.body.scrollHeight})
JSON.stringify({type:"height",data: document.body.scrollHeight})
), timeOut
);
}
sendHeight(1000);
</script>
<div class="infoText">
${getString(
'readerScreen.finished',
)}: ${chapterName?.trim()}
</div>
${
nextChapter
? `<button class="nextButton" ${onClickWebViewPostMessage(
{ type: 'next' },
)}>
Next: ${nextChapter.chapterName}
</button>`
: `<div class="infoText">${getString(
'readerScreen.noNextChapter',
)}</div>`
}
${
swipeGestures
? `
<script>
let initialX = null;
let initialY = null;
document.addEventListener("touchstart", e => {
initialX = e.changedTouches[0].screenX;
initialY = e.changedTouches[0].screenY;
});
document.addEventListener("touchend", e => {
let diffX = e.changedTouches[0].screenX - initialX;
let diffY = e.changedTouches[0].screenY - initialY;
if (Math.abs(diffX) > Math.abs(diffY) && Math.abs(diffX) > 10) {
e.preventDefault();
window.ReactNativeWebView.postMessage(JSON.stringify({type: diffX<0 ? "next" : "prev"}))
}
});
</script>`
: ''
}
${swipeGestures ? createSwipeGestures() : ''}
<script>
async function fn(){
// Position important to prevent layout bugs
${readerPages && createHorizontalReaderPages()}
let novelName = "${chapterInfo.novelName}";
let chapterName = "${chapterInfo.chapterName}";
let sourceId =${chapterInfo.sourceId};
let chapterId =${chapterInfo.chapterId};
let novelId =${chapterInfo.novelId};
let html = document.querySelector("chapter").innerHTML;
${readerSettings.customJS}
${readerSettings.customJS}
}
document.addEventListener("DOMContentLoaded", fn);
</script>
@@ -328,5 +268,4 @@ const WebViewReader: React.FC<WebViewReaderProps> = props => {
/>
);
};
export default React.memo(WebViewReader, isEqual);
@@ -0,0 +1,167 @@
import { ThemeColors } from '@theme/types';
export const createStyles = (
currentHeight: number,
readerSettings: {
theme: string;
textColor: string;
textSize: number;
textAlign: string;
padding: number;
fontFamily: string;
lineHeight: number;
customCSS: string;
customJS: string;
},
theme: ThemeColors,
layoutHeight: number,
readerPages: boolean,
) => `
<style>
html {
scroll-behavior: smooth;
overflow-x: hidden;
padding-top: ${currentHeight};
word-wrap: break-word;
}
body {
padding-bottom: 40px;
margin-left: 0;
margin-right: 0;
font-size: ${readerSettings.textSize}px;
color: ${readerSettings.textColor};
text-align: ${readerSettings.textAlign};
line-height: ${readerSettings.lineHeight};
font-family: ${readerSettings.fontFamily};
}
chapter{
display: block;
}
chapter > *{
padding-left: ${readerSettings.padding}%;
padding-right: ${readerSettings.padding}%;
}
hr {
margin-top: 20px;
margin-bottom: 20px;
}
a {
color: ${theme.primary};
}
img {
display: block;
width: auto;
height: auto;
max-width: 100%;
}
img.load-icon {
display: block;
margin-inline: auto;
animation: rotation 1s infinite linear;
}
@keyframes rotation {
100% {
transform: rotate(360deg);
}
0% {
transform: rotate(0deg);
}
}
#infoContainer {
padding-left: ${readerSettings.padding}%;
padding-right: ${readerSettings.padding}%;
}
.nextButton,
.infoText {
width: 100%;
border-radius: 50px;
border-width: 1;
color: ${theme.onPrimary};
background-color: ${theme.primary};
font-family: ${readerSettings.fontFamily};
font-size: 16px;
border-width: 0;
}
.nextButton {
min-height: 40px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
padding: 0 16px;
}
.infoText {
background-color: transparent;
text-align:center;
border: none;
margin: 0px;
color: inherit;
padding-top: 16px;
padding-bottom: 16px;
}
.chapterCtn {
min-height: ${layoutHeight - 140};
margin-bottom: auto;
}
${
readerPages &&
`
body {
overflow: hidden;
position: relative;
}
#left,
#right, #middle {
position: absolute;
height: 100%;
width: 35%;
top: 0;
z-index: 20
}
#middle {
left: 35%;
width: 30%;
}
#left {
left: 0;
}
#right {
right: 0;
}
#infoContainer {
position: absolute;
z-index: 1000;
bottom: 40;
width: calc(100% - 2*${readerSettings.padding}%)
}
.nextButton{
position: relative;
z-index: 100000 !important
}
chapter {
height: 100%;
display: flexbox;
flex-direction: column;
flex-wrap: wrap;
column-gap: 0;
column-width: 100vw;
transition: 200ms;
}
chapter > * {
width: calc(100% - 2*${readerSettings.padding}%) !important;
}
#spacer {
display: block;
min-height: 60px;
width: 100%;
}
.hide {
transform: translate(110%);
transition: 200ms
}
.show {
transform: translate(0%);
}
`
}
</style>`;
@@ -0,0 +1,18 @@
export function createSwipeGestures() {
return `<script>
let initialX = null;
let initialY = null;
document.addEventListener("touchstart", e => {
initialX = e.changedTouches[0].screenX;
initialY = e.changedTouches[0].screenY;
});
document.addEventListener("touchend", e => {
let diffX = e.changedTouches[0].screenX - initialX;
let diffY = e.changedTouches[0].screenY - initialY;
if (Math.abs(diffX) > Math.abs(diffY) && Math.abs(diffX) > 10) {
e.preventDefault();
window.ReactNativeWebView.postMessage(JSON.stringify({type: diffX<0 ? "next" : "prev"}))
}
});
</script>`;
}
@@ -0,0 +1,71 @@
export const createHorizontalReaderPages = () => {
return `
const id = (key) => {
return document.getElementById(key)
}
const select = (key) => {
return document.querySelector(key)
}
const chapter = select("chapter");
const clientWidth = document.documentElement.clientWidth;
const textWidth = chapter.scrollWidth;
const navLeft = id("left");
const navRight = id("right");
const infoBox = id("infoContainer");
infoBox.classList.add("hidden");
id("spacer").style.height = infoBox.scrollHeight + 'px';
const pages = (Math.ceil(textWidth / clientWidth) - 1);
let page = 0;
navRight.addEventListener("click", () => {
page = select("chapter").getAttribute('data-page');
if (isNaN(page)) {
page = 0;
}
if (page < pages ) {
page++;
movePage();
if (page === pages){
infoBox.classList.add("show")
}
}
});
navLeft.addEventListener("click", () => {
page = select("chapter").getAttribute('data-page');
if (page > 0) {
infoBox.classList.remove("show")
page--;
movePage();
}
})
function movePage(){
chapter.style.transform = 'translate(-'+page*100+'%)';
select('chapter').setAttribute('data-page',
page
);
window.ReactNativeWebView.postMessage(
JSON.stringify(
{
type:"scrollend",
data:{
offSetY: page * 100, percentage: page === 0 ? 1 : page / pages * 100,
}
}
)
);
}
let sendWidthTimeout;
const sendPages = (timeOut) => {
clearTimeout(sendHeightTimeout);
sendHeightTimeout = setTimeout(
window.ReactNativeWebView.postMessage(
JSON.stringify({type:"pages",data: pages})
), timeOut
);
}
sendPages(200);
;`;
};
+110 -100
View File
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { ScrollView } from 'react-native';
import { Dialog, Portal } from 'react-native-paper';
import { useTheme } from '@hooks/useTheme';
@@ -15,11 +15,15 @@ import { deleteReadChaptersFromDb } from '../../database/queries/DownloadQueries
import { openDirectory } from '../../native/epubParser';
import { Appbar, Button, List } from '@components';
import useSourceStorage from '@hooks/useSourceStorage';
import { ScreenContainer } from '@components/Common';
import CookieManager from '@react-native-cookies/cookies';
const AdvancedSettings = ({ navigation }) => {
const theme = useTheme();
const { clearCookies } = useSourceStorage({});
const clearCookies = () => {
CookieManager.clearAll();
showToast('Cookies cleared');
};
/**
* Confirm Clear Database Dialog
@@ -39,118 +43,124 @@ const AdvancedSettings = ({ navigation }) => {
} = useBoolean();
return (
<>
<ScreenContainer theme={theme}>
<Appbar
title="Advanced"
handleGoBack={() => navigation.goBack()}
theme={theme}
/>
<List.Section>
<List.SubHeader theme={theme}>Data Management</List.SubHeader>
<List.Item
title="Clear database"
description="Delete history for novels not in your library"
onPress={showClearDatabaseDialog}
theme={theme}
/>
<List.Item
title="Clear updates tab"
description="Clears chapter entries in updates tab"
onPress={showClearUpdatesDialog}
theme={theme}
/>
<List.Item
title="Clean up cached covers"
description="Delete all covers cached"
onPress={clearCoverCache}
theme={theme}
/>
<List.Item
title={getString('advancedSettings.deleteReadChapters')}
onPress={showDeleteReadChaptersDialog}
theme={theme}
/>
<List.Item title="Clear cookies" onPress={clearCookies} theme={theme} />
<List.Item
title="Import Epub"
onPress={() => openDirectory()}
theme={theme}
/>
</List.Section>
<Portal>
<ConfirmationDialog
title={getString('advancedSettings.deleteReadChaptersDialogTitle')}
visible={deleteReadChaptersDialog}
onSubmit={deleteReadChaptersFromDb}
onDismiss={hideDeleteReadChaptersDialog}
theme={theme}
/>
<Dialog
visible={clearDatabaseDialog}
onDismiss={hideClearDatabaseDialog}
style={{
borderRadius: 28,
backgroundColor: theme.overlay3,
}}
>
<Dialog.Title
<ScrollView>
<List.Section>
<List.SubHeader theme={theme}>Data Management</List.SubHeader>
<List.Item
title="Clear database"
description="Delete history for novels not in your library"
onPress={showClearDatabaseDialog}
theme={theme}
/>
<List.Item
title="Clear updates tab"
description="Clears chapter entries in updates tab"
onPress={showClearUpdatesDialog}
theme={theme}
/>
<List.Item
title="Clean up cached covers"
description="Delete all covers cached"
onPress={clearCoverCache}
theme={theme}
/>
<List.Item
title={getString('advancedSettings.deleteReadChapters')}
onPress={showDeleteReadChaptersDialog}
theme={theme}
/>
<List.Item
title="Clear cookies"
onPress={clearCookies}
theme={theme}
/>
<List.Item
title="Import Epub"
onPress={() => openDirectory()}
theme={theme}
/>
</List.Section>
<Portal>
<ConfirmationDialog
title={getString('advancedSettings.deleteReadChaptersDialogTitle')}
visible={deleteReadChaptersDialog}
onSubmit={deleteReadChaptersFromDb}
onDismiss={hideDeleteReadChaptersDialog}
theme={theme}
/>
<Dialog
visible={clearDatabaseDialog}
onDismiss={hideClearDatabaseDialog}
style={{
letterSpacing: 0,
fontSize: 16,
lineHeight: 16 * 1.5,
color: theme.onSurface,
borderRadius: 28,
backgroundColor: theme.overlay3,
}}
>
Are you sure? Read chapters and progress of non-library novels will
be lost.
</Dialog.Title>
<Dialog.Actions>
<Button onPress={hideClearDatabaseDialog}>
{getString('common.cancel')}
</Button>
<Button
onPress={() => {
deleteNovelCache();
hideClearDatabaseDialog();
<Dialog.Title
style={{
letterSpacing: 0,
fontSize: 16,
lineHeight: 16 * 1.5,
color: theme.onSurface,
}}
>
{getString('common.ok')}
</Button>
</Dialog.Actions>
</Dialog>
<Dialog
visible={clearUpdatesDialog}
onDismiss={hideClearUpdatesDialog}
style={{
borderRadius: 28,
backgroundColor: theme.overlay3,
}}
>
<Dialog.Title
Are you sure? Read chapters and progress of non-library novels
will be lost.
</Dialog.Title>
<Dialog.Actions>
<Button onPress={hideClearDatabaseDialog}>
{getString('common.cancel')}
</Button>
<Button
onPress={() => {
deleteNovelCache();
hideClearDatabaseDialog();
}}
>
{getString('common.ok')}
</Button>
</Dialog.Actions>
</Dialog>
<Dialog
visible={clearUpdatesDialog}
onDismiss={hideClearUpdatesDialog}
style={{
letterSpacing: 0,
fontSize: 16,
lineHeight: 16 * 1.5,
color: theme.onSurface,
borderRadius: 28,
backgroundColor: theme.overlay3,
}}
>
Are you sure? Updates tab will be cleared.
</Dialog.Title>
<Dialog.Actions>
<Button onPress={hideClearUpdatesDialog}>Cancel</Button>
<Button
onPress={() => {
clearUpdates();
showToast('Updates cleared.');
hideClearUpdatesDialog();
<Dialog.Title
style={{
letterSpacing: 0,
fontSize: 16,
lineHeight: 16 * 1.5,
color: theme.onSurface,
}}
>
Ok
</Button>
</Dialog.Actions>
</Dialog>
</Portal>
</>
Are you sure? Updates tab will be cleared.
</Dialog.Title>
<Dialog.Actions>
<Button onPress={hideClearUpdatesDialog}>Cancel</Button>
<Button
onPress={() => {
clearUpdates();
showToast('Updates cleared.');
hideClearUpdatesDialog();
}}
>
Ok
</Button>
</Dialog.Actions>
</Dialog>
</Portal>
</ScrollView>
</ScreenContainer>
);
};
@@ -62,6 +62,7 @@ const GenralSettings: React.FC<GenralSettingsProps> = ({ navigation }) => {
refreshNovelMetadata = false,
disableHapticFeedback = false,
useLibraryFAB = false,
jumpToLastReadChapter = false,
} = useSettings();
const { showLastUpdateTime = true } = useSelector(
@@ -215,6 +216,18 @@ const GenralSettings: React.FC<GenralSettingsProps> = ({ navigation }) => {
onPress={defaultChapterSortModal.setTrue}
theme={theme}
/>
<SwitchSetting
label={getString(
'moreScreen.settingsScreen.generalSettingsScreen.jumpToLastReadChapter',
)}
value={jumpToLastReadChapter}
onPress={() =>
dispatch(
setAppSettings('jumpToLastReadChapter', !jumpToLastReadChapter),
)
}
theme={theme}
/>
<List.Divider theme={theme} />
<List.SubHeader theme={theme}>
{getString(
@@ -24,6 +24,7 @@ const GeneralSettings: React.FC = () => {
useVolumeButtons = false,
verticalSeekbar = true,
swipeGestures = false,
readerPages = false,
autoScroll = false,
autoScrollInterval = 10,
autoScrollOffset = null,
@@ -76,6 +77,12 @@ const GeneralSettings: React.FC = () => {
}
theme={theme}
/>
<SwitchItem
label={getString('readerScreen.bottomSheet.readerPages')}
value={readerPages}
onPress={() => dispatch(setAppSettings('readerPages', !readerPages))}
theme={theme}
/>
<SwitchItem
label={getString('readerScreen.bottomSheet.autoscroll')}
value={autoScroll}
@@ -21,18 +21,16 @@ import { useAppDispatch } from '@redux/hooks';
import { useTheme } from '@hooks/useTheme';
import { noop } from 'lodash-es';
import { getSourceStorage } from '@hooks/useSourceStorage';
import { defaultUserAgentString } from '@utils/fetch/fetch';
const NovelCover = ({
uri,
navigateToNovel,
sourceId,
}: {
uri: string;
navigateToNovel: () => void;
sourceId: number;
}) => {
const { cookies = '' } = getSourceStorage(sourceId);
return (
<Pressable
onPress={navigateToNovel}
@@ -41,7 +39,7 @@ const NovelCover = ({
}}
>
<FastImage
source={{ uri, headers: { 'cookie': cookies } }}
source={{ uri, headers: { 'User-Agent': defaultUserAgentString } }}
style={styles.cover}
/>
</Pressable>
+35 -42
View File
@@ -42,56 +42,49 @@ const updateLibrary = async options => {
i++
) {
try {
if (BackgroundService.isRunning()) {
/**
* Update chapters
*/
await updateNovel(
libraryNovels[i].sourceId,
libraryNovels[i].novelUrl,
libraryNovels[i].novelId,
options,
);
/**
* Update chapters
*/
await updateNovel(
libraryNovels[i].sourceId,
libraryNovels[i].novelUrl,
libraryNovels[i].novelId,
options,
);
/**
* Update notification
*/
await BackgroundService.updateNotification({
taskTitle: libraryNovels[i].novelName,
taskDesc: '(' + (i + 1) + '/' + libraryNovels.length + ')',
progressBar: { max: libraryNovels.length, value: i + 1 },
});
/**
* Update notification
*/
await BackgroundService.updateNotification({
taskTitle: libraryNovels[i].novelName,
taskDesc: '(' + (i + 1) + '/' + libraryNovels.length + ')',
progressBar: { max: libraryNovels.length, value: i + 1 },
});
/**
* When updating library is finished
*/
if (libraryNovels.length === i + 1) {
resolve();
const nextNovelIndex = i + 1;
Notifications.scheduleNotificationAsync({
content: {
title: 'Library Updated',
body: libraryNovels.length + ' novels updated',
},
trigger: null,
});
}
const nextNovelIndex = i + 1;
if (
nextNovelIndex in libraryNovels &&
libraryNovels[nextNovelIndex].sourceId ===
libraryNovels[i].sourceId
) {
await sleep(taskData.delay);
}
if (
nextNovelIndex in libraryNovels &&
libraryNovels[nextNovelIndex].sourceId === libraryNovels[i].sourceId
) {
await sleep(taskData.delay);
}
} catch (error) {
showToast(libraryNovels[i].novelName + ': ' + error.message);
continue;
}
}
/**
* When updating library is finished
*/
resolve();
Notifications.scheduleNotificationAsync({
content: {
title: 'Library Updated',
body: libraryNovels.length + ' novels updated',
},
trigger: null,
});
});
if (libraryNovels.length > 0) {
+220 -167
View File
@@ -1,4 +1,6 @@
import { showToast } from '@hooks/showToast';
import { fetchHtml } from '@utils/fetch/fetch';
import * as cheerio from 'cheerio';
const sourceId = 165;
@@ -72,8 +74,8 @@ const parseNovelAndChapters = async novelUrl => {
genres.push(loadedCheerio(this).text());
});
if (genres && genres.length > 0) {
novel.genres = genres.join(', ');
if (genres.length) {
novel.genre = genres.join(', ');
}
// Table of Content is on a different page than the summary page
@@ -130,175 +132,186 @@ const parseNovelAndChapters = async novelUrl => {
};
const parseChapter = async (novelUrl, chapterUrl) => {
let chapterName, chapterText, hasNextPage;
let chapterName, chapterText, hasNextPage, pageHasNextPage, pageText, urlNext;
let pageNumber = 1;
const delay = ms => new Promise(res => setTimeout(res, ms));
/*
* TODO: Maybe there are other ways to get the translation table
* It is embed and encrypted inside readtool.js
* UPDATE: Decrypted, by skillgg
*/
const mapping_dict = {
'“': '「',
'’': '』',
'': '是',
'': '不',
'': '他',
'': '个',
'': '来',
'': '大',
'': '子',
'': '说',
'': '年',
'': '那',
'': '她',
'': '得',
'': '自',
'': '家',
'': '而',
'': '去',
'': '小',
'': '于',
'': '么',
'': '好',
'': '发',
'': '成',
'': '事',
'': '用',
'': '道',
'': '种',
'': '乳',
'': '茎',
'': '肉',
'': '胸',
'': '淫',
'': '射',
'': '骚',
'”': '」',
'': '的',
'': '了',
'': '人',
'': '有',
'': '上',
'': '到',
'': '地',
'': '中',
'': '生',
'': '着',
'': '和',
'': '出',
'': '里',
'': '以',
'': '可',
'': '过',
'': '能',
'': '多',
'': '心',
'': '之',
'': '看',
'': '当',
'': '只',
'': '把',
'': '第',
'': '想',
'': '开',
'': '阴',
'': '欲',
'': '交',
'': '私',
'': '臀',
'': '脱',
'': '唇',
'‘': '『',
'': '一',
'': '我',
'': '在',
'': '这',
'': '们',
'': '时',
'': '为',
'': '你',
'': '国',
'': '就',
'': '要',
'': '也',
'': '后',
'': '会',
'': '下',
'': '天',
'': '对',
'': '然',
'': '学',
'': '都',
'': '起',
'': '没',
'': '如',
'': '还',
'': '样',
'': '作',
'': '美',
'': '液',
'': '呻',
'': '性',
'': '穴',
'': '舔',
'': '裸',
'\u201c': '\u300c',
'\u201d': '\u300d',
'\u2018': '\u300e',
'\u2019': '\u300f',
'\ue82c': '\u7684',
'\ue836': '\u4e00',
'\ue852': '\u662f',
'\ue850': '\u4e86',
'\ue832': '\u6211',
'\ue812': '\u4e0d',
'\ue833': '\u4eba',
'\ue849': '\u5728',
'\ue821': '\u4ed6',
'\ue810': '\u6709',
'\ue84c': '\u8fd9',
'\ue815': '\u4e2a',
'\ue842': '\u4e0a',
'\ue82e': '\u4eec',
'\ue817': '\u6765',
'\ue835': '\u5230',
'\ue837': '\u65f6',
'\ue82d': '\u5927',
'\ue859': '\u5730',
'\ue85c': '\u4e3a',
'\ue82f': '\u5b50',
'\ue84d': '\u4e2d',
'\ue854': '\u4f60',
'\ue81e': '\u8bf4',
'\ue853': '\u751f',
'\ue80f': '\u56fd',
'\ue80e': '\u5e74',
'\ue813': '\u7740',
'\ue802': '\u5c31',
'\ue81a': '\u90a3',
'\ue83b': '\u548c',
'\ue851': '\u8981',
'\ue82a': '\u5979',
'\ue838': '\u51fa',
'\ue808': '\u4e5f',
'\ue83a': '\u5f97',
'\ue814': '\u91cc',
'\ue857': '\u540e',
'\ue855': '\u81ea',
'\ue800': '\u4ee5',
'\ue81b': '\u4f1a',
'\ue85f': '\u5bb6',
'\ue816': '\u53ef',
'\ue83e': '\u4e0b',
'\ue84f': '\u800c',
'\ue80b': '\u8fc7',
'\ue828': '\u5929',
'\ue843': '\u53bb',
'\ue806': '\u80fd',
'\ue81f': '\u5bf9',
'\ue834': '\u5c0f',
'\ue81c': '\u591a',
'\ue848': '\u7136',
'\ue830': '\u4e8e',
'\ue84b': '\u5fc3',
'\ue84a': '\u5b66',
'\ue85d': '\u4e48',
'\ue861': '\u4e4b',
'\ue809': '\u90fd',
'\ue80c': '\u597d',
'\ue84e': '\u770b',
'\ue858': '\u8d77',
'\ue840': '\u53d1',
'\ue85b': '\u5f53',
'\ue863': '\u6ca1',
'\ue839': '\u6210',
'\ue827': '\u53ea',
'\ue841': '\u5982',
'\ue805': '\u4e8b',
'\ue845': '\u628a',
'\ue820': '\u8fd8',
'\ue83c': '\u7528',
'\ue847': '\u7b2c',
'\ue819': '\u6837',
'\ue82b': '\u9053',
'\ue80a': '\u60f3',
'\ue822': '\u4f5c',
'\ue85e': '\u79cd',
'\ue801': '\u5f00',
'\ue856': '\u7f8e',
'\ue811': '\u4e73',
'\ue860': '\u9634',
'\ue80d': '\u6db2',
'\ue83f': '\u830e',
'\ue803': '\u6b32',
'\ue804': '\u547b',
'\ue825': '\u8089',
'\ue846': '\u4ea4',
'\ue85a': '\u6027',
'\ue831': '\u80f8',
'\ue81d': '\u79c1',
'\ue826': '\u7a74',
'\ue818': '\u6deb',
'\ue823': '\u81c0',
'\ue829': '\u8214',
'\ue807': '\u5c04',
'\ue862': '\u8131',
'\ue83d': '\u88f8',
'\ue824': '\u9a9a',
'\ue844': '\u5507',
};
const addPage = async pageCheerio => {
let pageText;
const formatPage = async () => {
// Remove JS
pageCheerio('#acontent .cgo').remove();
let style = pageCheerio('style:first').prop('innerHTML');
style = style.replace(/(.*?)\{.*?\}/g, '$1,').split(',');
style.push(style.pop().replace(/\}/, '.cgo'));
style.map(tag => pageCheerio(tag).remove());
// Load lazyloaded images
pageCheerio('#acontent img.imagecontent').each(function () {
// Sometimes images are either in data-src or src
const imgSrc =
pageCheerio(this).attr('data-src') || pageCheerio(this).attr('src');
if (imgSrc) {
// The original CDN URL is locked behind a CF-like challenge, switch the URL to bypass that
// There are no react-native-url-polyfill lib, can't use URL API
const regex = /\/\/.+\.com\//;
const imgUrl = imgSrc.replace(regex, '//img.linovelib.com/');
// Clean up img element
pageCheerio(this)
.attr('src', imgUrl)
.removeAttr('data-src')
.removeClass('lazyload');
}
});
pageCheerio('.atitle')
.next()
.find('img.imagecontent')
.each(function () {
// Sometimes images are either in data-src or src
const imgSrc =
pageCheerio(this).attr('data-src') || pageCheerio(this).attr('src');
if (imgSrc) {
// The original CDN URL is locked behind a CF-like challenge, switch the URL to bypass that
// There are no react-native-url-polyfill lib, can't use URL API
const regex = /\/\/.+\.com\//;
const imgUrl = imgSrc.replace(regex, '//img.linovelib.com/');
// Clean up img element
pageCheerio(this)
.attr('src', imgUrl)
.removeAttr('data-src')
.removeClass('lazyload')
.addClass('delayed-src');
}
});
// Recover the original character
pageText = pageCheerio('#acontent').html();
pageText = pageText.replace(/./g, char => mapping_dict[char] || char);
pageText = pageCheerio('.atitle').next().html();
pageText = pageText?.replace(/./g, char => mapping_dict[char] || char);
return Promise.resolve();
};
await formatPage();
chapterName ??=
pageCheerio('#atitle + h3').text() +
' — ' +
pageCheerio('#atitle').text();
chapterText += pageText;
chapterName =
pageCheerio('.atitle h3').text() + ' — ' + pageCheerio('#atitle').text();
if (chapterText === undefined) {
chapterText = '<h2>' + chapterName + '</h2>';
}
chapterText +=
pageText ||
'Chapter not found, Report at lnreader github/discord if you see this message';
};
const loadPage = async url => {
const body = await fetchHtml({ url, sourceId });
const pageCheerio = cheerio.load(body);
addPage(pageCheerio);
pageHasNextPage =
pageCheerio('#footlink > a[onclick$=ReadParams.url_next;]').text() ===
'下一页'
? true
: false;
await addPage(pageCheerio);
urlNext = pageCheerio('#aread script:first')
.prop('innerHTML')
.replace(/.*next:'(.*?)'.*/g, '$1');
pageHasNextPage = urlNext.match(/_/) ? true : false;
return { pageCheerio, pageHasNextPage };
};
let url = chapterUrl;
do {
const page = await loadPage(url);
await delay(1000);
hasNextPage = page.pageHasNextPage;
if (hasNextPage === true) {
pageNumber++;
@@ -318,51 +331,91 @@ const parseChapter = async (novelUrl, chapterUrl) => {
};
const searchNovels = async searchTerm => {
const url = `${baseUrl}/search.html?searchkey=` + encodeURI(searchTerm);
const body = await fetchHtml({ url, sourceId });
const loadedCheerio = cheerio.load(body);
const searchUrl = `${baseUrl}/search/`;
const Term = encodeURI(searchTerm);
let nextPage, noNextPage, deadEnd;
let pageNumber = 1;
const novels = [];
const loadSearchResults = function () {
loadedCheerio('.book-ol .book-layout').each(function () {
let novelUrl = loadedCheerio(this).attr('href');
const addPage = async (pageCheerio, redirect) => {
const loadSearchResults = function () {
pageCheerio('.book-ol .book-layout').each(function () {
let novelUrl = pageCheerio(this).attr('href');
if (novelUrl) {
const novelName = loadedCheerio(this).find('.book-title').text();
const novelCover = loadedCheerio(this)
.find('img.book-cover')
.attr('data-src');
novelUrl = baseUrl + novelUrl;
if (novelUrl) {
const novelName = pageCheerio(this).find('.book-title').text();
const novelCover = pageCheerio(this)
.find('img.book-cover')
.attr('data-src');
novelUrl = baseUrl + novelUrl;
const novel = {
url: novelUrl,
name: novelName,
cover: novelCover,
};
const novel = { sourceId, novelUrl, novelName, novelCover };
novels.push(novel);
}
});
novels.push(novel);
}
});
};
const novelResults = pageCheerio('.book-ol a.book-layout');
if (novelResults.length === 0) {
showToast('Bypass check by searching in Webview');
} else {
loadSearchResults();
}
if (redirect.length) {
novels.length = 0;
const novelName = pageCheerio('#bookDetailWrapper .book-title').text();
const novelCover = pageCheerio('#bookDetailWrapper img.book-cover').attr(
'src',
);
const novelUrl =
baseUrl +
pageCheerio('#btnReadBook').attr('href').slice(0, -8) +
'.html';
const novel = { sourceId, novelUrl, novelName, novelCover };
novels.push(novel);
}
};
const novelResults = loadedCheerio('.book-ol .book-layout');
const loadPage = async url => {
const body = await fetchHtml({ url, sourceId });
const pageCheerio = cheerio.load(body);
const redirect = pageCheerio('div.book-layout');
await addPage(pageCheerio, redirect);
nextPage = pageCheerio('.next').attr('href');
if (!nextPage) {
noNextPage === true;
} else {
noNextPage = nextPage === '#' ? true : false;
}
return { pageCheerio, noNextPage };
};
if (novelResults.length === 0) {
// console.log('Challenge');
} else {
loadSearchResults();
}
let url = `${searchUrl}${Term}_${pageNumber}.html`;
do {
const page = await loadPage(url);
deadEnd = page.noNextPage;
if (deadEnd === false) {
pageNumber++;
url = `${searchUrl}${Term}_${pageNumber}.html`;
}
} while (deadEnd === false);
return novels;
};
const headers = {
Referer: 'https://w.linovelib.com',
};
const LinovelibScraper = {
popularNovels,
parseNovelAndChapters,
parseChapter,
searchNovels,
headers,
};
export default LinovelibScraper;
+25 -25
View File
@@ -1,16 +1,11 @@
import { fetchHtml } from '@utils/fetch/fetch';
import * as cheerio from 'cheerio';
import {
SourceChapter,
SourceChapterItem,
SourceNovel,
SourceNovelItem,
} from '../types';
import { SourceChapterItem, SourceNovel, SourceNovelItem } from '../types';
const sourceId = 163;
const sourceName = 'novelsOnline';
// const baseUrl = 'https://novelsonline.net';
const baseUrl = 'https://novelsonline.net';
const searchNovels = async (searchTerm: string) => {
const result = await fetchHtml({
@@ -101,32 +96,37 @@ const parseNovelAndChapters = async (novelUrl: string) => {
return novel;
};
const popularNovels = async () => {
return { novels: [] as SourceNovelItem[] }; /** TO DO */
};
const popularNovels = async (page: number) => {
const url = `${baseUrl}/top-novel/${page}`;
const result = await fetchHtml({ sourceId, url });
let $ = cheerio.load(result);
const parseChapter = async (novelUrl: string, chapterUrl: string) => {
const result = await fetchHtml({ url: chapterUrl });
let loadedCheerio = cheerio.load(result);
const headers = $('div.top-novel-block');
return headers
.map((i, h) => {
const novelName = $(h).find('h2').text();
const novelUrl = $(h).find('a').attr('href');
const novelCover = $(h).find('img').attr('src');
const chapterName = loadedCheerio('h1').text();
const chapterText = loadedCheerio('#contentall').html() || '';
if (!novelUrl) {
return null;
}
const chapter: SourceChapter = {
sourceId,
novelUrl,
chapterUrl,
chapterName,
chapterText,
};
return chapter;
return {
sourceId,
novelName,
novelCover,
novelUrl,
} as SourceNovelItem;
})
.get()
.filter(sr => sr !== null);
};
const NovelsOnlineScraper = {
popularNovels,
parseNovelAndChapters,
parseChapter,
// parseChapter,
searchNovels,
};
+1
View File
@@ -127,6 +127,7 @@ const parseChapter = async (novelUrl: string, chapterUrl: string) => {
const body = await result.text();
const loadedCheerio = cheerio.load(body);
loadedCheerio('#chapter-content div').remove();
const chapterName = loadedCheerio('.chapter-title').attr('title');
const chapterText = loadedCheerio('#chapter-content').html() || '';
-187
View File
@@ -1,187 +0,0 @@
import * as cheerio from 'cheerio';
const baseUrl = 'https://freewebnovel.com/';
const popularNovels = async page => {
let url = baseUrl + 'completed-novel/' + page;
const result = await fetch(url);
const body = await result.text();
const loadedCheerio = cheerio.load(body);
let novels = [];
loadedCheerio('.li-row').each(function () {
const novelName = loadedCheerio(this).find('.tit').text();
const novelCover = loadedCheerio(this).find('img').attr('src');
let novelUrl = loadedCheerio(this)
.find('h3 > a')
.attr('href')
.replace('.html', '')
.slice(1);
const novel = {
sourceId: 13,
novelName,
novelCover,
novelUrl,
};
novels.push(novel);
});
return { novels };
};
const parseNovelAndChapters = async novelUrl => {
const url = `${baseUrl}${novelUrl.replace('/', '')}.html`;
const result = await fetch(url);
const body = await result.text();
const loadedCheerio = cheerio.load(body);
let novel = {};
novel.sourceId = 13;
novel.sourceName = 'FreeWebNovel';
novel.url = url;
novel.novelUrl = novelUrl;
novel.novelName = loadedCheerio('h1.tit').text();
novel.novelCover = loadedCheerio('.pic > img').attr('src');
novel.genre = loadedCheerio('[title=Genre]')
.next()
.text()
.replace(/[\t\n]/g, '');
novel.author = loadedCheerio('[title=Author]')
.next()
.text()
.replace(/[\t\n]/g, '');
novel.artist = null;
novel.status = loadedCheerio('[title=Status]')
.next()
.text()
.replace(/[\t\n]/g, '');
let novelSummary = loadedCheerio('.inner').text().trim();
novel.summary = novelSummary;
let novelChapters = [];
let latestChapter;
loadedCheerio('h3.tit').each(function (res) {
if (loadedCheerio(this).find('a').text() === novel.novelName) {
latestChapter = loadedCheerio(this)
.next()
.find('span.s3')
.text()
.match(/\d+/);
}
});
latestChapter = latestChapter[0];
for (let i = 1; i <= parseInt(latestChapter, 10); i++) {
const chapterName = 'Chapter ' + i;
const releaseDate = null;
const chapterUrl = 'chapter-' + i;
const chapter = { chapterName, releaseDate, chapterUrl };
novelChapters.push(chapter);
}
novel.chapters = novelChapters;
return novel;
};
const parseChapter = async (novelUrl, chapterUrl) => {
let novelId = novelUrl.replace('/', '');
const url = `${baseUrl}${novelId}/${chapterUrl}.html`;
const result = await fetch(url);
const body = await result.text();
const loadedCheerio = cheerio.load(body);
let chapterName = loadedCheerio('h1.tit').text();
let chapterText = loadedCheerio('div.txt').html();
const chapter = {
sourceId: 13,
novelUrl,
chapterUrl,
chapterName,
chapterText,
};
return chapter;
};
const searchNovels = async searchTerm => {
const url = baseUrl + 'search/';
const formData = new FormData();
formData.append('searchkey', searchTerm);
const result = await fetch(url, {
method: 'POST',
body: formData,
});
const body = await result.text();
const loadedCheerio = cheerio.load(body);
let novels = [];
loadedCheerio('.li-row > .li > .con').each(function () {
const novelName = loadedCheerio(this).find('.tit').text();
const novelCover = loadedCheerio(this)
.find('.pic > a > img')
.attr('data-cfsrc');
let novelUrl = loadedCheerio(this)
.find('h3 > a')
.attr('href')
.replace('.html', '')
.slice(1);
novelUrl += '/';
const novel = {
sourceId: 13,
novelName,
novelCover,
novelUrl,
};
novels.push(novel);
});
return novels;
};
const FreeWebNovelScraper = {
popularNovels,
parseNovelAndChapters,
parseChapter,
searchNovels,
};
export default FreeWebNovelScraper;
+122
View File
@@ -0,0 +1,122 @@
import { Status } from '../helpers/constants';
import * as cheerio from 'cheerio';
import {
SourceChapter,
SourceChapterItem,
SourceNovel,
SourceNovelItem,
} from '../types';
const sourceId = 13;
const sourceName = 'FreeWebNovel';
const baseUrl = 'https://freewebnovel.com';
const popularNovels = async (page: number, { showLatestNovels }) => {
const sort = showLatestNovels
? '/latest-release-novels/'
: '/completed-novels/';
const result = await fetch(baseUrl + sort + page).then(res => res.text());
const loadedCheerio = cheerio.load(result);
const novels: SourceNovelItem[] = loadedCheerio('.li-row')
.map((index, element) => ({
sourceId,
novelName: loadedCheerio(element).find('.tit').text(),
novelCover: loadedCheerio(element).find('img').attr('src'),
novelUrl: baseUrl + loadedCheerio(element).find('h3 > a').attr('href'),
}))
.get();
return { novels };
};
const parseNovelAndChapters = async (novelUrl: string) => {
const result = await fetch(novelUrl).then(res => res.text());
const loadedCheerio = cheerio.load(result);
const novel: SourceNovel = {
sourceId,
sourceName,
novelUrl,
url: novelUrl,
novelName: loadedCheerio('h1.tit').text(),
novelCover: loadedCheerio('.pic > img').attr('src'),
summary: loadedCheerio('.inner').text().trim(),
};
novel.genre = loadedCheerio('[title=Genre]')
.next()
.text()
.replace(/[\t\n]/g, '');
novel.author = loadedCheerio('[title=Author]')
.next()
.text()
.replace(/[\t\n]/g, '');
novel.status =
loadedCheerio('[title=Status]')
.next()
.text()
.replace(/[\t\n]/g, '') === 'OnGoing'
? Status.ONGOING
: Status.COMPLETED;
const chapters: SourceChapterItem[] = loadedCheerio('#idData > li > a')
.map((index, element) => ({
chapterName: loadedCheerio(element).attr('title') || 'Chapter ' + index,
releaseDate: null,
chapterUrl: baseUrl + loadedCheerio(element).attr('href'),
}))
.get();
novel.chapters = chapters;
return novel;
};
const parseChapter = async (novelUrl: string, chapterUrl: string) => {
const result = await fetch(chapterUrl).then(res => res.text());
const loadedCheerio = cheerio.load(result);
const chapter: SourceChapter = {
sourceId,
novelUrl,
chapterUrl,
chapterName: loadedCheerio('h1.tit').text(),
chapterText: loadedCheerio('div.txt').html(),
};
return chapter;
};
const searchNovels = async (searchTerm: string) => {
const result = await fetch(baseUrl + '/search/', {
method: 'POST',
body: 'searchkey=' + encodeURIComponent(searchTerm),
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
},
}).then(res => res.text());
const loadedCheerio = cheerio.load(result);
const novels: SourceNovelItem[] = loadedCheerio('.li-row > .li > .con')
.map((index, element) => ({
sourceId,
novelName: loadedCheerio(element).find('.tit').text(),
novelCover: loadedCheerio(element).find('.pic > a > img').attr('src'),
novelUrl: baseUrl + loadedCheerio(element).find('h3 > a').attr('href'),
}))
.get();
return novels;
};
const FreeWebNovelScraper = {
popularNovels,
parseNovelAndChapters,
parseChapter,
searchNovels,
};
export default FreeWebNovelScraper;
+137
View File
@@ -0,0 +1,137 @@
import { fetchHtml } from '@utils/fetch/fetch';
import * as cheerio from 'cheerio';
import { FilterInputs } from '../types/filterTypes';
const sourceId = 170;
const sourceName = 'I cant read japanese tl';
const baseUrl = 'https://icantreadjapanese.wordpress.com/';
const popularNovels = async (page, { filters }) => {
let link = `${baseUrl}`;
if (page === 1) {
link += (filters?.storyStatus ? filters.storyStatus : 'projects') + '/';
}
const body = await fetchHtml({ url: link, sourceId });
const loadedCheerio = cheerio.load(body);
let novels = [];
loadedCheerio('figure.wp-block-image').each(function () {
const novelName = loadedCheerio(this).find('a').text();
const novelCover = loadedCheerio(this).find('img').attr('src');
const novelUrl = loadedCheerio(this).find('a').attr('href');
const novel = { sourceId, novelName, novelCover, novelUrl };
novels.push(novel);
});
return { novels };
};
const parseNovelAndChapters = async novelUrl => {
const body = await fetchHtml({ url: novelUrl, sourceId: sourceId });
let loadedCheerio = cheerio.load(body);
let novel = {
sourceId,
sourceName,
url: novelUrl,
novelUrl,
};
novel.novelName = loadedCheerio('h1.entry-title').text().trim();
novel.novelCover = loadedCheerio('figure.wp-block-image > img').attr('src');
novel.author = loadedCheerio('.has-text-align-left')
.text()
.replace(/.*Author: (.*) \|.*/g, '$1');
novel.summary = loadedCheerio('.entry-content > p:first')
.nextUntil('hr:first')
.text();
let chapters = [];
loadedCheerio('.wp-block-column li a').each(function () {
let chapterName = loadedCheerio(this).text();
const releaseDate = null;
const chapterUrl = loadedCheerio(this).attr('href');
const volumeName = loadedCheerio(this)
.parent()
.prop('innerHTML')
.replace(/<a.*>/g, '')
.replace(/<br>/g, ' ')
.trim();
if (volumeName.length) {
chapterName = volumeName + ' - ' + chapterName;
}
chapters.push({ chapterName, releaseDate, chapterUrl });
});
novel.chapters = chapters;
return novel;
};
const parseChapter = async (novelUrl, chapterUrl) => {
const body = await fetchHtml({
url: chapterUrl,
sourceId: sourceId,
});
let loadedCheerio = cheerio.load(body);
const chapterName = loadedCheerio('h1.entry-title').text();
const chapterText = loadedCheerio('.entry-content').html();
const chapter = {
sourceId,
novelUrl,
chapterUrl,
chapterName,
chapterText,
};
return chapter;
};
const searchNovels = async () => {
showToast('Search is not implemented for this source');
return;
};
const filters = [
{
key: 'storyStatus',
label: 'Translation Status',
values: [
{ label: 'Ongoing Projects', value: 'projects' },
{
label: 'Maybe Will Read Again, Caught Up',
value: 'maybe-will-read-again-caught-up',
},
{ label: 'Dropped', value: 'dropped' },
{ label: 'Finished', value: 'finished' },
],
inputType: FilterInputs.Picker,
},
];
const ICantReadJPTLScraper = {
popularNovels,
parseNovelAndChapters,
parseChapter,
searchNovels,
filters,
};
export default ICantReadJPTLScraper;
+6 -4
View File
@@ -87,15 +87,17 @@ const parseNovelAndChapters = async novelUrl => {
const chaptersHtml = await fetchHtml({
url: chaptersUrl,
init: { headers },
sourceId,
});
loadedCheerio = cheerio.load(chaptersHtml);
loadedCheerio('.chapter-list li').each(function () {
const chapterName = loadedCheerio(this)
.find('.chapter-title')
.text()
.trim();
const chapterName =
'Chapter ' +
loadedCheerio(this).find('.chapter-no').text().trim() +
' - ';
loadedCheerio(this).find('.chapter-title').text().trim();
const releaseDate = loadedCheerio(this)
.find('.chapter-update')
+37 -34
View File
@@ -1,6 +1,5 @@
import * as cheerio from 'cheerio';
import { showToast } from '../../hooks/showToast';
import { htmlToText } from '../helpers/htmlToText';
const baseUrl = 'https://lnmtl.com/';
@@ -56,23 +55,26 @@ const parseNovelAndChapters = async novelUrl => {
novel.novelName = loadedCheerio('.novel-name').text();
novel.novelCover = loadedCheerio('div.novel').find('img').attr('src');
novel.novelCover = loadedCheerio('.novel').find('img').attr('src');
novel.summary = loadedCheerio('div.description').text().trim();
novel.summary = loadedCheerio('.description').text().trim();
novel.author = loadedCheerio(
'main > div:nth-child(3) > div > div.col-lg-3.col-md-4 > div:nth-child(2) > div.panel-body > dl:nth-child(1) > dd > a > span',
).text();
loadedCheerio('.panel-body > dl').each(function () {
let detailName = loadedCheerio(this).find('dt').text().trim();
let detail = loadedCheerio(this).find('dd').text().trim();
novel.status = loadedCheerio(
'main > div:nth-child(3) > div > div.col-lg-3.col-md-4 > div:nth-child(2) > div.panel-body > dl:last-child > dd',
)
.text()
.trim();
switch (detailName) {
case 'Authors':
novel.author = detail;
break;
case 'Current status':
novel.status = detail;
break;
}
});
novel.genre = loadedCheerio(
'main > div.container > div > div.col-lg-3.col-md-4 > div:nth-child(4) > div.panel-body > ul',
)
novel.genre = loadedCheerio('.panel-heading:contains(" Genres ")')
.next()
.text()
.trim()
.replace(/\s\s/g, ',');
@@ -128,17 +130,15 @@ const parseChapter = async (novelUrl, chapterUrl) => {
let chapterName = loadedCheerio('h3 > span.chapter-title').text().trim();
loadedCheerio('.original').remove();
loadedCheerio('.original, script').remove();
loadedCheerio('sentence.translated').wrap('<p></p>');
let chapterText = loadedCheerio('.chapter-body').html();
let chapterText = loadedCheerio('.chapter-body').html().replace(/„/g, '“');
if (!chapterText) {
chapterText = loadedCheerio('.alert.alert-warning').text();
}
chapterText =
chapterName + '\n\n' + htmlToText(chapterText, { removeLineBreaks: false });
const chapter = {
sourceId: 37,
novelUrl,
@@ -151,32 +151,35 @@ const parseChapter = async (novelUrl, chapterUrl) => {
};
const searchNovels = async searchTerm => {
const url = 'https://lnmtl.com/term';
const result = await fetch(url);
const result = await fetch(baseUrl);
const body = await result.text();
const loadedCheerio = cheerio.load(body);
let novels = loadedCheerio('footer')
const list = loadedCheerio('footer')
.next()
.next()
.html()
.match(/local: \[(.*?)\]/)[0]
.replace('local: ', '');
.match(/prefetch: '\/(.*json)/)[1];
novels = JSON.parse(novels);
const search = await fetch(`${baseUrl}${list}`);
const data = await search.json();
novels = novels.filter(novel =>
novel.name.toLowerCase().includes(searchTerm.toLowerCase()),
let nov = data.filter(res =>
res.name.toLowerCase().includes(searchTerm.toLowerCase()),
);
novels = novels.map(novel => ({
sourceId: 37,
novelName: novel.name,
novelUrl: novel.slug,
novelCover: novel.image,
}));
const novels = [];
nov.map(res => {
const novelName = res.name;
const novelUrl = res.slug;
const novelCover = res.image;
const novel = { sourceId: 37, novelName, novelUrl, novelCover };
novels.push(novel);
});
return novels;
};
@@ -2,8 +2,8 @@ import { fetchHtml } from '@utils/fetch/fetch';
import * as cheerio from 'cheerio';
const sourceId = 3;
const baseUrl = 'https://fastnovel.org';
const searchUrl = 'https://fastnovel.org/search/';
const baseUrl = 'https://novelbin.org';
const searchUrl = 'https://novelbin.org/search/';
const popularNovels = async page => {
const url = `${baseUrl}/sort/p/?page=${page}`;
@@ -42,7 +42,7 @@ const parseNovelAndChapters = async novelUrl => {
let novel = {
sourceId,
sourceName: 'FastNovel',
sourceName: 'NovelBin',
url,
novelUrl,
};
@@ -158,11 +158,11 @@ const searchNovels = async searchTerm => {
return novels;
};
const fastNovelScraper = {
const NovelBinScraper = {
popularNovels,
parseNovelAndChapters,
parseChapter,
searchNovels,
};
export default fastNovelScraper;
export default NovelBinScraper;
+36 -41
View File
@@ -1,5 +1,5 @@
import * as cheerio from 'cheerio';
import { fetchHtml } from '@utils/fetch/fetch';
import { fetchApi, fetchHtml } from '@utils/fetch/fetch';
const baseUrl = 'https://www.novelpub.com/';
@@ -9,18 +9,12 @@ const sourceId = 94;
const headers = new Headers({
Accept: 'application/json',
'Content-Type': 'application/json',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36',
});
const popularNovels = async page => {
let url = baseUrl + 'browse/all/popular/all/' + page;
const body = await fetchHtml({
url,
sourceId,
init: { headers: { 'User-Agent': userAgent } },
});
const body = await fetchHtml({ url, sourceId });
const loadedCheerio = cheerio.load(body);
@@ -46,48 +40,39 @@ const popularNovels = async page => {
const parseNovelAndChapters = async novelUrl => {
const url = novelUrl;
const body = await fetchHtml({
url,
sourceId,
init: { headers: { 'User-Agent': userAgent } },
});
const body = await fetchHtml({ url, sourceId });
let loadedCheerio = cheerio.load(body);
let novel = { url, novelUrl, sourceId, sourceName, genre: '' };
let novel = {
sourceId,
url: novelUrl,
novelUrl,
sourceName,
};
novel.novelName = loadedCheerio('h1.novel-title').text().trim();
novel.novelCover = loadedCheerio('figure.cover > img').attr('data-src');
loadedCheerio('div.categories > ul > li').each(function () {
novel.genre +=
loadedCheerio(this)
.text()
.replace(/[\t\n]/g, '') + ',';
});
novel.genre = loadedCheerio('.categories li')
.find('a')
.map((i, el) => loadedCheerio(el).text())
.toArray()
.join(',');
loadedCheerio('div.header-stats > span').each(function () {
if (loadedCheerio(this).find('small').text() === 'Status') {
novel.status = loadedCheerio(this).find('strong').text();
}
});
novel.genre = novel.genre.slice(0, -1);
novel.status = loadedCheerio('small:contains("Status")').prev().text().trim();
novel.author = loadedCheerio('.author > a > span').text();
loadedCheerio('.expand').remove();
novel.summary = loadedCheerio('.summary > .content').text().trim();
const delay = ms => new Promise(res => setTimeout(res, ms));
let lastPage = 1;
lastPage = loadedCheerio(
'#novel > header > div.header-body.container > div.novel-info > div.header-stats > span:nth-child(1) > strong',
)
.text()
?.trim();
lastPage = loadedCheerio('small:contains("Chapters")').prev().text().trim();
lastPage = Math.ceil(lastPage / 100);
@@ -97,8 +82,11 @@ const parseNovelAndChapters = async novelUrl => {
for (let i = 1; i <= lastPage; i++) {
const chaptersUrl = `${novelUrl}/chapters/page-${i}`;
const chaptersRequest = await fetch(chaptersUrl, { headers });
const chaptersHtml = await chaptersRequest.text();
const chaptersHtml = await fetchHtml({
url: chaptersUrl,
init: { headers },
sourceId,
});
loadedCheerio = cheerio.load(chaptersHtml);
@@ -133,11 +121,7 @@ const parseNovelAndChapters = async novelUrl => {
const parseChapter = async (novelUrl, chapterUrl) => {
const url = chapterUrl;
const body = await fetchHtml({
url,
sourceId,
init: { headers: { 'User-Agent': userAgent } },
});
const body = await fetchHtml({ url, sourceId });
const loadedCheerio = cheerio.load(body);
@@ -150,12 +134,23 @@ const parseChapter = async (novelUrl, chapterUrl) => {
};
const searchNovels = async searchTerm => {
const url = `${baseUrl}lnwsearchlive?inputContent=${searchTerm}`;
const url = `${baseUrl}lnsearchlive`;
const link = `${baseUrl}search`;
const response = await fetchApi({ url: link, sourceId }).then(r => r.text());
const token = cheerio.load(response);
let verifytoken = token('#novelSearchForm > input').attr('value');
let formData = new FormData();
formData.append('inputContent', searchTerm);
const body = await fetchHtml({
url,
init: {
method: 'POST',
headers: { 'LNRequestVerifyToken': verifytoken },
body: formData,
},
sourceId,
init: { headers: { 'User-Agent': userAgent } },
});
let loadedCheerio = cheerio.load(body);
+26 -7
View File
@@ -1,15 +1,20 @@
import * as cheerio from 'cheerio';
import { fetchApi, fetchHtml, cloudflareCheck } from '@utils/fetch/fetch';
import {
fetchApi,
fetchHtml,
cloudflareCheck,
defaultUserAgentString,
} from '@utils/fetch/fetch';
import { defaultTo } from 'lodash-es';
import { FilterInputs } from '../types/filterTypes';
const sourceId = 50;
const sourceName = 'Novel Updates';
const baseUrl = 'https://www.novelupdates.com/';
const userAgent =
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36';
const userAgent = defaultUserAgentString;
const getPopularNovelsUrl = (page, { showLatestNovels, filters }) => {
let url = `${baseUrl}${
@@ -105,7 +110,9 @@ const parseNovelAndChapters = async novelUrl => {
novel.novelName = loadedCheerio('.seriestitlenu').text();
novel.novelCover = loadedCheerio('.seriesimg > img').attr('src');
novel.novelCover =
loadedCheerio('.seriesimg > img').attr('src') ||
loadedCheerio('.serieseditimg > img').attr('src');
novel.author = loadedCheerio('#showauthors').text().trim();
novel.genre = loadedCheerio('#seriesgenre')
@@ -204,6 +211,12 @@ const parseChapter = async (novelUrl, chapterUrl) => {
let isWattpad = result.url.toLowerCase().includes('wattpad');
let isAnomalously = result.url.toLowerCase().includes('anotivereads');
let isBlossomTranslation = result.url
.toLowerCase()
.includes('blossomtranslation');
let isLightNovelsTls = result.url
.toLowerCase()
.includes('lightnovelstranslations');
@@ -241,22 +254,28 @@ const parseChapter = async (novelUrl, chapterUrl) => {
if (isWuxiaWorld) {
chapterText = loadedCheerio('#chapter-content').html();
} else if (isRainOfSnow) {
chapterText = loadedCheerio('div.content').html();
chapterText = loadedCheerio('.content').html();
} else if (isTumblr) {
chapterText = loadedCheerio('.post').html();
} else if (isBlogspot) {
loadedCheerio('.post-share-buttons').remove();
chapterText = loadedCheerio('.entry-content').html();
chapterText =
loadedCheerio('.entry-content').html() ||
loadedCheerio('#post-body').html();
} else if (isHostedNovel) {
chapterText = loadedCheerio('.chapter').html();
} else if (isScribbleHub) {
chapterText = loadedCheerio('div.chp_raw').html();
chapterText = loadedCheerio('.chp_raw').html();
} else if (isWattpad) {
chapterText = loadedCheerio('.container pre').html();
} else if (isTravisTranslation) {
chapterText = loadedCheerio('.reader-content').html();
} else if (isLightNovelsTls) {
chapterText = loadedCheerio('.text_story').html();
} else if (isBlossomTranslation) {
chapterText = loadedCheerio('.manga-child-content').html();
} else if (isAnomalously) {
chapterText = loadedCheerio('#comic').html();
} else if (isiNovelTranslation) {
const link = 'https://api.' + result.url.slice(8);
const json = await fetchApi({
+1 -1
View File
@@ -127,7 +127,7 @@ const parseChapter = async (novelUrl, chapterUrl) => {
const loadedCheerio = cheerio.load(body);
let chapterName = loadedCheerio('div.chapter-content').find('strong').text();
let chapterName = loadedCheerio('h1.font-white').text();
let chapterText = loadedCheerio('div.chapter-content').html();
+7 -7
View File
@@ -112,15 +112,15 @@ const parseNovelAndChapters = async novelUrl => {
};
const parseChapter = async (novelUrl, chapterUrl) => {
const url = `${baseUrl}series/${novelUrl}/${chapterUrl}/`;
const url = `${baseUrl}series/${novelUrl}${chapterUrl}`;
const result = await fetch(url);
const body = await result.text();
const loadedCheerio = cheerio.load(body);
const chapterName = loadedCheerio('h1#chapter-heading').text();
const chapterName = loadedCheerio('.reading-content b:first').text();
loadedCheerio('input, .reading-content b:first').remove();
let chapterText = loadedCheerio('.reading-content').html();
const chapter = {
@@ -144,11 +144,11 @@ const searchNovels = async searchTerm => {
let novels = [];
loadedCheerio('.c-tabs-item__content').each(function () {
const novelName = loadedCheerio(this).find('.h4 > a').text();
const novelCover = loadedCheerio(this).find('img').attr('src');
loadedCheerio('.tab-thumb').each(function () {
const novelName = loadedCheerio(this).find('a').attr('title');
const novelCover = loadedCheerio(this).find('img').attr('data-src');
let novelUrl = loadedCheerio(this).find('.h4 > a').attr('href');
let novelUrl = loadedCheerio(this).find('a').attr('href');
novelUrl = novelUrl.replace(`${baseUrl}series/`, '');
const novel = {
+17 -25
View File
@@ -23,12 +23,12 @@ const popularNovels = async page => {
if (novelName) {
const novelId = loadedCheerio(this).find('td:nth-child(1)').text();
const novelCover = baseUrl + '/data/image/' + novelId + '.jpg';
const novelCover = baseUrl + 'data/image/' + novelId + '.jpg';
let novelUrl = loadedCheerio(this)
.find('td.novel > a')
.attr('href')
.replace('/novel/', '');
.replace('/book/', '');
const novel = {
sourceId,
@@ -45,7 +45,7 @@ const popularNovels = async page => {
};
const parseNovelAndChapters = async novelUrl => {
const url = `${baseUrl}novel/${novelUrl}/`;
const url = `${baseUrl}book/${novelUrl}/`;
const result = await fetch(url);
const body = await result.text();
@@ -59,25 +59,19 @@ const parseNovelAndChapters = async novelUrl => {
novelUrl,
};
novel.novelName = loadedCheerio(
'body > div.container-fluid.text-center > div.row.content > div.col-sm-8.text-left > div:nth-child(6) > div.panel-heading.clearfix > h4',
).text();
novel.novelName = loadedCheerio('h4[itemprop="name>"]:first').text();
novel.novelCover = loadedCheerio('img[itemprop="image"]').attr('src');
novel.novelCover = loadedCheerio('.img-thumbnail').attr('src');
novel.author = loadedCheerio(
'div.col-md-6 > div > div:nth-child(2) > div > div:nth-child(2) > a',
).text();
novel.author = loadedCheerio('h4:contains("Author:")').next().text();
novel.genre = loadedCheerio(
'div.row > div.col-md-6 > div > a:nth-child(4)',
).text();
novel.summary = loadedCheerio('div[itemprop="description"]')
.find('p')
.text()
.trim();
novel.genre = loadedCheerio('h4:contains("Genre:")')
.nextUntil('h4')
.map((i, el) => loadedCheerio(el).text())
.toArray()
.join(',');
novel.summary = loadedCheerio('h4:contains("Description:")').next().text();
let novelChapters = [];
loadedCheerio('table#chplist > #chapters > tr').each(function () {
@@ -137,20 +131,18 @@ const parseNovelAndChapters = async novelUrl => {
};
const parseChapter = async (novelUrl, chapterUrl) => {
const url = `${baseUrl}novel/${novelUrl}/${chapterUrl}`;
const url = `${baseUrl}book/${novelUrl}/${chapterUrl}`;
const result = await fetch(url);
const body = await result.text();
const loadedCheerio = cheerio.load(body);
let chapterName = loadedCheerio('div.panel-body.article').find('h4').text();
let chapterName = loadedCheerio('.panel-body.article').find('h4').text();
loadedCheerio('ul.pager').remove();
loadedCheerio('ul.pager, .panel-body.article div,button,span').remove();
let chapterText = loadedCheerio('div.panel-body.article')
.html()
.replace(/<span(.*?)>(.*?)<\/span>/g, '');
let chapterText = loadedCheerio('.panel-body.article').html();
const chapter = {
sourceId,
@@ -184,7 +176,7 @@ const searchNovels = async searchTerm => {
let novelUrl = loadedCheerio(this)
.find('a')
.attr('href')
.replace(baseUrl + 'novel/', '');
.replace(baseUrl + 'book/', '');
const novel = {
sourceId,
+193
View File
@@ -0,0 +1,193 @@
import * as cheerio from 'cheerio';
const baseUrl = 'https://es.mtlnovel.com';
const popularNovels = async page => {
const url = `${baseUrl}/alltime-rank/page/${page}`;
// console.log(url);
let headers = new Headers({
referer: 'https://es.mtlnovel.com/',
'User-Agent':
"'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36",
});
const result = await fetch(url, {
method: 'GET',
headers: headers,
});
const body = await result.text();
const loadedCheerio = cheerio.load(body);
let novels = [];
loadedCheerio('div.box.wide').each(function () {
const novelName = loadedCheerio(this).find('a.list-title').text().slice(4);
const novelCover = loadedCheerio(this).find('amp-img').attr('src');
let novelUrl = loadedCheerio(this).find('a.list-title').attr('href');
novelUrl = novelUrl.replace('https://es.mtlnovel.com/', '');
const novel = {
sourceId: 173,
novelUrl,
novelName,
novelCover,
};
novels.push(novel);
});
return { novels };
};
const parseNovelAndChapters = async novelUrl => {
const url = `${baseUrl}/${novelUrl}`;
let headers = new Headers({
referer: 'https://es.mtlnovel.com/alltime-rank/',
'User-Agent':
"'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36",
});
const result = await fetch(url, {
method: 'GET',
headers: headers,
});
const body = await result.text();
let loadedCheerio = cheerio.load(body);
let novel = {};
novel.sourceId = 173;
novel.sourceName = 'Esmtlnovel';
novel.url = url;
novel.novelUrl = novelUrl;
novel.novelName = loadedCheerio('h1.entry-title').text();
novel.novelCover = loadedCheerio('.nov-head > amp-img').attr('src');
novel.summary = loadedCheerio('div.desc > h2').next().text().trim();
novel.author = loadedCheerio('tr > td')
.filter(function () {
return loadedCheerio(this).prev().text().trim() === 'Author';
})
.next()
.text()
.replace('Auhtor:', '');
novel.status = loadedCheerio('tr > td')
.filter(function () {
return loadedCheerio(this).prev().text().trim() === 'Status';
})
.next()
.text()
.replace('Status:', '');
novel.genre = loadedCheerio('td')
.filter(function () {
return loadedCheerio(this).prev().text().trim() === 'Genre';
})
.next()
.text()
.replace('Genre:', '');
novel.artist = null;
const chapterListUrl = url + '/chapter-list/';
const getChapters = async () => {
const listResult = await fetch(chapterListUrl);
const listBody = await listResult.text();
loadedCheerio = cheerio.load(listBody);
let novelChapters = [];
loadedCheerio('div.ch-list')
.find('a.ch-link')
.each(function () {
const chapterName = loadedCheerio(this).text().replace('~ ', '');
const releaseDate = null;
let chapterUrl = loadedCheerio(this).attr('href');
chapterUrl = chapterUrl.replace(
`https://es.mtlnovel.com/${novelUrl}`,
'',
);
novelChapters.push({
chapterUrl,
chapterName,
releaseDate,
});
});
return novelChapters.reverse();
};
novel.chapters = await getChapters();
return novel;
};
const parseChapter = async (novelUrl, chapterUrl) => {
const url = `${baseUrl}/${novelUrl}${chapterUrl}`;
// console.log(url);
const result = await fetch(url);
const body = await result.text();
const loadedCheerio = cheerio.load(body);
const chapterName = loadedCheerio('h1.main-title').text();
let chapterText = loadedCheerio('div.par').html();
const chapter = {
sourceId: 173,
novelUrl,
chapterUrl,
chapterName,
chapterText,
};
return chapter;
};
const searchNovels = async searchTerm => {
const searchUrl =
'https://es.mtlnovel.com/wp-admin/admin-ajax.php?action=autosuggest&q=' +
searchTerm +
'&__amp_source_origin=https%3A%2F%2Fes.mtlnovel.com';
const res = await fetch(searchUrl);
const result = await res.json();
let novels = [];
result.items[0].results.map(item => {
const novelName = item.title.replace(/<\/?strong>/g, '');
const novelCover = item.thumbnail;
const novelUrl = item.permalink.replace('https://es.mtlnovel.com/', '');
const novel = { sourceId: 173, novelName, novelCover, novelUrl };
novels.push(novel);
});
return novels;
};
const mtlNovelEsScraper = {
popularNovels,
parseNovelAndChapters,
parseChapter,
searchNovels,
};
export default mtlNovelEsScraper;
+1 -1
View File
@@ -91,7 +91,7 @@ const parseChapter = async (novUrl, chapUrl) => {
let chapterName = item.chp_index_title;
let chapterText = item.chp_content;
let chapterText = item.chp_content.replace(/\n/g, '<br>');
novelUrl = novelId + '/' + novelUrl + '/';
chapterUrl = item.id + '/' + item.chp_name;
+105 -37
View File
@@ -1,6 +1,7 @@
import * as cheerio from 'cheerio';
import { defaultCoverUri, Status } from '../helpers/constants';
import { fetchHtml } from '@utils/fetch/fetch';
import { showToast } from '@hooks/showToast';
const sourceId = 23;
const sourceName = 'TuNovelaLigera';
@@ -94,58 +95,125 @@ const parseNovelAndChapters = async novelUrl => {
let novelChapters = [];
const novelId =
loadedCheerio('.rating-post-id').attr('value') ||
loadedCheerio('#manga-chapters-holder').attr('data-id');
const delay = ms => new Promise(res => setTimeout(res, ms));
let lastPage = 1;
lastPage = loadedCheerio('.lcp_paginator li:last').prev().text().trim();
let formData = new FormData();
formData.append('action', 'manga_get_chapters');
formData.append('manga', novelId);
const getChapters = async () => {
const chaptersAjax = `${baseUrl}wp-admin/admin-ajax.php`;
showToast('Cargando desde Archivo...');
const text = await fetchHtml({
url: 'https://tunovelaligera.com/wp-admin/admin-ajax.php',
init: {
method: 'POST',
body: formData,
},
});
let formData = new FormData();
formData.append('action', 'madara_load_more');
formData.append('page', '0');
formData.append('template', 'html/loop/content');
formData.append('vars[category_name]', novelUrl.slice(0, -1));
formData.append('vars[posts_per_page]', '10000');
loadedCheerio = cheerio.load(text);
const formBody = await fetchHtml({
url: chaptersAjax,
init: {
method: 'POST',
body: formData,
},
sourceId,
});
loadedCheerio('.wp-manga-chapter').each(function () {
const chapterName = loadedCheerio(this)
.find('a')
.text()
.replace(/[\t\n]/g, '')
.trim();
const loadedCheerio = cheerio.load(formBody);
const releaseDate = loadedCheerio(this).find('span').text().trim();
loadedCheerio('.heading').each((i, el) => {
const chapterName = loadedCheerio(el)
.text()
.replace(/[\t\n]/g, '')
.trim();
const releaseDate = null;
let chapterUrl = loadedCheerio(el).find('a').attr('href');
chapterUrl = chapterUrl.replace(`${baseUrl}${novelUrl}`, '');
let chapterUrl = loadedCheerio(this).find('a').attr('href').split('/');
novelChapters.push({ chapterName, releaseDate, chapterUrl });
});
return novelChapters.reverse();
};
chapterUrl[6]
? (chapterUrl = chapterUrl[5] + '/' + chapterUrl[6])
: (chapterUrl = chapterUrl[5]);
const getPageChapters = async () => {
for (let i = 1; i <= lastPage; i++) {
const chaptersUrl = `${baseUrl}novelas/${novelUrl}?lcp_page0=${i}`;
showToast(`Cargando desde la página ${i}/${lastPage}...`);
const chaptersHtml = await fetchHtml({
url: chaptersUrl,
sourceId,
});
novelChapters.push({ chapterName, releaseDate, chapterUrl });
});
loadedCheerio = cheerio.load(chaptersHtml);
loadedCheerio('h2:contains("Resumen")')
.closest('div')
.next()
.find('ul:first li')
.each((i, el) => {
const chapterName = loadedCheerio(el)
.find('a')
.text()
.replace(/[\t\n]/g, '')
.trim();
novel.chapters = novelChapters.reverse();
const releaseDate = loadedCheerio(el).find('span').text().trim();
const chapterUrl = loadedCheerio(el).find('a').attr('href');
novelChapters.push({ chapterName, releaseDate, chapterUrl });
});
await delay(2000);
}
return novelChapters.reverse();
};
// novel.chapters = await getChapters();
// if (!novel.chapters.length) {
// showToast('¡Archivo no encontrado!');
// await delay(1000);
//
// }
novel.chapters = await getPageChapters();
return novel;
};
const parseChapter = async (novelUrl, chapterUrl) => {
const url = `${baseUrl}novelas/${novelUrl}/${chapterUrl}`;
const url = `${baseUrl}${novelUrl}${chapterUrl}`;
const body = await fetchHtml({ url });
const body = await fetchHtml({ url, sourceId });
let loadedCheerio = cheerio.load(body);
const loadedCheerio = cheerio.load(body);
let chapterName = loadedCheerio('h1#chapter-heading').text();
const chapterName = loadedCheerio('h1#chapter-heading').text();
let chapterText = loadedCheerio('.text-left').html();
novelUrl = novelUrl + '/';
let pageText = loadedCheerio('li:contains("A")').closest('div').next();
let cleanup = [];
pageText.find('div').each((i, el) => {
let hb = loadedCheerio(el).attr('id')?.match(/hb.*/);
if (!hb) {
return;
}
let idAttr = `div[id="${hb}"]`;
cleanup.push(idAttr);
});
cleanup.push(
'center',
'.clear',
'.code-block',
'.ai-viewport-2',
'.cbxwpbkmarkwrap',
'.flagcontent-form-container',
'strong:last',
);
cleanup.map(tag => pageText.find(tag).remove());
pageText.find('a, span').removeAttr();
const chapterText = pageText.html();
const chapter = {
sourceId,
@@ -161,11 +229,11 @@ const parseChapter = async (novelUrl, chapterUrl) => {
const searchNovels = async searchTerm => {
const url = `${baseUrl}?s=${searchTerm}&post_type=wp-manga`;
const body = await fetchHtml({ url });
const body = await fetchHtml({ url, sourceId });
let loadedCheerio = cheerio.load(body);
const loadedCheerio = cheerio.load(body);
let novels = [];
const novels = [];
loadedCheerio('.c-tabs-item__content').each(function () {
const novelName = loadedCheerio(this).find('.h4 > a').text();
@@ -1,13 +1,10 @@
import { getSourceStorage } from '@hooks/useSourceStorage';
import { defaultUserAgentString, fetchApi } from '@utils/fetch/fetch';
import { Cheerio, CheerioAPI, Element } from 'cheerio';
export const bypassImages = async (
loadedCheerio: CheerioAPI,
element: Cheerio<Element>,
sourceId: number,
): Promise<string> => {
const { cookies = '' } = getSourceStorage(sourceId);
let promises: Promise<number>[] = [];
element.find('noscript').each(function () {
@@ -28,7 +25,7 @@ export const bypassImages = async (
const response = await fetchApi({
url: attr.value,
init: {
headers: { Cookie: cookies, 'User-Agent': defaultUserAgentString },
headers: { 'User-Agent': defaultUserAgentString },
},
});
+1 -1
View File
@@ -106,7 +106,7 @@ const parseChapter = async (novelUrl, chapterUrl) => {
let loadedCheerio = cheerio.load(body);
const chapterName = loadedCheerio('.title-chapter').text();
const chapterText = loadedCheerio('.reader').html();
const chapterText = loadedCheerio('.entry-pagination').next().html();
const chapter = {
sourceId,
+1 -1
View File
@@ -101,7 +101,7 @@ const parseChapter = async (novelUrl, chapterUrl) => {
let loadedCheerio = cheerio.load(body);
const chapterName = loadedCheerio('.title-chapter').text();
const chapterText = loadedCheerio('.readers').html();
const chapterText = loadedCheerio('.reader-setting').next().next().html();
const chapter = {
sourceId,
@@ -0,0 +1,106 @@
import { FilterInputs } from '../../types/filterTypes';
import MultiSrcScraper from './IfreedomScraper';
const defaultFilter = [
{
key: 'sort',
inputType: FilterInputs.Picker,
label: 'Сортировка:',
values: [
{ label: 'По дате добавления', value: 'По дате добавления' },
{ label: 'По дате обновления', value: 'По дате обновления' },
{ label: 'По количеству глав', value: 'По количеству глав' },
{ label: 'По названию', value: 'По названию' },
{ label: 'По просмотрам', value: 'По просмотрам' },
{ label: 'По рейтингу', value: 'По рейтингу' },
],
},
{
key: 'status',
inputType: FilterInputs.Checkbox,
label: 'Статус:',
values: [
{ label: 'Перевод активен', value: 'Перевод активен' },
{ label: 'Перевод приостановлен', value: 'Перевод приостановлен' },
{ label: 'Произведение завершено', value: 'Произведение завершено' },
],
},
{
key: 'lang',
inputType: FilterInputs.Checkbox,
label: 'Язык:',
values: [
{ label: 'Английский', value: 'Английский' },
{ label: 'Китайский', value: 'Китайский' },
{ label: 'Корейский', value: 'Корейский' },
{ label: 'Японский', value: 'Японский' },
],
},
{
key: 'genre',
inputType: FilterInputs.Checkbox,
label: 'Жанры:',
values: [
{ label: 'Боевик', value: 'Боевик' },
{ label: 'Боевые Искусства', value: 'Боевые Искусства' },
{ label: 'Вампиры', value: 'Вампиры' },
{ label: 'Виртуальный Мир', value: 'Виртуальный Мир' },
{ label: 'Гарем', value: 'Гарем' },
{ label: 'Героическое фэнтези', value: 'Героическое фэнтези' },
{ label: 'Детектив', value: 'Детектив' },
{ label: 'Дзёсэй', value: 'Дзёсэй' },
{ label: 'Драма', value: 'Драма' },
{ label: 'Игра', value: 'Игра' },
{ label: 'История', value: 'История' },
{ label: 'Киберпанк', value: 'Киберпанк' },
{ label: 'Комедия', value: 'Комедия' },
{ label: 'ЛитРПГ', value: 'ЛитРПГ' },
{ label: 'Меха', value: 'Меха' },
{ label: 'Милитари', value: 'Милитари' },
{ label: 'Мистика', value: 'Мистика' },
{ label: 'Научная Фантастика', value: 'Научная Фантастика' },
{ label: 'Повседневность', value: 'Повседневность' },
{ label: 'Постапокалипсис', value: 'Постапокалипсис' },
{ label: 'Приключения', value: 'Приключения' },
{ label: 'Психология', value: 'Психология' },
{ label: 'Романтика', value: 'Романтика' },
{ label: 'Сверхъестественное', value: 'Сверхъестественное' },
{ label: 'Сёдзё', value: 'Сёдзё' },
{ label: 'Сёнэн', value: 'Сёнэн' },
{ label: 'Сёнэн-ай', value: 'Сёнэн-ай' },
{ label: 'Спорт', value: 'Спорт' },
{ label: 'Сэйнэн', value: 'Сэйнэн' },
{ label: 'Сюаньхуа', value: 'Сюаньхуа' },
{ label: 'Трагедия', value: 'Трагедия' },
{ label: 'Триллер', value: 'Триллер' },
{ label: 'Ужасы', value: 'Ужасы' },
{ label: 'Фантастика', value: 'Фантастика' },
{ label: 'Фэнтези', value: 'Фэнтези' },
{ label: 'Школьная жизнь', value: 'Школьная жизнь' },
{ label: 'Экшн', value: 'Экшн' },
{ label: 'Эротика', value: 'Эротика' },
{ label: 'Этти', value: 'Этти' },
{ label: 'Яой', value: 'Яой' },
{ label: 'Adult', value: 'Adult' },
{ label: 'Mature', value: 'Mature' },
{ label: 'Xianxia', value: 'Xianxia' },
{ label: 'Xuanhuan', value: 'Xuanhuan' },
],
},
];
const IfreedomScraper = new MultiSrcScraper(
171,
'https://ifreedom.su',
'Свободный Мир Ранобэ',
defaultFilter,
);
const BookhamsterScraper = new MultiSrcScraper(
172,
'https://bookhamster.ru',
'Bookhamster',
defaultFilter,
);
export { IfreedomScraper, BookhamsterScraper };
@@ -0,0 +1,157 @@
import { Status } from '../../helpers/constants';
import * as cheerio from 'cheerio';
class IfreedomScraper {
constructor(sourceId, baseUrl, sourceName, filters) {
this.sourceId = sourceId;
this.baseUrl = baseUrl;
this.sourceName = sourceName;
this.filters = filters;
}
async popularNovels(page, { showLatestNovels, filters }) {
let url =
this.baseUrl +
'/vse-knigi/?sort=' +
(showLatestNovels
? 'По дате обновления'
: filters?.sort || 'По рейтингу');
if (filters?.status?.length) {
url += filters.status.map(i => '&status[]=' + i).join('');
}
if (filters?.lang?.length) {
url += filters.lang.map(i => '&lang[]=' + i).join('');
}
if (filters?.genre?.length) {
url += filters.genre.map(i => '&genre[]=' + i).join('');
}
url += '&bpage=' + page;
const body = await fetch(url).then(res => res.text());
const loadedCheerio = cheerio.load(body);
const novels = loadedCheerio('div.one-book-home > div.img-home a')
.map((index, element) => ({
sourceId: this.sourceId,
novelName: loadedCheerio(element).attr('title'),
novelCover: loadedCheerio(element).find('img').attr('src'),
novelUrl: loadedCheerio(element).attr('href'),
}))
.get()
.filter(novel => novel.novelName && novel.novelUrl);
return { novels };
}
async parseNovelAndChapters(novelUrl) {
const body = await fetch(novelUrl).then(res => res.text());
const loadedCheerio = cheerio.load(body);
const novel = {
sourceId: this.sourceId,
sourceName: this.sourceName,
url: novelUrl,
novelUrl,
novelName: loadedCheerio('.entry-title').text(),
novelCover: loadedCheerio('.img-ranobe > img').attr('src'),
summary: loadedCheerio('meta[name="description"]').attr('content'),
};
loadedCheerio('div.data-ranobe').each(function () {
switch (loadedCheerio(this).find('b').text()) {
case 'Автор':
novel.author = loadedCheerio(this)
.find('div.data-value')
.text()
.trim();
break;
case 'Жанры':
novel.genre = loadedCheerio('div.data-value > a')
.map((index, element) => loadedCheerio(element).text()?.trim())
.get()
.join(',');
break;
case 'Статус книги':
novel.status = loadedCheerio('div.data-value')
.text()
.includes('активен')
? Status.ONGOING
: Status.COMPLETED;
break;
}
});
if (novel.author === 'Не указан') {
delete novel.author;
}
const chapters = [];
loadedCheerio('div.li-ranobe').each(function () {
const chapterName = loadedCheerio(this).find('a').text();
const chapterUrl = loadedCheerio(this).find('a').attr('href');
if (
!loadedCheerio(this).find('label.buy-ranobe').length &&
chapterName &&
chapterUrl
) {
const releaseDate = loadedCheerio(this)
.find('div.li-col2-ranobe')
.text()
.trim();
chapters.push({ chapterName, releaseDate, chapterUrl });
}
});
novel.chapters = chapters.reverse();
return novel;
}
async parseChapter(novelUrl, chapterUrl) {
const body = await fetch(chapterUrl).then(res => res.text());
const loadedCheerio = cheerio.load(body);
loadedCheerio('.entry-content img').each(function () {
const srcset = loadedCheerio(this).attr('srcset')?.split?.(' ');
if (srcset?.length) {
const bestlink = srcset
.filter(url => url.startsWith('http'))
?.unshift();
if (bestlink) {
loadedCheerio(this).attr('src', bestlink);
}
}
});
const chapter = {
sourceId: this.sourceId,
novelUrl,
chapterUrl,
chapterName: loadedCheerio('.entry-title').text(),
chapterText: loadedCheerio('.entry-content').html(),
};
return chapter;
}
async searchNovels(searchTerm) {
const url = this.baseUrl + '/vse-knigi/?searchname=' + searchTerm;
const result = await fetch(url).then(res => res.text());
const loadedCheerio = cheerio.load(result);
const novels = loadedCheerio('div.one-book-home > div.img-home a')
.map((index, element) => ({
sourceId: this.sourceId,
novelName: loadedCheerio(element).attr('title'),
novelCover: loadedCheerio(element).find('img').attr('src'),
novelUrl: loadedCheerio(element).attr('href'),
}))
.get()
.filter(novel => novel.novelName && novel.novelUrl);
return novels;
}
}
export default IfreedomScraper;
@@ -350,3 +350,10 @@ export const WebNovelOkuScraper = new MadaraScraper(
'WebNovelOku ',
{ 'lang': 'Turkish' },
);
export const AsuraLightNovelScraper = new MadaraScraper(
169,
'https://asuralightnovel.com/',
'Asura Light Novel',
{ 'useNewChapterEndpoint': true, 'lang': 'English' },
);
@@ -361,5 +361,14 @@
"options": {
"lang": "Turkish"
}
},
{
"sourceId": 169,
"baseUrl": "https://asuralightnovel.com/",
"sourceName": "Asura Light Novel",
"options": {
"useNewChapterEndpoint": true,
"lang": "English"
}
}
]
@@ -7,64 +7,26 @@ const ReadwnScraper = new MultiSrcScraper(
{
label: 'Genre / Category',
values: [
{ label: 'All', value: 'all' },
{ label: 'Action', value: 'action' },
{ label: 'Adventure', value: 'adventure' },
{ label: 'Chinese', value: 'chinese' },
{ label: 'Comedy', value: 'comedy' },
{ label: 'Contemporary Romance', value: 'contemporary-romance' },
{ label: 'Drama', value: 'drama' },
{ label: 'Eastern Fantasy', value: 'eastern-fantasy' },
{ label: 'Ecchi', value: 'ecchi' },
{ label: 'Erciyuan', value: 'erciyuan' },
{ label: 'Faloo', value: 'faloo' },
{ label: 'Fan-Fiction', value: 'fan-fiction' },
{ label: 'Fantasy', value: 'fantasy' },
{ label: 'Fantasy Romance', value: 'fantasy-romance' },
{ label: 'Game', value: 'game' },
{ label: 'Gender Bender', value: 'gender-bender' },
{ label: 'Harem', value: 'harem' },
{ label: 'Hentai', value: 'hentai' },
{ label: 'Historical', value: 'historical' },
{ label: 'Horror', value: 'horror' },
{ label: 'Isekai', value: 'isekai' },
{ label: 'Japanese', value: 'japanese' },
{ label: 'Josei', value: 'josei' },
{ label: 'Korean', value: 'korean' },
{ label: 'Lolicon', value: 'lolicon' },
{ label: 'Magic', value: 'magic' },
{ label: 'Magical Realism', value: 'magical-realism' },
{ label: 'Martial Arts', value: 'martial-arts' },
{ label: 'Mecha', value: 'mecha' },
{ label: 'Military', value: 'military' },
{ label: 'Mystery', value: 'mystery' },
{ label: 'Official Circles', value: 'official_circles' },
{ label: 'Psychological', value: 'psychological' },
{ label: 'Romance', value: 'romance' },
{ label: 'School Life', value: 'school-life' },
{ label: 'Sci-fi', value: 'sci-fi' },
{ label: 'Science Fiction', value: 'science_fiction' },
{ label: 'Seinen', value: 'seinen' },
{ label: 'Shoujo', value: 'shoujo' },
{ label: 'Shoujo Ai', value: 'shoujo-ai' },
{ label: 'Shounen', value: 'shounen' },
{ label: 'Shounen Ai', value: 'shounen-ai' },
{ label: 'Slice of Life', value: 'slice-of-life' },
{ label: 'Sports', value: 'sports' },
{ label: 'Supernatural', value: 'supernatural' },
{ label: 'Suspense Thriller', value: 'suspense_thriller' },
{ label: 'Tragedy', value: 'tragedy' },
{ label: 'Travel Through Time', value: 'travel_through_time' },
{ label: 'Two-dimensional', value: 'two-dimensional' },
{ label: 'Urban', value: 'urban' },
{ label: 'Urban Life', value: 'urban-life' },
{ label: 'Video Games', value: 'video-games' },
{ label: 'Virtual Reality', value: 'virtual-reality' },
{ label: 'Wuxia', value: 'wuxia' },
{ label: 'Wuxia Xianxia', value: 'wuxia_xianxia' },
{ label: 'Xianxia', value: 'xianxia' },
{ label: 'Xuanhuan', value: 'xuanhuan' },
{ label: 'Yaoi', value: 'yaoi' },
{ label: 'Yuri', value: 'yuri' },
],
},
@@ -77,70 +39,31 @@ const NovelmtScraper = new MultiSrcScraper(
{
label: 'Genre / Category',
values: [
{ label: 'All', value: 'all' },
{ label: 'Action', value: 'action' },
{ label: 'Adult', value: 'adult' },
{ label: 'Adventure', value: 'adventure' },
{ label: 'Billionaire', value: 'billionaire' },
{ label: 'CEO', value: 'ceo' },
{ label: 'Chinese', value: 'chinese' },
{ label: 'Comedy', value: 'comedy' },
{ label: 'Contemporary Romance', value: 'contemporary-romance' },
{ label: 'Drama', value: 'drama' },
{ label: 'Eastern Fantasy', value: 'eastern-fantasy' },
{ label: 'Ecchi', value: 'ecchi' },
{ label: 'Erciyuan', value: 'erciyuan' },
{ label: 'Faloo', value: 'faloo' },
{ label: 'Fan-Fiction', value: 'fan-fiction' },
{ label: 'Fantasy', value: 'fantasy' },
{ label: 'Fantasy Romance', value: 'fantasy-romance' },
{ label: 'Farming', value: 'farming' },
{ label: 'Game', value: 'game' },
{ label: 'Games', value: 'games' },
{ label: 'Gay Romance', value: 'gay-romance' },
{ label: 'Gender Bender', value: 'gender-bender' },
{ label: 'Harem', value: 'harem' },
{ label: 'Historical', value: 'historical' },
{ label: 'Historical Romance', value: 'historical-romance' },
{ label: 'Horror', value: 'horror' },
{ label: 'Isekai', value: 'isekai' },
{ label: 'Japanese', value: 'japanese' },
{ label: 'Josei', value: 'josei' },
{ label: 'Korean', value: 'korean' },
{ label: 'Lolicon', value: 'lolicon' },
{ label: 'Magic', value: 'magic' },
{ label: 'Magical Realism', value: 'magical-realism' },
{ label: 'Martial Arts', value: 'martial-arts' },
{ label: 'Mature', value: 'mature' },
{ label: 'Mecha', value: 'mecha' },
{ label: 'Military', value: 'military' },
{ label: 'Modern Life', value: 'modern-life' },
{ label: 'Modern Romance', value: 'modern-romance' },
{ label: 'Mystery', value: 'mystery' },
{ label: 'Psychological', value: 'psychological' },
{ label: 'Romance', value: 'romance' },
{ label: 'Romantic', value: 'romantic' },
{ label: 'School Life', value: 'school-life' },
{ label: 'Sci-fi', value: 'sci-fi' },
{ label: 'Seinen', value: 'seinen' },
{ label: 'Shoujo', value: 'shoujo' },
{ label: 'Shoujo Ai', value: 'shoujo-ai' },
{ label: 'Shounen', value: 'shounen' },
{ label: 'Shounen Ai', value: 'shounen-ai' },
{ label: 'Slice of Life', value: 'slice-of-life' },
{ label: 'Smut', value: 'smut' },
{ label: 'Sports', value: 'sports' },
{ label: 'Supernatural', value: 'supernatural' },
{ label: 'Tragedy', value: 'tragedy' },
{ label: 'Two-dimensional', value: 'two-dimensional' },
{ label: 'Urban', value: 'urban' },
{ label: 'Urban Life', value: 'urban-life' },
{ label: 'Video Games', value: 'video-games' },
{ label: 'Virtual Reality', value: 'virtual-reality' },
{ label: 'Wuxia', value: 'wuxia' },
{ label: 'Xianxia', value: 'xianxia' },
{ label: 'Xuanhuan', value: 'xuanhuan' },
{ label: 'Yaoi', value: 'yaoi' },
{ label: 'Yuri', value: 'yuri' },
],
},
@@ -153,47 +76,10 @@ const LtnovelScraper = new MultiSrcScraper(
{
label: 'Genre / Category',
values: [
{ label: 'All', value: 'all' },
{ label: 'Action', value: 'action' },
{ label: 'Adult', value: 'adult' },
{ label: 'Adventure', value: 'adventure' },
{ label: 'Comedy', value: 'comedy' },
{ label: 'Contemporary Romance', value: 'contemporary-romance' },
{ label: 'Drama', value: 'drama' },
{ label: 'Eastern Fantasy', value: 'eastern-fantasy' },
{ label: 'Ecchi', value: 'ecchi' },
{ label: 'Fantasy', value: 'fantasy' },
{ label: 'Fantasy Romance', value: 'fantasy-romance' },
{ label: 'Game', value: 'game' },
{ label: 'Gender Bender', value: 'gender-bender' },
{ label: 'Harem', value: 'harem' },
{ label: 'Historical', value: 'historical' },
{ label: 'Horror', value: 'horror' },
{ label: 'Josei', value: 'josei' },
{ label: 'Lolicon', value: 'lolicon' },
{ label: 'Magical Realism', value: 'magical-realism' },
{ label: 'Martial Arts', value: 'martial-arts' },
{ label: 'Mature', value: 'mature' },
{ label: 'Mecha', value: 'mecha' },
{ label: 'Mystery', value: 'mystery' },
{ label: 'Psychological', value: 'psychological' },
{ label: 'Romance', value: 'romance' },
{ label: 'School Life', value: 'school-life' },
{ label: 'Sci-fi', value: 'sci-fi' },
{ label: 'Seinen', value: 'seinen' },
{ label: 'Shoujo', value: 'shoujo' },
{ label: 'Shounen', value: 'shounen' },
{ label: 'Shounen Ai', value: 'shounen-ai' },
{ label: 'Slice of Life', value: 'slice-of-life' },
{ label: 'Smut', value: 'smut' },
{ label: 'Sports', value: 'sports' },
{ label: 'Supernatural', value: 'supernatural' },
{ label: 'Tragedy', value: 'tragedy' },
{ label: 'Video Games', value: 'video-games' },
{ label: 'Wuxia', value: 'wuxia' },
{ label: 'Xianxia', value: 'xianxia' },
{ label: 'Xuanhuan', value: 'xuanhuan' },
{ label: 'Yaoi', value: 'yaoi' },
],
},
);
+134 -126
View File
@@ -1,9 +1,10 @@
import * as cheerio from 'cheerio';
import QueryString from 'qs';
import { fetchHtml } from '@utils/fetch/fetch';
import { parseMadaraDate } from '../../helpers/parseDate';
import { FilterInputs } from '../../types/filterTypes';
import { Status } from '../../helpers/constants';
import { fetchHtml } from '@utils/fetch/fetch';
class ReadwnScraper {
constructor(sourceId, baseUrl, sourceName, genres) {
@@ -33,156 +34,176 @@ class ReadwnScraper {
},
{
key: 'genres',
label: genres.label,
values: genres.values,
label: 'Genre / Category',
values: [
{ label: 'All', value: 'all' },
{ label: 'Action', value: 'action' },
{ label: 'Adventure', value: 'adventure' },
{ label: 'Comedy', value: 'comedy' },
{ label: 'Contemporary Romance', value: 'contemporary-romance' },
{ label: 'Drama', value: 'drama' },
{ label: 'Eastern Fantasy', value: 'eastern-fantasy' },
{ label: 'Fantasy', value: 'fantasy' },
{ label: 'Fantasy Romance', value: 'fantasy-romance' },
{ label: 'Game', value: 'game' },
{ label: 'Gender Bender', value: 'gender-bender' },
{ label: 'Harem', value: 'harem' },
{ label: 'Historical', value: 'historical' },
{ label: 'Horror', value: 'horror' },
{ label: 'Josei', value: 'josei' },
{ label: 'Lolicon', value: 'lolicon' },
{ label: 'Magical Realism', value: 'magical-realism' },
{ label: 'Martial Arts', value: 'martial-arts' },
{ label: 'Mecha', value: 'mecha' },
{ label: 'Mystery', value: 'mystery' },
{ label: 'Psychological', value: 'psychological' },
{ label: 'Romance', value: 'romance' },
{ label: 'School Life', value: 'school-life' },
{ label: 'Sci-fi', value: 'sci-fi' },
{ label: 'Seinen', value: 'seinen' },
{ label: 'Shoujo', value: 'shoujo' },
{ label: 'Shounen', value: 'shounen' },
{ label: 'Shounen Ai', value: 'shounen-ai' },
{ label: 'Slice of Life', value: 'slice-of-life' },
{ label: 'Sports', value: 'sports' },
{ label: 'Supernatural', value: 'supernatural' },
{ label: 'Tragedy', value: 'tragedy' },
{ label: 'Video Games', value: 'video-games' },
{ label: 'Wuxia', value: 'wuxia' },
{ label: 'Xianxia', value: 'xianxia' },
{ label: 'Xuanhuan', value: 'xuanhuan' },
{ label: 'Yaoi', value: 'yaoi' },
...genres.values,
],
inputType: FilterInputs.Picker,
},
];
}
async popularNovels(page, { showLatestNovels, filters }) {
const baseUrl = this.baseUrl;
const sourceId = this.sourceId;
const pageNo = page - 1;
let url = baseUrl + 'list/';
let url = this.baseUrl + 'list/';
url += (filters?.genres || 'all') + '/';
url += (filters?.status || 'all') + '-';
url +=
(showLatestNovels ? 'lastdotime' : filters?.sort || 'newstime') + '-';
url += pageNo + '.html';
url += page - 1 + '.html';
const body = await fetchHtml({ url, sourceId });
const body = await fetchHtml({ url, sourceId: this.sourceId });
const loadedCheerio = cheerio.load(body);
let novels = [];
loadedCheerio('li.novel-item').each(function () {
const novelName = loadedCheerio(this).find('h4').text();
const novelUrl = baseUrl + loadedCheerio(this).find('a').attr('href');
const coverUri = loadedCheerio(this)
.find('.novel-cover > img')
.attr('data-src');
const novelCover = baseUrl + coverUri;
const novel = { sourceId, novelName, novelCover, novelUrl };
novels.push(novel);
});
const novels = loadedCheerio('li.novel-item')
.map((index, element) => ({
sourceId: this.sourceId,
novelName: loadedCheerio(element).find('h4').text(),
novelCover:
this.baseUrl +
loadedCheerio(element).find('.novel-cover > img').attr('data-src'),
novelUrl: this.baseUrl + loadedCheerio(element).find('a').attr('href'),
}))
.get();
return { novels };
}
async parseNovelAndChapters(novelUrl) {
const sourceId = this.sourceId;
const baseUrl = this.baseUrl;
const sourceName = this.sourceName;
const body = await fetchHtml({ url: novelUrl, sourceId: this.sourceId });
const loadedCheerio = cheerio.load(body);
const url = novelUrl;
const body = await fetchHtml({ url, sourceId });
let loadedCheerio = cheerio.load(body);
let novel = {
sourceId: sourceId,
sourceName: sourceName,
url,
const novel = {
sourceId: this.sourceId,
sourceName: this.sourceName,
url: novelUrl,
novelUrl,
};
novel.novelName = loadedCheerio('h1.novel-title').text();
const coverUri = loadedCheerio('figure.cover > img').attr('data-src');
novel.novelCover = baseUrl + coverUri;
novel.novelCover =
this.baseUrl + loadedCheerio('figure.cover > img').attr('data-src');
novel.author = loadedCheerio('span[itemprop=author]').text();
novel.summary = loadedCheerio('.summary')
.text()
.replace('Summary', '')
.trim();
novel.genre = '';
loadedCheerio('div.categories > ul > li').each(function () {
novel.genre += loadedCheerio(this).text().trim() + ',';
});
novel.genre = loadedCheerio('div.categories > ul > li')
.map((index, element) => loadedCheerio(element).text()?.trim())
.get()
.join(',');
loadedCheerio('div.header-stats > span').each(function () {
if (loadedCheerio(this).find('small').text() === 'Status') {
novel.status = loadedCheerio(this).find('strong').text();
novel.status =
loadedCheerio(this).find('strong').text() === 'Ongoing'
? Status.ONGOING
: Status.COMPLETED;
}
});
novel.genre = novel.genre.slice(0, -1);
novel.author = loadedCheerio('span[itemprop=author]').text();
let novelChapters = [];
const novelId = novelUrl.replace('.html', '').replace(baseUrl, '');
const latestChapterNo = loadedCheerio('.header-stats')
.find('span > strong')
.first()
.text()
.trim();
let lastChapterNo = 1;
loadedCheerio('.chapter-list li').each(function () {
const chapterName = loadedCheerio(this)
.find('a .chapter-title')
const latestChapterNo = parseInt(
loadedCheerio('.header-stats')
.find('span > strong')
.first()
.text()
.trim();
.trim(),
10,
);
const chapterUrl = loadedCheerio(this).find('a').attr('href').trim();
const chapters = loadedCheerio('.chapter-list li')
.map((index, element) => {
const chapterName = loadedCheerio(element)
.find('a .chapter-title')
.text()
.trim();
const chapterUrl = loadedCheerio(element)
.find('a')
.attr('href')
?.trim();
const releaseDate = loadedCheerio(element)
.find('a .chapter-update')
.text()
.trim();
const releaseDate = loadedCheerio(this)
.find('a .chapter-update')
.text()
.trim();
if (chapterUrl) {
return {
chapterName,
releaseDate: parseMadaraDate(releaseDate),
chapterUrl: this.baseUrl + chapterUrl,
};
}
})
.get()
.filter(chapter => chapter?.chapterName);
lastChapterNo = loadedCheerio(this).find('a .chapter-no').text().trim();
if (latestChapterNo > chapters.length) {
const lastChapterNo = parseInt(
chapters[chapters.length - 1].chapterUrl.match(/_(\d+)\.html/)?.[1] ||
chapters.length,
10,
);
const chapter = { chapterName, releaseDate, chapterUrl };
novelChapters.push(chapter);
});
// Itterate once more before loop to finish off
lastChapterNo++;
for (let i = lastChapterNo; i <= latestChapterNo; i++) {
const chapterName = `Chapter ${i}`;
const chapterUrl = `${novelId}_${i}.html`;
const releaseDate = null;
const chapter = { chapterName, releaseDate, chapterUrl };
novelChapters.push(chapter);
for (let i = lastChapterNo + 1; i <= latestChapterNo; i++) {
chapters.push({
chapterName: 'Chapter ' + i,
releaseDate: null,
chapterUrl: novelUrl.replace('.html', '_' + i + '.html'),
});
}
}
novel.chapters = novelChapters;
novel.chapters = chapters;
return novel;
}
async parseChapter(novelUrl, chapterUrl) {
const baseUrl = this.baseUrl;
const url = baseUrl + chapterUrl;
const sourceId = this.sourceId;
const body = await fetchHtml({ url, sourceId });
const body = await fetchHtml({ url: chapterUrl, sourceId: this.sourceId });
const loadedCheerio = cheerio.load(body);
const chapterName = loadedCheerio('.titles > h2').text();
const chapterText = loadedCheerio('.chapter-content').html();
const chapter = {
sourceId,
sourceId: this.sourceId,
novelUrl,
chapterUrl,
chapterName,
@@ -193,17 +214,14 @@ class ReadwnScraper {
}
async searchNovels(searchTerm) {
const baseUrl = this.baseUrl;
const sourceId = this.sourceId;
const searchUrl = `${baseUrl}e/search/index.php`;
const body = await fetchHtml({
url: searchUrl,
url: this.baseUrl + 'e/search/index.php',
sourceId: this.sourceId,
init: {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Referer: `${baseUrl}search.html`,
Origin: baseUrl,
Referer: this.baseUrl + 'search.html',
Origin: this.baseUrl,
'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',
},
@@ -218,25 +236,15 @@ class ReadwnScraper {
});
const loadedCheerio = cheerio.load(body);
let novels = [];
loadedCheerio('li.novel-item').each(function () {
const novelName = loadedCheerio(this).find('h4').text();
const novelUrl = baseUrl + loadedCheerio(this).find('a').attr('href');
const coverUri = loadedCheerio(this).find('img').attr('data-src');
const novelCover = baseUrl + coverUri;
const novel = {
sourceId,
novelName,
novelCover,
novelUrl,
};
novels.push(novel);
});
const novels = loadedCheerio('li.novel-item')
.map((index, element) => ({
sourceId: this.sourceId,
novelName: loadedCheerio(element).find('h4').text(),
novelCover:
this.baseUrl + loadedCheerio(element).find('img').attr('data-src'),
novelUrl: this.baseUrl + loadedCheerio(element).find('a').attr('href'),
}))
.get();
return novels;
}
+66 -3
View File
@@ -54,8 +54,6 @@ const RulateScraper = new MultiSrcScraper(
{ label: 'Фэнтези', value: '37' },
{ label: 'Школа', value: '38' },
{ label: 'Этти', value: '39' },
{ label: 'Bl', value: '43' },
{ label: 'Gl', value: '40' },
],
inputType: FilterInputs.Checkbox,
},
@@ -311,7 +309,6 @@ const RulateScraper = new MultiSrcScraper(
{ label: 'Горничные', value: '8091' },
{ label: 'Гробница', value: '388' },
{ label: 'Дазай осаму', value: '4112' },
{ label: 'Даньмэй', value: '3411' },
{ label: 'Даосизм', value: '258' },
{ label: 'Дарк соулс', value: '6057' },
{ label: 'Дарт вейдер', value: '6826' },
@@ -383,6 +380,7 @@ const RulateScraper = new MultiSrcScraper(
{ label: 'Жестокость', value: '7880' },
{ label: 'Животные', value: '7488' },
{ label: 'Животные компаньоны', value: '2061' },
{ label: 'Жизнь в небесах', value: '8426' },
{ label: 'Жизнь и смерть', value: '2014' },
{ label: 'Жнец', value: '6288' },
{ label: 'Жойен рид', value: '3926' },
@@ -420,6 +418,7 @@ const RulateScraper = new MultiSrcScraper(
{ label: 'Игра на выживание', value: '2182' },
{ label: 'Игровая система', value: '384' },
{ label: 'Игровые элементы', value: '720' },
{ label: 'Избранный', value: '8425' },
{ label: 'Извращения', value: '651' },
{ label: 'Измена', value: '1874' },
{ label: 'Изменение характера', value: '1113' },
@@ -1061,6 +1060,7 @@ const RulateScraper = new MultiSrcScraper(
{ label: 'Церковь', value: '1980' },
{ label: 'Цундере', value: '1078' },
{ label: 'Чакра', value: '1393' },
{ label: 'Чат-система', value: '8407' },
{ label: 'Черный юмор', value: '2868' },
{ label: 'Честная главная героиня', value: '5855' },
{ label: 'Честный главный герой', value: '129' },
@@ -1083,6 +1083,7 @@ const RulateScraper = new MultiSrcScraper(
{ label: 'Элементальная магия', value: '1264' },
{ label: 'Эльфы', value: '1362' },
{ label: 'Эротика', value: '2053' },
{ label: 'Это история без главной героини', value: '8417' },
{ label: 'Юмор', value: '7360' },
{ label: 'Яды', value: '201' },
{ label: 'Якудза', value: '4599' },
@@ -1161,21 +1162,26 @@ const ErolateScraper = new MultiSrcScraper(
{ label: 'Айдолы', value: '364' },
{ label: 'Акула', value: '53' },
{ label: 'Альтернативное развитие событий', value: '338' },
{ label: 'Альфа самец', value: '453' },
{ label: 'Аморальный главный герой', value: '219' },
{ label: 'Анал', value: '7' },
{ label: 'Анальный секс', value: '67' },
{ label: 'Ангелы', value: '114' },
{ label: 'Антигерой', value: '226' },
{ label: 'Аристократия', value: '260' },
{ label: 'Армия', value: '415' },
{ label: 'Артефакт', value: '379' },
{ label: 'Афродизиак', value: '276' },
{ label: 'Ахегао', value: '240' },
{ label: 'Бабушка беременна от внука', value: '393' },
{ label: 'Бабушка и внук', value: '380' },
{ label: 'Бандиты', value: '441' },
{ label: 'Бдс', value: '420' },
{ label: 'Бдсм', value: '131' },
{ label: 'Беременность', value: '148' },
{ label: 'Бесплатно', value: '79' },
{ label: 'Бесстрашные персонажи', value: '339' },
{ label: 'Бесстыдный главный герой', value: '433' },
{ label: 'Библиотека', value: '265' },
{ label: 'Бистиалити', value: '391' },
{ label: 'Битва за трон', value: '404' },
@@ -1184,6 +1190,7 @@ const ErolateScraper = new MultiSrcScraper(
{ label: 'Богатые персонажи', value: '248' },
{ label: 'Боги', value: '115' },
{ label: 'Боевик', value: '252' },
{ label: 'Боевые искусства', value: '430' },
{ label: 'Большая грудь', value: '33' },
{ label: 'Большая попка', value: '328' },
{ label: 'Большой член', value: '76' },
@@ -1193,7 +1200,9 @@ const ErolateScraper = new MultiSrcScraper(
{ label: 'Брак по расчёту', value: '346' },
{ label: 'Брат', value: '29' },
{ label: 'Брат и сестра', value: '30' },
{ label: 'Братский комплекс', value: '466' },
{ label: 'Брюнетка', value: '28' },
{ label: 'Будущее', value: '457' },
{ label: 'Бэтмен', value: '369' },
{ label: 'Бэтмен', value: '170' },
{ label: 'В первый раз', value: '179' },
@@ -1208,7 +1217,9 @@ const ErolateScraper = new MultiSrcScraper(
{ label: 'Виртуальная реальность', value: '382' },
{ label: 'Вирус', value: '203' },
{ label: 'Внучка', value: '189' },
{ label: 'Возвращение домой', value: '448' },
{ label: 'Война', value: '387' },
{ label: 'Волшебство', value: '438' },
{ label: 'Воспоминания из прошлого', value: '405' },
{ label: 'Враги становятся любовниками', value: '410' },
{ label: 'Время', value: '81' },
@@ -1220,7 +1231,9 @@ const ErolateScraper = new MultiSrcScraper(
{ label: 'Гвен', value: '402' },
{ label: 'Гг имба', value: '87' },
{ label: 'Генетические модификации', value: '314' },
{ label: 'Гениальный главный герой', value: '471' },
{ label: 'Гипноз', value: '143' },
{ label: 'Гл', value: '427' },
{ label: 'Главная героиня девушка', value: '147' },
{ label: 'Главный герой женщина', value: '213' },
{ label: 'Главный герой извращенец', value: '116' },
@@ -1269,6 +1282,7 @@ const ErolateScraper = new MultiSrcScraper(
{ label: 'Замок', value: '140' },
{ label: 'Запретная любовь', value: '273' },
{ label: 'Заражение', value: '206' },
{ label: 'Зачарованные', value: '467' },
{ label: 'Звёздные войны', value: '120' },
{ label: 'Зверодевочки', value: '158' },
{ label: 'Зло', value: '343' },
@@ -1276,6 +1290,7 @@ const ErolateScraper = new MultiSrcScraper(
{ label: 'Золотой дождь', value: '146' },
{ label: 'Зомби апокалипсис', value: '241' },
{ label: 'Зоофилия', value: '144' },
{ label: 'Зрелы', value: '419' },
{ label: 'Зрелые женщины', value: '398' },
{ label: 'Игровые элементы', value: '385' },
{ label: 'Извращения', value: '192' },
@@ -1284,26 +1299,35 @@ const ErolateScraper = new MultiSrcScraper(
{ label: 'Изменения личности', value: '316' },
{ label: 'Изнасилование', value: '275' },
{ label: 'Изуку мидория', value: '337' },
{ label: 'Инвалидность', value: '458' },
{ label: 'Ино', value: '127' },
{ label: 'Инопланетяне', value: '304' },
{ label: 'Интересный сюжет', value: '349' },
{ label: 'Интимные сцены', value: '322' },
{ label: 'Интрига', value: '435' },
{ label: 'Интриги и заговоры', value: '262' },
{ label: 'Интроверт', value: '38' },
{ label: 'Инфекция', value: '207' },
{ label: 'Инцест', value: '35' },
{ label: 'Исторический роман', value: '406' },
{ label: 'Камера', value: '92' },
{ label: 'Киберспорт', value: '426' },
{ label: 'Китай', value: '445' },
{ label: 'Колледж', value: '82' },
{ label: 'Кольца', value: '455' },
{ label: 'Комикс', value: '286' },
{ label: 'Контроль', value: '19' },
{ label: 'Контроль над разумом', value: '181' },
{ label: 'Контроль разума', value: '65' },
{ label: 'Кончил внутрь', value: '221' },
{ label: 'Корея', value: '446' },
{ label: 'Кормление грудью', value: '440' },
{ label: 'Королевская семья', value: '358' },
{ label: 'Королевство', value: '465' },
{ label: 'Коррупция', value: '49' },
{ label: 'Космос', value: '204' },
{ label: 'Красивая главная героиня', value: '255' },
{ label: 'Красивы', value: '462' },
{ label: 'Красивые женщины', value: '321' },
{ label: 'Красивый главный герой', value: '266' },
{ label: 'Ксенофилия', value: '386' },
@@ -1316,9 +1340,13 @@ const ErolateScraper = new MultiSrcScraper(
{ label: 'Лишение девственности', value: '212' },
{ label: 'Лоли', value: '218' },
{ label: 'Лунатизм', value: '279' },
{ label: 'Любовный треугольник', value: '473' },
{ label: 'Любовь', value: '89' },
{ label: 'Любовь с первого взгляда', value: '436' },
{ label: 'Магические предметы', value: '454' },
{ label: 'Магический мир', value: '357' },
{ label: 'Магия', value: '80' },
{ label: 'Магия природы', value: '437' },
{ label: 'Мама', value: '3' },
{ label: 'Мама беременна от сына', value: '274' },
{ label: 'Мама и дочь', value: '163' },
@@ -1337,6 +1365,7 @@ const ErolateScraper = new MultiSrcScraper(
{ label: 'Мачеха', value: '197' },
{ label: 'Мачеха беременна от сына', value: '395' },
{ label: 'Мачеха и сын', value: '394' },
{ label: 'Медленная романтика', value: '431' },
{ label: 'Медсестра', value: '341' },
{ label: 'Межрассовый', value: '54' },
{ label: 'Месть', value: '306' },
@@ -1349,8 +1378,10 @@ const ErolateScraper = new MultiSrcScraper(
{ label: 'Младшая сестра', value: '154' },
{ label: 'Много спермы', value: '409' },
{ label: 'Модель', value: '324' },
{ label: 'Монахиня', value: '470' },
{ label: 'Монстры', value: '333' },
{ label: 'Мошеничество', value: '44' },
{ label: 'Муж куколд', value: '452' },
{ label: 'Мужская беременность', value: '363' },
{ label: 'Мужчина протагонист', value: '108' },
{ label: 'Музыка', value: '331' },
@@ -1360,6 +1391,7 @@ const ErolateScraper = new MultiSrcScraper(
{ label: 'Насилие', value: '128' },
{ label: 'Насилие и жестокость', value: '193' },
{ label: 'Научный эксперимент', value: '313' },
{ label: 'Невеста', value: '421' },
{ label: 'Нежить', value: '373' },
{ label: 'Некромантия', value: '298' },
{ label: 'Ненормативная лексика', value: '124' },
@@ -1367,6 +1399,7 @@ const ErolateScraper = new MultiSrcScraper(
{ label: 'Нетори', value: '284' },
{ label: 'Нижнее бельё', value: '392' },
{ label: 'Ниндзя', value: '296' },
{ label: 'Новый год', value: '459' },
{ label: 'Ношеные трусики', value: '166' },
{ label: 'Нудизм', value: '156' },
{ label: 'Няня', value: '88' },
@@ -1379,11 +1412,13 @@ const ErolateScraper = new MultiSrcScraper(
{ label: 'Оральный секс', value: '10' },
{ label: 'Оргия', value: '356' },
{ label: 'Орки', value: '388' },
{ label: 'От бедности к богатству', value: '444' },
{ label: 'От слабого к сильному', value: '351' },
{ label: 'Отец', value: '2' },
{ label: 'Отец делится с сыном', value: '6' },
{ label: 'Отец и дочь', value: '205' },
{ label: 'Отец куколд', value: '272' },
{ label: 'Отчим', value: '417' },
{ label: 'Офис', value: '234' },
{ label: 'Падчерица', value: '271' },
{ label: 'Пайзури', value: '195' },
@@ -1391,6 +1426,7 @@ const ErolateScraper = new MultiSrcScraper(
{ label: 'Паразиты', value: '208' },
{ label: 'Параллельный мир', value: '320' },
{ label: 'Пародия', value: '371' },
{ label: 'Первая любовь', value: '434' },
{ label: 'Первый раз', value: '159' },
{ label: 'Перемещение в другой мир', value: '347' },
{ label: 'Перемещение во времени', value: '245' },
@@ -1417,8 +1453,11 @@ const ErolateScraper = new MultiSrcScraper(
{ label: 'Потеря девственности', value: '142' },
{ label: 'Похищение', value: '161' },
{ label: 'Преданные любовный интерес', value: '407' },
{ label: 'Призраки', value: '412' },
{ label: 'Приключения', value: '134' },
{ label: 'Принуждение', value: '66' },
{ label: 'Проклятие', value: '456' },
{ label: 'Психические расстройства', value: '463' },
{ label: 'Психология', value: '238' },
{ label: 'Публично', value: '85' },
{ label: 'Публичный секс', value: '233' },
@@ -1428,11 +1467,15 @@ const ErolateScraper = new MultiSrcScraper(
{ label: 'Разврат', value: '64' },
{ label: 'Райзен', value: '95' },
{ label: 'Раса инопланетных космических лесбиянок', value: '294' },
{ label: 'Ревность', value: '414' },
{ label: 'Реинкарнация', value: '428' },
{ label: 'Религия', value: '403' },
{ label: 'Рестленг', value: '84' },
{ label: 'Риас гремори', value: '389' },
{ label: 'Рождество', value: '460' },
{ label: 'Романтика', value: '178' },
{ label: 'Рыжий', value: '90' },
{ label: 'С', value: '413' },
{ label: 'Сакура', value: '126' },
{ label: 'Самолет', value: '93' },
{ label: 'Санса старк', value: '277' },
@@ -1454,6 +1497,7 @@ const ErolateScraper = new MultiSrcScraper(
{ label: 'Секса будет много', value: '151' },
{ label: 'Сексуальное желание', value: '209' },
{ label: 'Селфцест', value: '397' },
{ label: 'Семейные традиции', value: '449' },
{ label: 'Семья', value: '5' },
{ label: 'Сестра', value: '31' },
{ label: 'Сестра беременна от брата', value: '355' },
@@ -1474,14 +1518,24 @@ const ErolateScraper = new MultiSrcScraper(
{ label: 'Соседка', value: '312' },
{ label: 'Сперма', value: '229' },
{ label: 'Сперма на лицо', value: '199' },
{ label: 'Спорт', value: '450' },
{ label: 'Спящие', value: '187' },
{ label: 'Средневековье', value: '443' },
{ label: 'Сталкер', value: '418' },
{ label: 'Старшая сестра', value: '311' },
{ label: 'Стеб', value: '111' },
{ label: 'Студенты', value: '177' },
{ label: 'Суккуб', value: '390' },
{ label: 'Супергерои', value: '305' },
{ label: 'Суперспособности', value: '300' },
{ label: 'Счастливый конец', value: '461' },
{ label: 'Сын', value: '4' },
{ label: 'Сын солдат', value: '416' },
{ label: 'Сюаньхуа', value: '425' },
{ label: 'Сянься', value: '424' },
{ label: 'Таблетки для развития', value: '429' },
{ label: 'Тайная любовь', value: '432' },
{ label: 'Тайные отношения', value: '451' },
{ label: 'Твинцест', value: '188' },
{ label: 'Темное фэнтези', value: '293' },
{ label: 'Тентакли', value: '303' },
@@ -1505,6 +1559,8 @@ const ErolateScraper = new MultiSrcScraper(
{ label: 'Умные персонажи', value: '257' },
{ label: 'Умный главный герой', value: '354' },
{ label: 'Университет', value: '323' },
{ label: 'Уся', value: '423' },
{ label: 'Учеба в университете', value: '472' },
{ label: 'Ученик', value: '83' },
{ label: 'Ф', value: '149' },
{ label: 'Фанатичная любовь', value: '366' },
@@ -1516,16 +1572,22 @@ const ErolateScraper = new MultiSrcScraper(
{ label: 'Фетиш', value: '60' },
{ label: 'Флэш', value: '121' },
{ label: 'Футанария', value: '150' },
{ label: 'Фэн', value: '422' },
{ label: 'Фэнтези', value: '359' },
{ label: 'Фэнтезийный мир', value: '464' },
{ label: 'Хентай', value: '75' },
{ label: 'Хината', value: '110' },
{ label: 'Холодная главная героиня', value: '263' },
{ label: 'Христианство', value: '468' },
{ label: 'Хулиганы', value: '442' },
{ label: 'Церковь', value: '469' },
{ label: 'Черная вдова', value: '220' },
{ label: 'Черная Вдова', value: '175' },
{ label: 'Черный юмор', value: '374' },
{ label: 'Читерство', value: '45' },
{ label: 'Чтение мыслей', value: '227' },
{ label: 'Чудовища', value: '378' },
{ label: 'Чулки', value: '439' },
{ label: 'Шантаж', value: '129' },
{ label: 'Школа', value: '118' },
{ label: 'Школьная жизнь', value: '318' },
@@ -1540,6 +1602,7 @@ const ErolateScraper = new MultiSrcScraper(
{ label: 'Этти', value: '327' },
{ label: 'Юмор', value: '105' },
{ label: 'Яндере', value: '155' },
{ label: 'Япония', value: '447' },
{ label: 'Bl', value: '125' },
{ label: 'Dc', value: '411' },
{ label: 'Gl', value: '96' },
+7 -4
View File
@@ -88,11 +88,11 @@ class RulateScraper {
url += '&adult=' + (filters?.adult || '0');
if (filters?.genres?.length) {
url += filters.genres.map(i => `&genres[]=${i}`).join('');
url += filters.genres.map(i => '&genres[]=' + i).join('');
}
if (filters?.tags?.length) {
url += filters.tags.map(i => `&tags[]=${i}`).join('');
url += filters.tags.map(i => '&tags[]=' + i).join('');
}
if (filters?.trash?.length) {
@@ -150,6 +150,9 @@ class RulateScraper {
)
.text()
.trim();
if (novel.novelName?.includes?.('[')) {
novel.novelName = novel.novelName.split('[')[0].trim();
}
novel.novelCover =
this.baseUrl + loadedCheerio('div[class="images"] > div img').attr('src');
novel.summary = loadedCheerio(
@@ -191,7 +194,7 @@ class RulateScraper {
},
);
if (genre.length > 0) {
if (genre.length) {
novel.genre = genre.reverse().join(',');
}
@@ -210,7 +213,7 @@ class RulateScraper {
.attr('href');
if (
loadedCheerio(this).find('td > span[class="disabled"]').length < 1 &&
!loadedCheerio(this).find('td > span[class="disabled"]').length &&
releaseDate
) {
chapters.push({ chapterName, releaseDate, chapterUrl });
+21 -23
View File
@@ -13,17 +13,18 @@ const sourceName = 'LitSpace';
const baseUrl = 'https://freedlit.space';
const popularNovels = async (page, { showLatestNovels, filters }) => {
let url = baseUrl + '/books/';
url += (filters?.genre || 'all') + '?sort=';
let url = baseUrl + '/get-books/all/list/' + page + '?sort=';
url += showLatestNovels ? 'recent' : filters?.sort || 'popular';
url += '&status=' + (filters?.status || 'all');
url += '&access=' + (filters?.access || 'all');
url += '&adult=' + (filters?.adult || 'hide');
url += '&page=' + page;
const result = await fetch(url);
const body = await result.text();
const loadedCheerio = cheerio.load(body);
if (filters?.genre?.length) {
url += filters.genre.map(id => '&genres_included[]=' + id).join('');
}
const result = await fetch(url).then(res => res.text());
const loadedCheerio = cheerio.load(result);
const novels: SourceNovelItem[] = [];
loadedCheerio('#bookListBlock > div > div').each(function () {
@@ -44,31 +45,30 @@ const popularNovels = async (page, { showLatestNovels, filters }) => {
};
const parseNovelAndChapters = async novelUrl => {
const result = await fetch(novelUrl);
const body = await result.text();
const loadedCheerio = cheerio.load(body);
const result = await fetch(novelUrl).then(res => res.text());
const loadedCheerio = cheerio.load(result);
const novel: SourceNovel = {
sourceId,
sourceName,
novelUrl,
url: novelUrl,
novelName: loadedCheerio('.book-info > h4').text(),
novelName: loadedCheerio('.book-info h4').text(),
novelCover: loadedCheerio('.book-cover > div > img').attr('src')?.trim(),
summary: loadedCheerio('#nav-home').text()?.trim(),
author: loadedCheerio('.book-info > h5 > a').text(),
author: loadedCheerio('.book-info h5 > a').text(),
genre: loadedCheerio('.genre-list > a')
.map((index, element) => loadedCheerio(element).text())
.get()
.join(','),
};
let chapters: SourceChapterItem[] = [];
const chapters: SourceChapterItem[] = [];
loadedCheerio('#nav-contents > div').each(function () {
const chapterName = loadedCheerio(this).find('a').text();
loadedCheerio('a.chapter-line').each(function () {
const chapterName = loadedCheerio(this).find('h6').text();
const releaseDate = loadedCheerio(this).find('span[class="date"]').text();
const chapterUrl = loadedCheerio(this).find('a').attr('href');
const chapterUrl = loadedCheerio(this).attr('href');
if (chapterName && chapterUrl) {
chapters.push({ chapterName, releaseDate, chapterUrl });
}
@@ -83,8 +83,8 @@ const parseChapter = async (novelUrl, chapterUrl) => {
const body = await result.text();
const loadedCheerio = cheerio.load(body);
loadedCheerio('div[class="standart-block"]').remove();
loadedCheerio('div[class="mobile-block"]').remove();
loadedCheerio('div.mobile-block').remove();
loadedCheerio('div.standart-block').remove();
const chapter: SourceChapter = {
sourceId,
@@ -99,11 +99,10 @@ const parseChapter = async (novelUrl, chapterUrl) => {
const searchNovels = async searchTerm => {
const url = `${baseUrl}/search?query=${searchTerm}&type=all`;
const result = await fetch(url);
const body = await result.text();
const loadedCheerio = cheerio.load(body);
const result = await fetch(url).then(res => res.text());
const loadedCheerio = cheerio.load(result);
let novels: Novel.Item[] = [];
const novels: SourceNovelItem[] = [];
loadedCheerio('#bookListBlock > div').each(function () {
const novelName = loadedCheerio(this).find('h4 > a').text()?.trim();
const novelCover = loadedCheerio(this).find('a > img').attr('src')?.trim();
@@ -132,7 +131,6 @@ const filters = [
key: 'genre',
label: 'Жанры:',
values: [
{ label: 'Любой жанр', value: 'all' },
{ label: 'Альтернативная история', value: 'alternative-history' },
{ label: 'Антиутопия', value: 'dystopia' },
{ label: 'Бизнес-литература', value: 'business-literature' },
@@ -210,7 +208,7 @@ const filters = [
{ label: 'Юмористическое фэнтези', value: 'humor-fantasy' },
{ label: 'RPS', value: 'rps' },
],
inputType: FilterInputs.Picker,
inputType: FilterInputs.Checkbox,
},
{
key: 'status',
+3 -3
View File
@@ -10,15 +10,15 @@ const baseUrl = 'https://jaomix.ru';
const popularNovels = async (page, { showLatestNovels, filters }) => {
let url = baseUrl + '/?searchrn';
if (filters?.lang instanceof Array) {
if (filters?.lang?.length) {
url += filters.lang.map((lang, idx) => `&lang[${idx}]=${lang}`).join('');
}
if (filters?.genre instanceof Array) {
if (filters?.genre?.length) {
url += filters.genre
.map((genre, idx) => `&genre[${idx}]=${genre}`)
.join('');
}
if (filters?.delgenre instanceof Array) {
if (filters?.delgenre?.length) {
url += filters.delgenre
.map((genre, idx) => `&delgenre[${idx}]=del ${genre}`)
.join('');
+6 -6
View File
@@ -16,27 +16,27 @@ const popularNovels = async (page, { showLatestNovels, filters }) => {
url += '&page=' + page;
if (filters?.type?.length) {
url += filters.type.map(i => `&types[]=${i}`).join('');
url += filters.type.map(i => '&types[]=' + i).join('');
}
if (filters?.format?.length) {
url += filters.format.map(i => `&format[include][]=${i}`).join('');
url += filters.format.map(i => '&format[include][]=' + i).join('');
}
if (filters?.status?.length) {
url += filters.status.map(i => `&status[]=${i}`).join('');
url += filters.status.map(i => '&status[]=' + i).join('');
}
if (filters?.statuss?.length) {
url += filters.statuss.map(i => `&manga_status[]=${i}`).join('');
url += filters.statuss.map(i => '&manga_status[]=' + i).join('');
}
if (filters?.genres?.length) {
url += filters.genres.map(i => `&genres[include][]=${i}`).join('');
url += filters.genres.map(i => '&genres[include][]=' + i).join('');
}
if (filters?.tags?.length) {
url += filters.tags.map(i => `&tags[include][]=${i}`).join('');
url += filters.tags.map(i => '&tags[include][]=' + i).join('');
}
const result = await fetch(url);
+10 -10
View File
@@ -13,24 +13,24 @@ const popularNovels = async (page, { showLatestNovels, filters }) => {
url += filters?.order ? filters?.order?.replace('+', '') : '-';
url += showLatestNovels ? 'chapter_date' : filters?.sort || 'rating';
if (filters?.genres instanceof Array) {
url += filters.genres.map(i => `&genres=${i}`).join('');
if (filters?.genres?.length) {
url += filters.genres.map(i => '&genres=' + i).join('');
}
if (filters?.status instanceof Array) {
url += filters?.status.map(i => `&status=${i}`).join('');
if (filters?.status?.length) {
url += filters.status.map(i => '&status=' + i).join('');
}
if (filters?.types instanceof Array) {
url += filters.types.map(i => `&types=${i}`).join('');
if (filters?.types?.length) {
url += filters.types.map(i => '&types=' + i).join('');
}
if (filters?.categories instanceof Array) {
url += filters.categories.map(i => `&categories=${i}`).join('');
if (filters?.categories?.length) {
url += filters.categories.map(i => '&categories=' + i).join('');
}
if (filters?.age_limit instanceof Array) {
url += filters.age_limit.map(i => `&age_limit=${i}`).join('');
if (filters?.age_limit?.length) {
url += filters.age_limit.map(i => '&age_limit=' + i).join('');
}
url += '&page=' + page;
+14 -2
View File
@@ -1,7 +1,7 @@
import EPubSource from './local/epubSource';
import ComradeMaoScraper from './en/comrademao';
import ReadLightNovelScraper from './en/readlightnovel';
import fastNovelScraper from './en/fastnovel';
import NovelBinScraper from './en/novelbin';
import readNovelFullScraper from './en/readnovelfull';
import mtlNovelScraper from './en/mtlnovel';
import novelhallScraper from './en/novelhall';
@@ -24,12 +24,15 @@ import YuukiTlsScraper from './es/yuukitls';
import NovelaWuxiaScraper from './es/novelawuxia';
import OasisTranslationsScraper from './es/oasistranslations';
import HasuTlScraper from './es/hasutl';
import mtlNovelEsScraper from './es/esmtlnovel';
import RoyalRoadScraper from './en/royalroad';
import ScribbleHubScraper from './en/scribblehub';
import SyosetuScraper from './jp/syosetu';
import LNMTLScraper from './en/lnmtl';
import LightNovelFullScraper from './en/lightnovelfull';
import ICantReadJPTLScraper from './en/icantreadjapanese';
import {
AsuraLightNovelScraper,
ArMTLScraper,
BoxNovelScraper,
ClickNovelScraper,
@@ -142,6 +145,10 @@ import {
RulateScraper,
ErolateScraper,
} from './multisrc/rulate/RulateGenerator';
import {
IfreedomScraper,
BookhamsterScraper,
} from './multisrc/ifreedom/IfreedomGenerator';
import {
RuRanobeScraper,
UkrRanobeScraper,
@@ -191,7 +198,7 @@ export const sourceManager = (sourceId: number): Scraper => {
0: EPubSource, // @ts-ignore
1: BoxNovelScraper, // @ts-ignore
2: ReadLightNovelScraper, // @ts-ignore
3: fastNovelScraper, // @ts-ignore
3: NovelBinScraper, // @ts-ignore
4: readNovelFullScraper, // @ts-ignore
5: mtlNovelScraper, // @ts-ignore
6: novelhallScraper, // @ts-ignore
@@ -336,6 +343,11 @@ export const sourceManager = (sourceId: number): Scraper => {
166: NOVAScraper, // @ts-ignore
167: SmakolykyTlScraper, // @ts-ignore
168: LitSpaceScraper, // @ts-ignore
169: AsuraLightNovelScraper, // @ts-ignore
170: ICantReadJPTLScraper, // @ts-ignore
171: IfreedomScraper, // @ts-ignore
172: BookhamsterScraper, // @ts-ignore
173: mtlNovelEsScraper, // @ts-ignore
};
return scrapers[sourceId];
+37 -2
View File
@@ -15,8 +15,8 @@
},
{
"sourceId": 3,
"url": "https://fastnovel.org/",
"sourceName": "FastNovel",
"url": "https://novelbin.org/",
"sourceName": "NovelBin",
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/icons/src/en/fastnovel/icon.png?raw=true",
"lang": "English"
},
@@ -1027,5 +1027,40 @@
"url": "https://freedlit.space/",
"lang": "Russian",
"icon": "https://freedlit.space/images/fav/apple-touch-icon.png"
},
{
"sourceId": 169,
"url": "https://asuralightnovel.com/",
"sourceName": "Asura Light Novel",
"icon": "https://asuralightnovel.com/wp-content/uploads/2021/06/favicon-asura-2.png",
"lang": "English"
},
{
"sourceId": 170,
"url": "https://icantreadjapanese.wordpress.com/",
"sourceName": "I Cant Read Japanese TL",
"icon": "https://icantreadjapanese.files.wordpress.com/2021/03/cropped-cropped-site-icon-1.png?w=16",
"lang": "English"
},
{
"sourceId": 171,
"url": "https://ifreedom.su",
"sourceName": "Свободный Мир Ранобэ",
"icon": "https://ifreedom.su/wp-content/uploads/2021/03/logo.png",
"lang": "Russian"
},
{
"sourceId": 172,
"url": "https://bookhamster.ru",
"sourceName": "Bookhamster",
"icon": "https://bookhamster.ru/wp-content/uploads/2023/01/log.jpg",
"lang": "Russian"
},
{
"sourceId": 173,
"url": "https://es.mtlnovel.com",
"sourceName": "MTLNovel (ES)",
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/icons/src/en/mtlnovel/icon.png?raw=true",
"lang": "Spanish"
}
]
-12
View File
@@ -1,12 +1,9 @@
import { getSourceStorage } from '@hooks/useSourceStorage';
export const defaultUserAgentString =
'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Mobile Safari/537.36';
interface FetchParams {
url: string; // URL of request
init?: RequestInit; // Variable for passing headers and other information
sourceId?: number; // ID number of source for cookies
}
// Checks if we bypassed cloudflare. If we failed to bypass, throw error.
@@ -27,7 +24,6 @@ export const cloudflareCheck = (text: string) => {
export const fetchApi = async ({
url,
init,
sourceId,
}: FetchParams): Promise<Response> => {
let headers = new Headers({
'User-Agent': defaultUserAgentString,
@@ -38,14 +34,6 @@ export const fetchApi = async ({
// You can have NO user agent by doing this:
// init: { headers: { 'User-Agent': undefined } },
if (sourceId) {
const { cookies = '' } = getSourceStorage(sourceId);
if (cookies) {
headers.append('cookie', cookies);
}
}
return fetch(url, { ...init, headers });
};
+7 -4
View File
@@ -134,9 +134,10 @@
"chooseLocation": "Select EPUB storage directory",
"pathToFolder": "Path to EPUB save folder",
"useReaderTheme": "Use reader theme",
"useCustomCSS": "Use customCSS",
"useCustomJS": "Use customJS",
"useCustomJSWarning": "May not be supported by all EPUB readers"
"useCustomCSS": "Use custom CSS",
"useCustomJS": "Use custom JS",
"useCustomJSWarning": "May not be supported by all EPUB readers",
"chaptersWarning": "Only downloaded chapters are included in the EPUB"
}
},
"more": "More",
@@ -189,7 +190,9 @@
"openJSFile": "Open JS file",
"notSaved": "Not saved",
"cssHint": "Hint: You can create source specific CSS configuration with the source ID. (#sourceId-[SOURCEID])",
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId"
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId",
"clearCustomCSS": "Are you sure? Your custom CSS would be reset.",
"clearCustomJS": "Are you sure? Your custom JS would be reset."
},
"browseSettings": "Browse Settings",
"browseSettingsScreen": {
+13 -10
View File
@@ -88,13 +88,13 @@
"lineHeight": "ارتفاع الخط",
"fontStyle": "نمط الخط",
"fullscreen": "تكبير الشاشة",
"bionicReading": "Bionic Reading",
"bionicReading": "الكلمات ثنائية الخط",
"renderHml": "عرض HTML",
"autoscroll": "تدوير تلقائي",
"verticalSeekbar": "شريط البحث العمودي",
"showBatteryAndTime": "اضهر البطارية والوقت",
"showProgressPercentage": "النسبة المئوية للتقدم",
"swipeGestures": "اسحب لليسار أو لليمين للتبديل بين علامات التبويب",
"swipeGestures": "اسحب لليسار أو لليمين للتنقل بين الفصول",
"allowTextSelection": "السماح باختيار النص",
"useChapterDrawerSwipeNavigation": "مرر يمينا لفتح القائمة الجانبية",
"removeExtraSpacing": "إزالة المسافات الزائدة بين الفقرات",
@@ -131,12 +131,13 @@
}
},
"convertToEpubModal": {
"chooseLocation": "Select EPUB storage directory",
"chooseLocation": "حدد مسار تخزين ملفات EPUB",
"pathToFolder": "مسار مجلد لحفظ EPUB",
"useReaderTheme": "Use reader theme",
"useCustomCSS": "Use customCSS",
"useCustomJS": "Use customJS",
"useCustomJSWarning": "May not be supported by all EPUB readers"
"useReaderTheme": "استخدام قالب القارئ",
"useCustomCSS": "استخدام CSS حسب التفضيل",
"useCustomJS": "استخدام JS حسب التفضيل",
"useCustomJSWarning": "قد لا يكون مدعوماً من كل قراء EPUB",
"chaptersWarning": "الفصول التي تم تنزيلها فقط مدرجة في EPUB"
}
},
"more": "المزيد",
@@ -184,12 +185,14 @@
"saveCustomTheme": "حفظ السمة المخصصة",
"deleteCustomTheme": "حذف السمة المخصصة",
"customCSS": "تنسيقات CSS مخصصة",
"customJS": "Custom JS",
"customJS": "JS حسب التفضيل",
"openCSSFile": "افتح ملف CSS",
"openJSFile": "فتح ملف JS",
"notSaved": "لم يُحفظ",
"cssHint": "تلميح: يمكنك إنشاء تضبيطات CSS مخصصة لمصدر محدد باستخدام معرفه. (#sourceId-[SOURCEID])",
"jsHint": "تلميح: يمكنك الوصول إلى المتغيرات التالية واستخدامها: html, novelName, chapterName, sourceId, chapterId, novelId"
"jsHint": "تلميح: يمكنك الوصول إلى المتغيرات التالية واستخدامها: html, novelName, chapterName, sourceId, chapterId, novelId",
"clearCustomCSS": "هل أنت متأكد؟ سيتم إعادة CSS الخاص بك إلي الحالة الافتراضية.",
"clearCustomJS": "هل أنت متأكد؟ سيتم إعادة JS الخاص بك إلي الحالة الافتراضية."
},
"browseSettings": "تصفح الإعدادات",
"browseSettingsScreen": {
@@ -214,7 +217,7 @@
"statusDistribution": "توزيع الحالات"
},
"globalSearch": {
"searchIn": "البحث في رواية",
"searchIn": "البحث عن رواية في",
"allSources": "جميع المصادر",
"pinnedSources": "المصادر المثبتة"
},
+7 -4
View File
@@ -134,9 +134,10 @@
"chooseLocation": "Select EPUB storage directory",
"pathToFolder": "Path to EPUB save folder",
"useReaderTheme": "Use reader theme",
"useCustomCSS": "Use customCSS",
"useCustomJS": "Use customJS",
"useCustomJSWarning": "May not be supported by all EPUB readers"
"useCustomCSS": "Use custom CSS",
"useCustomJS": "Use custom JS",
"useCustomJSWarning": "May not be supported by all EPUB readers",
"chaptersWarning": "Only downloaded chapters are included in the EPUB"
}
},
"more": "More",
@@ -189,7 +190,9 @@
"openJSFile": "Open JS file",
"notSaved": "Not saved",
"cssHint": "Hint: You can create source specific CSS configuration with the source ID. (#sourceId-[SOURCEID])",
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId"
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId",
"clearCustomCSS": "Are you sure? Your custom CSS would be reset.",
"clearCustomJS": "Are you sure? Your custom JS would be reset."
},
"browseSettings": "Browse Settings",
"browseSettingsScreen": {
+43 -40
View File
@@ -1,50 +1,50 @@
{
"common": {
"cancel": "Cancel",
"cancel": "Zrušit",
"ok": "Ok",
"save": "Save",
"clear": "Clear",
"reset": "Reset",
"search": "Search",
"install": "Install",
"newUpdateAvailable": "New update available",
"categories": "Categories",
"add": "Add",
"edit": "Edit",
"name": "Name",
"searchFor": "Search for",
"globally": "globally",
"searchResults": "Search results",
"chapters": "Chapters",
"submit": "Submit",
"retry": "Retry"
"save": "Uložit",
"clear": "Vymazat",
"reset": "Resetovat",
"search": "Hledat",
"install": "Instalovat",
"newUpdateAvailable": "Nová aktualizace k dispozici",
"categories": "Kategorie",
"add": "Přidat",
"edit": "Upravit",
"name": "Jméno",
"searchFor": "Hledat",
"globally": "globálně",
"searchResults": "Výsledky hledání",
"chapters": "Kapitoly",
"submit": "Odeslat",
"retry": "Opakovat"
},
"library": "Library",
"library": "Knihovna",
"libraryScreen": {
"searchbar": "Search library",
"empty": "Your library is empty. Add series to your library from Browse.",
"searchbar": "Hledat v knihovně",
"empty": "Vaše knihovna je prázdná. Přidejte sérii do knihovny z procházet.",
"bottomSheet": {
"filters": {
"downloaded": "Downloaded",
"unread": "Unread",
"completed": "Completed",
"started": "Started"
"downloaded": "Staženo",
"unread": "Nepřečteno",
"completed": "Dokončeno",
"started": "Zahájeno"
},
"sortOrders": {
"dateAdded": "Date added",
"alphabetically": "Alphabetically",
"totalChapters": "Total chapters",
"download": "Downloaded",
"unread": "Unread",
"lastRead": "Last read",
"lastUpdated": "Last updated"
"dateAdded": "Datum přidání",
"alphabetically": "Abecedně",
"totalChapters": "Celkem kapitol",
"download": "Staženo",
"unread": "Nepřečteno",
"lastRead": "Naposledy přečteno",
"lastUpdated": "Naposledy aktualizováno"
},
"display": {
"displayMode": "Display mode",
"compact": "Compact grid",
"comfortable": "Comfortable grid",
"noTitle": "Cover only grid",
"list": "List",
"displayMode": "Režim zobrazení",
"compact": "Kompaktní mřížka",
"comfortable": "Komfortní mřížka",
"noTitle": "Mřížka pouze s přebaly",
"list": "Seznam",
"badges": "Badges",
"downloadBadges": "Download badges",
"unreadBadges": "Unread badges",
@@ -134,9 +134,10 @@
"chooseLocation": "Select EPUB storage directory",
"pathToFolder": "Path to EPUB save folder",
"useReaderTheme": "Use reader theme",
"useCustomCSS": "Use customCSS",
"useCustomJS": "Use customJS",
"useCustomJSWarning": "May not be supported by all EPUB readers"
"useCustomCSS": "Use custom CSS",
"useCustomJS": "Use custom JS",
"useCustomJSWarning": "May not be supported by all EPUB readers",
"chaptersWarning": "Only downloaded chapters are included in the EPUB"
}
},
"more": "More",
@@ -189,7 +190,9 @@
"openJSFile": "Open JS file",
"notSaved": "Not saved",
"cssHint": "Hint: You can create source specific CSS configuration with the source ID. (#sourceId-[SOURCEID])",
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId"
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId",
"clearCustomCSS": "Are you sure? Your custom CSS would be reset.",
"clearCustomJS": "Are you sure? Your custom JS would be reset."
},
"browseSettings": "Browse Settings",
"browseSettingsScreen": {
+7 -4
View File
@@ -134,9 +134,10 @@
"chooseLocation": "Select EPUB storage directory",
"pathToFolder": "Path to EPUB save folder",
"useReaderTheme": "Use reader theme",
"useCustomCSS": "Use customCSS",
"useCustomJS": "Use customJS",
"useCustomJSWarning": "May not be supported by all EPUB readers"
"useCustomCSS": "Use custom CSS",
"useCustomJS": "Use custom JS",
"useCustomJSWarning": "May not be supported by all EPUB readers",
"chaptersWarning": "Only downloaded chapters are included in the EPUB"
}
},
"more": "More",
@@ -189,7 +190,9 @@
"openJSFile": "Open JS file",
"notSaved": "Not saved",
"cssHint": "Hint: You can create source specific CSS configuration with the source ID. (#sourceId-[SOURCEID])",
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId"
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId",
"clearCustomCSS": "Are you sure? Your custom CSS would be reset.",
"clearCustomJS": "Are you sure? Your custom JS would be reset."
},
"browseSettings": "Browse Settings",
"browseSettingsScreen": {
+19 -16
View File
@@ -88,7 +88,7 @@
"lineHeight": "Zeilenhöhe",
"fontStyle": "Schriftart",
"fullscreen": "Vollbildmodus",
"bionicReading": "Bionic Reading",
"bionicReading": "Bionisches Lesen",
"renderHml": "HTML anzeigen",
"autoscroll": "AutoScroll",
"verticalSeekbar": "Vertikale Suchleiste",
@@ -96,10 +96,10 @@
"showProgressPercentage": "Zeige Fortschritt in Prozent",
"swipeGestures": "Nach links oder rechts wischen, um zwischen den Kapiteln zu wechseln",
"allowTextSelection": "Textauswahl erlauben",
"useChapterDrawerSwipeNavigation": "Wische nach rechts, um Seitenleiste zu öffnen",
"useChapterDrawerSwipeNavigation": "Wische nach rechts, um die Seitenleiste zu öffnen",
"removeExtraSpacing": "Extra Absatzabstand entfernen",
"volumeButtonsScroll": "Scrollen mit Lautstärkeregler",
"scrollAmount": "Scroll amount (screen height by default)",
"scrollAmount": "Höhe des Bildlaufs (standardmäßig Bildschirmhöhe)",
"showSwipeMargins": "Show swipe margins"
},
"drawer": {
@@ -131,12 +131,13 @@
}
},
"convertToEpubModal": {
"chooseLocation": "Select EPUB storage directory",
"pathToFolder": "Path to EPUB save folder",
"chooseLocation": "EPUB-Speicherverzeichnis auswählen",
"pathToFolder": "Pfad zum EPUB-Speicherverzeichnis",
"useReaderTheme": "Use reader theme",
"useCustomCSS": "Use customCSS",
"useCustomJS": "Use customJS",
"useCustomJSWarning": "May not be supported by all EPUB readers"
"useCustomCSS": "Eigene CSS-Datei verwenden",
"useCustomJS": "Eigene JS-Datei verwenden",
"useCustomJSWarning": "Wird möglicherweise nicht von allen EPUB-Readern unterstützt",
"chaptersWarning": "Nur heruntergeladene Kapitel sind im EPUB enthalten"
}
},
"more": "Mehr",
@@ -165,8 +166,8 @@
"updateTime": "Zeige den Zeitpunkt der letzten Aktualisierung",
"autoDownload": "Automatisch herunterladen",
"epub": "EPUB",
"epubLocation": "EPUB Location",
"epubLocationDescription": "The place where you open and export your EPUB files.",
"epubLocation": "EPUB Speicherort",
"epubLocationDescription": "Der Speicherort, an dem Sie Ihre EPUB Dateien öffnen und exportieren.",
"downloadNewChapters": "Neue Kapitel herunterladen",
"disableHapticFeedback": "Haptisches Feedback deaktivieren"
},
@@ -184,12 +185,14 @@
"saveCustomTheme": "Benutzerdefiniertes Theme speichern",
"deleteCustomTheme": "Benutzerdefiniertes Theme löschen",
"customCSS": "Benutzerdefiniertes CSS",
"customJS": "Custom JS",
"openCSSFile": "Open CSS file",
"openJSFile": "Open JS file",
"notSaved": "Not saved",
"cssHint": "Hint: You can create source specific CSS configuration with the source ID. (#sourceId-[SOURCEID])",
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId"
"customJS": "Benutzerdefiniertes CSS",
"openCSSFile": "CSS-Datei öffnen",
"openJSFile": "JS-Datei öffnen",
"notSaved": "Nicht gespeichert",
"cssHint": "Hinweis: Sie können eine quellspezifische CSS-Konfiguration mit der Quell-ID erstellen. (#sourceId-[SOURCEID])",
"jsHint": "Hinweis: Sie haben Zugriff auf folgende Variablen: html, novelName, chapterName, sourceId, chapterId, novelId",
"clearCustomCSS": "Sind Sie sicher? Ihr benutzerdefiniertes CSS wird zurückgesetzt.",
"clearCustomJS": "Sind Sie sicher? Ihr benutzerdefiniertes JS wird zurückgesetzt."
},
"browseSettings": "Einstellungen durchsuchen",
"browseSettingsScreen": {
+7 -4
View File
@@ -134,9 +134,10 @@
"chooseLocation": "Select EPUB storage directory",
"pathToFolder": "Path to EPUB save folder",
"useReaderTheme": "Use reader theme",
"useCustomCSS": "Use customCSS",
"useCustomJS": "Use customJS",
"useCustomJSWarning": "May not be supported by all EPUB readers"
"useCustomCSS": "Use custom CSS",
"useCustomJS": "Use custom JS",
"useCustomJSWarning": "May not be supported by all EPUB readers",
"chaptersWarning": "Only downloaded chapters are included in the EPUB"
}
},
"more": "More",
@@ -189,7 +190,9 @@
"openJSFile": "Open JS file",
"notSaved": "Not saved",
"cssHint": "Hint: You can create source specific CSS configuration with the source ID. (#sourceId-[SOURCEID])",
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId"
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId",
"clearCustomCSS": "Are you sure? Your custom CSS would be reset.",
"clearCustomJS": "Are you sure? Your custom JS would be reset."
},
"browseSettings": "Browse Settings",
"browseSettingsScreen": {
+3 -1
View File
@@ -95,6 +95,7 @@
"showBatteryAndTime": "Show battery and time",
"showProgressPercentage": "Show progress percentage",
"swipeGestures": "Swipe left or right to navigate between chapters",
"readerPages": "Tap left or right to navigate through pages in the reader (Experimental)",
"allowTextSelection": "Allow text selection",
"useChapterDrawerSwipeNavigation": "Swipe right to open drawer",
"removeExtraSpacing": "Remove extra paragraph spacing",
@@ -169,7 +170,8 @@
"epubLocation": "EPUB Location",
"epubLocationDescription": "The place where you open and export your EPUB files.",
"downloadNewChapters": "Download new chapters",
"disableHapticFeedback": "Disable haptic feedback"
"disableHapticFeedback": "Disable haptic feedback",
"jumpToLastReadChapter": "Jump to last read chapter in list"
},
"readerSettings": {
"title": "Reader",
+158 -155
View File
@@ -17,7 +17,7 @@
"searchResults": "Resultados de la búsqueda",
"chapters": "Capítulos",
"submit": "Enviar",
"retry": "Intentar nuevamente"
"retry": "Reintentar"
},
"library": "Biblioteca",
"libraryScreen": {
@@ -36,191 +36,194 @@
"totalChapters": "Capítulos totales",
"download": "Descargado",
"unread": "No leído",
"lastRead": "Last read",
"lastUpdated": "Last updated"
"lastRead": "Última lectura",
"lastUpdated": "Última actualización"
},
"display": {
"displayMode": "Display mode",
"compact": "Compact grid",
"comfortable": "Comfortable grid",
"noTitle": "Cover only grid",
"list": "List",
"badges": "Badges",
"downloadBadges": "Download badges",
"unreadBadges": "Unread badges",
"showNoOfItems": "Show number of items"
"displayMode": "Modo de visualización",
"compact": "Rejilla compacta",
"comfortable": "Rejilla cómoda",
"noTitle": "Cubrir solo la rejilla",
"list": "Lista",
"badges": "Insignias",
"downloadBadges": "Descargar insignias",
"unreadBadges": "Insignias no leídas",
"showNoOfItems": "Mostrar número de elementos"
}
}
},
"updates": "Updates",
"updates": "Actualizaciones",
"updatesScreen": {
"searchbar": "Search updates",
"lastUpdatedAt": "Library last updated:",
"newChapters": "new Chapters",
"emptyView": "No recent updates",
"updatesLower": "updates"
"searchbar": "Buscar actualizaciones",
"lastUpdatedAt": "Biblioteca actualizada por última vez:",
"newChapters": "Nuevos capítulos",
"emptyView": "No hay actualizaciones recientes",
"updatesLower": "actualizaciones"
},
"history": "History",
"history": "Historial",
"historyScreen": {
"searchbar": "Search history",
"clearHistorWarning": "Are you sure? All history will be lost."
"searchbar": "Historial de búsqueda",
"clearHistorWarning": "¿Estás seguro? Todo el historial se perderá."
},
"browse": "Browse",
"browse": "Navegar",
"browseScreen": {
"discover": "Discover",
"searchbar": "Search sources",
"globalSearch": "Global search",
"listEmpty": "Enable languages from settings",
"lastUsed": "Last used",
"pinned": "Pinned",
"all": "All",
"latest": "Latest",
"addedToLibrary": "Added to library",
"removeFromLibrary": "Removed from library"
"discover": "Explorar",
"searchbar": "Buscar fuente",
"globalSearch": "Búsqueda global",
"listEmpty": "Habilitar idiomas desde la configuración",
"lastUsed": "Última usada",
"pinned": "Fijadas",
"all": "Todos",
"latest": "Últimos",
"addedToLibrary": "Añadido a la biblioteca",
"removeFromLibrary": "Eliminar de la biblioteca"
},
"readerScreen": {
"finished": "Finished",
"noNextChapter": "There's no next chapter",
"finished": "Finalizado",
"noNextChapter": "No hay próximo capítulo",
"bottomSheet": {
"textSize": "Text size",
"textSize": "Tamaño del texto",
"color": "Color",
"textAlign": "Text align",
"lineHeight": "Line Height",
"fontStyle": "Font style",
"fullscreen": "Fullscreen",
"bionicReading": "Bionic Reading",
"renderHml": "Render HTML",
"autoscroll": "AutoScroll",
"verticalSeekbar": "Vertical Seekbar",
"showBatteryAndTime": "Show battery and time",
"showProgressPercentage": "Show progress percentage",
"swipeGestures": "Swipe left or right to navigate between chapters",
"allowTextSelection": "Allow text selection",
"useChapterDrawerSwipeNavigation": "Swipe right to open drawer",
"removeExtraSpacing": "Remove extra paragraph spacing",
"volumeButtonsScroll": "Volume buttons scroll",
"scrollAmount": "Scroll amount (screen height by default)",
"showSwipeMargins": "Show swipe margins"
"textAlign": "Alineación del texto",
"lineHeight": "Interlineado",
"fontStyle": "Estilo de fuente",
"fullscreen": "Pantalla completa",
"bionicReading": "Lectura biónica",
"renderHml": "Renderizar HTML",
"autoscroll": "Desplazamiento automático",
"verticalSeekbar": "Barra de búsqueda vertical",
"showBatteryAndTime": "Mostrar batería y tiempo",
"showProgressPercentage": "Mostrar porcentaje de progreso",
"swipeGestures": "Desliza a la izquierda o a la derecha para navegar entre los capítulos",
"allowTextSelection": "Permitir selección de texto",
"useChapterDrawerSwipeNavigation": "Desliza hacia la derecha para abrir el cajón",
"removeExtraSpacing": "Eliminar espaciado extra del párrafo",
"volumeButtonsScroll": "Deslizamiento de botones de volumen",
"scrollAmount": "Cantidad de desplazamiento (alto de la pantalla de forma predeterminada)",
"showSwipeMargins": "Mostrar márgenes de deslizamiento"
},
"drawer": {
"scrollToCurrentChapter": "Scroll to current chapter",
"scrollToTop": "Scroll to top",
"scrollToBottom": "Scroll to bottom"
"scrollToCurrentChapter": "Desplazar al capítulo actual",
"scrollToTop": "Desplazar a arriba",
"scrollToBottom": "Desplazarse hasta abajo"
}
},
"novelScreen": {
"addToLibaray": "Add to library",
"inLibaray": "In library",
"continueReading": "Continue reading",
"chapters": "chapters",
"migrate": "Migrate",
"noSummary": "No summary",
"addToLibaray": "Añadir a la biblioteca",
"inLibaray": "En biblioteca",
"continueReading": "Continuar leyendo",
"chapters": "capítulos",
"migrate": "Migrar",
"noSummary": "Sin resumen",
"bottomSheet": {
"filter": "Filter",
"sort": "Sort",
"display": "Display"
"filter": "Filtrar",
"sort": "Ordenar",
"display": "Visualización"
},
"jumpToChapterModal": {
"jumpToChapter": "Jump to Chapter",
"openChapter": "Open Chapter",
"chapterName": "Chapter Name",
"chapterNumber": "Chapter Number",
"jumpToChapter": "Saltar al capítulo",
"openChapter": "Abrir Capítulo",
"chapterName": "Nombre del capítulo",
"chapterNumber": "Número del capítulo",
"error": {
"validChapterName": "Enter a valid chapter name",
"validChapterNumber": "Enter a valid chapter number"
"validChapterName": "Introduzca un nombre de capítulo válido",
"validChapterNumber": "Introduzca un número de capítulo válido"
}
},
"convertToEpubModal": {
"chooseLocation": "Select EPUB storage directory",
"pathToFolder": "Path to EPUB save folder",
"useReaderTheme": "Use reader theme",
"useCustomCSS": "Use customCSS",
"useCustomJS": "Use customJS",
"useCustomJSWarning": "May not be supported by all EPUB readers"
"chooseLocation": "Seleccionar directorio de almacenamiento EPUB",
"pathToFolder": "Ruta a la carpeta de guardado de EPUB",
"useReaderTheme": "Usar tema del lector",
"useCustomCSS": "Usar CSS personalizado",
"useCustomJS": "Usar JS personalizado",
"useCustomJSWarning": "Puede no ser soportado por todos los lectores de EPUB",
"chaptersWarning": "Sólo los capítulos descargados están incluidos en el EPUB"
}
},
"more": "More",
"more": "Más",
"moreScreen": {
"settings": "Settings",
"settings": "Configuración",
"settingsScreen": {
"generalSettings": "General",
"generalSettingsScreen": {
"display": "Display",
"displayMode": "Display Mode",
"itemsPerRow": "Items per row",
"itemsPerRowLibrary": "Items per row in library",
"novelBadges": "Novel Badges",
"novelSort": "Novel Sort",
"updateLibrary": "Update library on launch",
"useFAB": "Use FAB in Library",
"novel": "Novel",
"chapterSort": "Default chapter sort",
"bySource": "By source",
"asc": "(Ascending)",
"desc": "(Descending)",
"globalUpdate": "Global update",
"updateOngoing": "Only update ongoing novels",
"refreshMetadata": "Automatically refresh metadata",
"refreshMetadataDescription": "Check for new cover and details when updating library",
"updateTime": "Show last update time",
"autoDownload": "Auto-download",
"display": "Visualización",
"displayMode": "Modo de visualización",
"itemsPerRow": "Elementos por fila",
"itemsPerRowLibrary": "Elementos por fila en la biblioteca",
"novelBadges": "Insignia de novelas",
"novelSort": "Ordenar novela",
"updateLibrary": "Actualizar biblioteca al iniciar",
"useFAB": "Usar FAB en la biblioteca",
"novel": "Novela",
"chapterSort": "Orden de capítulo por defecto",
"bySource": "Por fuente",
"asc": "(Ascendente)",
"desc": "(Descendente)",
"globalUpdate": "Actualización global",
"updateOngoing": "Solo actualizar novelas en curso",
"refreshMetadata": "Actualizar automáticamente los metadatos",
"refreshMetadataDescription": "Buscar nueva portada y detalles al actualizar la biblioteca",
"updateTime": "Mostrar la última actualización",
"autoDownload": "Descarga automática",
"epub": "EPUB",
"epubLocation": "EPUB Location",
"epubLocationDescription": "The place where you open and export your EPUB files.",
"downloadNewChapters": "Download new chapters",
"disableHapticFeedback": "Disable haptic feedback"
"epubLocation": "Ubicación EPUB",
"epubLocationDescription": "El lugar donde se abren y exportan sus archivos EPUB.",
"downloadNewChapters": "Descargar nuevos capítulos",
"disableHapticFeedback": "Deshabilitar la respuesta háptica"
},
"readerSettings": {
"title": "Reader",
"readerTheme": "Reader theme",
"preset": "Preset",
"backgroundColor": "Background color",
"textColor": "Text color",
"backgroundColorModal": "Reader background color",
"textColorModal": "Reader text color",
"verticalSeekbarDesc": "Switch between vertical and horizontal seekbar",
"autoScrollInterval": "Auto scroll interval (in seconds)",
"autoScrollOffset": "Auto scroll offset (screen height by default)",
"saveCustomTheme": "Save custom theme",
"deleteCustomTheme": "Delete custom theme",
"customCSS": "Custom CSS",
"customJS": "Custom JS",
"openCSSFile": "Open CSS file",
"openJSFile": "Open JS file",
"notSaved": "Not saved",
"cssHint": "Hint: You can create source specific CSS configuration with the source ID. (#sourceId-[SOURCEID])",
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId"
"title": "Lector",
"readerTheme": "Tema del lector",
"preset": "Preajuste",
"backgroundColor": "Color de fondo",
"textColor": "Color del texto",
"backgroundColorModal": "Color de fondo del lector",
"textColorModal": "Color del texto del lector",
"verticalSeekbarDesc": "Cambiar entre barra de búsqueda vertical y horizontal",
"autoScrollInterval": "Intervalo de desplazamiento automático (en segundos)",
"autoScrollOffset": "Desplazamiento automático de desplazamiento (altura de pantalla por defecto)",
"saveCustomTheme": "Guardar tema personalizado",
"deleteCustomTheme": "Eliminar tema personalizado",
"customCSS": "CSS personalizado",
"customJS": "JS Personalizado",
"openCSSFile": "Abrir archivo CSS",
"openJSFile": "Abrir archivo JS",
"notSaved": "No guardado",
"cssHint": "Sugerencia: Puedes crear una configuración CSS específica de origen con el ID de origen. (#sourceId-[SOURCEID])",
"jsHint": "Sugerencia: Tiene acceso a las siguientes variables: html, novelName, chapterName, sourceId, chapterId, novelId",
"clearCustomCSS": "¿Estás seguro? Se restablecería tu CSS personalizado.",
"clearCustomJS": "¿Estás seguro? Tu JS personalizado se restablecería."
},
"browseSettings": "Browse Settings",
"browseSettings": "Ajustes de Navegación",
"browseSettingsScreen": {
"onlyShowPinnedSources": "Only show pinned sources",
"languages": "Languages",
"searchAllSources": "Search all sources",
"searchAllWarning": "Searching a large number of sources may freeze the app till searching is finished."
"onlyShowPinnedSources": "Mostrar sólo fuentes fijadas",
"languages": "Idiomas",
"searchAllSources": "Buscar en todas las fuentes",
"searchAllWarning": "Buscar un gran número de fuentes puede congelar la aplicación hasta que la búsqueda haya finalizado."
}
}
},
"sourceScreen": {
"noResultsFound": "No results found"
"noResultsFound": "No se han encontrado resultados"
},
"statsScreen": {
"title": "Statistics",
"titlesInLibrary": "Titles in library",
"totalChapters": "Total chapters",
"readChapters": "Read chapters",
"unreadChapters": "Unread chapters",
"downloadedChapters": "Downloaded chapters",
"genreDistribution": "Genre distribution",
"statusDistribution": "Status distribution"
"title": "Estadísticas",
"titlesInLibrary": "Títulos en biblioteca",
"totalChapters": "Capítulos totales",
"readChapters": "Leer capítulos",
"unreadChapters": "Capítulos no leídos",
"downloadedChapters": "Capítulos descargados",
"genreDistribution": "Distribución de género",
"statusDistribution": "Distribución de estado"
},
"globalSearch": {
"searchIn": "Search a novel in",
"allSources": "all sources",
"pinnedSources": "pinned sources"
"searchIn": "Buscar una novela en",
"allSources": "Todas las fuentes",
"pinnedSources": "fuentes fijadas"
},
"advancedSettings": {
"deleteReadChapters": "Delete read chapters",
"deleteReadChaptersDialogTitle": "Are you sure? All chapters marked as read will be deleted."
"deleteReadChapters": "Eliminar capítulos leídos",
"deleteReadChaptersDialogTitle": "¿Estás seguro? Se eliminarán todos los capítulos marcados como leídos."
},
"date": {
"calendar": {
@@ -231,27 +234,27 @@
}
},
"categories": {
"addCategories": "Add category",
"editCategories": "Rename category",
"setCategories": "Set categories",
"header": "Edit categories",
"emptyMsg": "You have no categories. Tap the plus button to create one for organizing your library",
"setModalEmptyMsg": "You have no categories. Tap the Edit button to create one for organizing your library",
"addCategories": "Añadir categoría",
"editCategories": "Renombrar categoría",
"setCategories": "Establecer Categorías",
"header": "Editar categorías",
"emptyMsg": "No tienes categorías. Pulsa el botón más para crear una para organizar tu biblioteca",
"setModalEmptyMsg": "No tienes categorías. Pulsa el botón Editar para crear una para organizar tu biblioteca",
"deleteModal": {
"header": "Delete category",
"desc": "Do you wish to delete category"
"header": "Eliminar categoría",
"desc": "¿Deseas eliminar la categoría?"
},
"duplicateError": "A category with this name already exists!",
"defaultCategory": "Default category"
"duplicateError": "¡Ya existe una categoría con este nombre!",
"defaultCategory": "Categoría por defecto"
},
"settings": {
"icognitoMode": "Incognito mode",
"downloadedOnly": "Downloaded only"
"icognitoMode": "Modo Incógnito",
"downloadedOnly": "Sólo descargado"
},
"downloadScreen": {
"dbInfo": "Downloads are saved in a SQLite Database.",
"downloadChapters": "downloaded Chapters",
"noDownloads": "No downloads",
"downloadsLower": "downloads"
"dbInfo": "Las descargas se guardan en una base de datos SQLite.",
"downloadChapters": "capítulos descargados",
"noDownloads": "No hay descargas",
"downloadsLower": "descargas"
}
}
+7 -4
View File
@@ -134,9 +134,10 @@
"chooseLocation": "Select EPUB storage directory",
"pathToFolder": "Path to EPUB save folder",
"useReaderTheme": "Use reader theme",
"useCustomCSS": "Use customCSS",
"useCustomJS": "Use customJS",
"useCustomJSWarning": "May not be supported by all EPUB readers"
"useCustomCSS": "Use custom CSS",
"useCustomJS": "Use custom JS",
"useCustomJSWarning": "May not be supported by all EPUB readers",
"chaptersWarning": "Only downloaded chapters are included in the EPUB"
}
},
"more": "More",
@@ -189,7 +190,9 @@
"openJSFile": "Open JS file",
"notSaved": "Not saved",
"cssHint": "Hint: You can create source specific CSS configuration with the source ID. (#sourceId-[SOURCEID])",
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId"
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId",
"clearCustomCSS": "Are you sure? Your custom CSS would be reset.",
"clearCustomJS": "Are you sure? Your custom JS would be reset."
},
"browseSettings": "Browse Settings",
"browseSettingsScreen": {
+15 -12
View File
@@ -98,13 +98,13 @@
"allowTextSelection": "Permettre la sélection du texte",
"useChapterDrawerSwipeNavigation": "Balayez vers la droite pour ouvrir le tiroir",
"removeExtraSpacing": "Supprimer l'espacement de paragraphe supplémentaire",
"volumeButtonsScroll": "Défilement avec les boutons de volume",
"volumeButtonsScroll": "Défilement avec boutons de volume",
"scrollAmount": "Défilement du montant (hauteur de l'écran par défaut)",
"showSwipeMargins": "Afficher les marges de balayage"
},
"drawer": {
"scrollToCurrentChapter": "Défiler vers le chapitre actuel",
"scrollToTop": "Défiler en haut",
"scrollToTop": "Défiler jusqu'en haut",
"scrollToBottom": "Défiler jusqu'en bas"
}
},
@@ -131,12 +131,13 @@
}
},
"convertToEpubModal": {
"chooseLocation": "Select EPUB storage directory",
"pathToFolder": "Path to EPUB save folder",
"useReaderTheme": "Use reader theme",
"useCustomCSS": "Use customCSS",
"useCustomJS": "Use customJS",
"useCustomJSWarning": "May not be supported by all EPUB readers"
"chooseLocation": "Sélectionner répertoire de stockage EPUB",
"pathToFolder": "Chemin vers le dossier de sauvegarde d'EPUB",
"useReaderTheme": "Utiliser le mode lecture",
"useCustomCSS": "Utiliser le CSS personnalisé",
"useCustomJS": "Utiliser JS personnalisé",
"useCustomJSWarning": "Peut ne pas être supporté par tous les lecteurs d'EPUB",
"chaptersWarning": "Seuls les chapitres téléchargés sont inclus dans l'EPUB"
}
},
"more": "Suite",
@@ -149,7 +150,7 @@
"displayMode": "Mode d'affichage",
"itemsPerRow": "Élément par ligne",
"itemsPerRowLibrary": "Éléments par ligne dans la bibliothèque",
"novelBadges": "Badges de roman",
"novelBadges": "Badges de Novel",
"novelSort": "Trier les romans",
"updateLibrary": "Mettre à jour la bibliothèque au lancement",
"useFAB": "Utiliser FAB dans la bibliothèque",
@@ -165,8 +166,8 @@
"updateTime": "Afficher la date de dernière mise à jour",
"autoDownload": "Téléchargement automatique",
"epub": "EPUB",
"epubLocation": "EPUB Location",
"epubLocationDescription": "The place where you open and export your EPUB files.",
"epubLocation": "Localisation EPUB",
"epubLocationDescription": "L'endroit où vous ouvrez et exportez vos fichiers EPUB.",
"downloadNewChapters": "Télécharger les nouveaux chapitres",
"disableHapticFeedback": "Désactiver le retour haptique"
},
@@ -189,7 +190,9 @@
"openJSFile": "Ouvrir le fichier JS",
"notSaved": "Non enregistré",
"cssHint": "Astuce : Vous pouvez créer une configuration CSS spécifique à la source avec l'ID source. (#sourceId-[SOURCEID])",
"jsHint": "Astuce : Vous avez accès aux variables suivantes : html, novelName, chapterName, sourceId, chapterId, novelId"
"jsHint": "Astuce : Vous avez accès aux variables suivantes : html, novelName, chapterName, sourceId, chapterId, novelId",
"clearCustomCSS": "Êtes-vous sûr? Votre CSS personnalisé serait réinitialisé.",
"clearCustomJS": "Êtes-vous sûr? Votre JS personnalisé serait réinitialisé."
},
"browseSettings": "Parcourir les paramètres",
"browseSettingsScreen": {
+7 -4
View File
@@ -134,9 +134,10 @@
"chooseLocation": "Select EPUB storage directory",
"pathToFolder": "Path to EPUB save folder",
"useReaderTheme": "Use reader theme",
"useCustomCSS": "Use customCSS",
"useCustomJS": "Use customJS",
"useCustomJSWarning": "May not be supported by all EPUB readers"
"useCustomCSS": "Use custom CSS",
"useCustomJS": "Use custom JS",
"useCustomJSWarning": "May not be supported by all EPUB readers",
"chaptersWarning": "Only downloaded chapters are included in the EPUB"
}
},
"more": "More",
@@ -189,7 +190,9 @@
"openJSFile": "Open JS file",
"notSaved": "Not saved",
"cssHint": "Hint: You can create source specific CSS configuration with the source ID. (#sourceId-[SOURCEID])",
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId"
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId",
"clearCustomCSS": "Are you sure? Your custom CSS would be reset.",
"clearCustomJS": "Are you sure? Your custom JS would be reset."
},
"browseSettings": "Browse Settings",
"browseSettingsScreen": {
+7 -4
View File
@@ -134,9 +134,10 @@
"chooseLocation": "Select EPUB storage directory",
"pathToFolder": "Path to EPUB save folder",
"useReaderTheme": "Use reader theme",
"useCustomCSS": "Use customCSS",
"useCustomJS": "Use customJS",
"useCustomJSWarning": "May not be supported by all EPUB readers"
"useCustomCSS": "Use custom CSS",
"useCustomJS": "Use custom JS",
"useCustomJSWarning": "May not be supported by all EPUB readers",
"chaptersWarning": "Only downloaded chapters are included in the EPUB"
}
},
"more": "More",
@@ -189,7 +190,9 @@
"openJSFile": "Open JS file",
"notSaved": "Not saved",
"cssHint": "Hint: You can create source specific CSS configuration with the source ID. (#sourceId-[SOURCEID])",
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId"
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId",
"clearCustomCSS": "Are you sure? Your custom CSS would be reset.",
"clearCustomJS": "Are you sure? Your custom JS would be reset."
},
"browseSettings": "Browse Settings",
"browseSettingsScreen": {
+7 -4
View File
@@ -134,9 +134,10 @@
"chooseLocation": "Select EPUB storage directory",
"pathToFolder": "Path to EPUB save folder",
"useReaderTheme": "Use reader theme",
"useCustomCSS": "Use customCSS",
"useCustomJS": "Use customJS",
"useCustomJSWarning": "May not be supported by all EPUB readers"
"useCustomCSS": "Use custom CSS",
"useCustomJS": "Use custom JS",
"useCustomJSWarning": "May not be supported by all EPUB readers",
"chaptersWarning": "Only downloaded chapters are included in the EPUB"
}
},
"more": "Több",
@@ -189,7 +190,9 @@
"openJSFile": "JS fájl megnyitása",
"notSaved": "Nem mentett",
"cssHint": "Hint: You can create source specific CSS configuration with the source ID. (#sourceId-[SOURCEID])",
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId"
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId",
"clearCustomCSS": "Are you sure? Your custom CSS would be reset.",
"clearCustomJS": "Are you sure? Your custom JS would be reset."
},
"browseSettings": "Browse Settings",
"browseSettingsScreen": {
File diff suppressed because one or more lines are too long
+19 -16
View File
@@ -88,7 +88,7 @@
"lineHeight": "Altezza riga",
"fontStyle": "Stile del font",
"fullscreen": "Schermo Intero",
"bionicReading": "Bionic Reading",
"bionicReading": "Lettura Bionica",
"renderHml": "Renderizza HTML",
"autoscroll": "Scorrimento automatico",
"verticalSeekbar": "Barra Verticale",
@@ -131,12 +131,13 @@
}
},
"convertToEpubModal": {
"chooseLocation": "Select EPUB storage directory",
"pathToFolder": "Path to EPUB save folder",
"useReaderTheme": "Use reader theme",
"useCustomCSS": "Use customCSS",
"useCustomJS": "Use customJS",
"useCustomJSWarning": "May not be supported by all EPUB readers"
"chooseLocation": "Seleziona la cartella di archiviazione EPUB",
"pathToFolder": "Percorso cartella di salvataggio EPUB",
"useReaderTheme": "Usa tema lettore",
"useCustomCSS": "Usa CSS personalizzato",
"useCustomJS": "Usa JS personalizzato",
"useCustomJSWarning": "Potrebbe non essere supportato da tutti i lettori EPUB",
"chaptersWarning": "Solo i capitoli scaricati sono inclusi nell'EPUB"
}
},
"more": "Altro",
@@ -149,8 +150,8 @@
"displayMode": "Modalità di visualizzazione",
"itemsPerRow": "Elementi per riga",
"itemsPerRowLibrary": "Elementi per riga nella libreria",
"novelBadges": "Badge dei romanzi",
"novelSort": "Ordine romanzi",
"novelBadges": "Badge delle novel",
"novelSort": "Ordine novel",
"updateLibrary": "Aggiorna la libreria all'avvio",
"useFAB": "Usa FAB nella libreria",
"novel": "Romanzo",
@@ -159,14 +160,14 @@
"asc": "(Crescente)",
"desc": "(Decrescente)",
"globalUpdate": "Aggiornamento globale",
"updateOngoing": "Aggiorna solo i romanzi in corso",
"updateOngoing": "Aggiorna solo le novel in corso",
"refreshMetadata": "Aggiorna automaticamente i metadati",
"refreshMetadataDescription": "Controlla nuove copertine e dettagli durante l'aggiornamento della libreria",
"updateTime": "Mostra l'orario dell'ultimo aggiornamento",
"autoDownload": "Scarica automaticamente",
"epub": "EPUB",
"epubLocation": "EPUB Location",
"epubLocationDescription": "The place where you open and export your EPUB files.",
"epubLocation": "Posizione EPUB",
"epubLocationDescription": "Il posto dove apri ed esporti i tuoi file EPUB.",
"downloadNewChapters": "Scarica nuovi capitoli",
"disableHapticFeedback": "Disattiva vibrazione"
},
@@ -188,8 +189,10 @@
"openCSSFile": "Apri file CSS",
"openJSFile": "Apri file JS",
"notSaved": "Non salvato",
"cssHint": "Suggerimento: è possibile creare una configurazione CSS specifica con l'ID sorgente. (#sourceId-[SOURCEID])",
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId"
"cssHint": "Consiglio: è possibile creare una configurazione CSS specifica con l'ID sorgente. (#sourceId-[SOURCEID])",
"jsHint": "Consiglio: Hai accesso alle variabili seguenti: html, novelName, chapterName, sourceId, chapterId, novelId",
"clearCustomCSS": "Sei sicuro? Il CSS personalizzato verrebbe resettato.",
"clearCustomJS": "Sei sicuro? Il tuo JS personalizzato verrebbe resettato."
},
"browseSettings": "Impostazioni Esplora",
"browseSettingsScreen": {
@@ -214,7 +217,7 @@
"statusDistribution": "Distribuzione dello stato"
},
"globalSearch": {
"searchIn": "Cerca un romanzo in",
"searchIn": "Cerca una novel in",
"allSources": "Tutte le fonti",
"pinnedSources": "Fonti fissate"
},
@@ -235,7 +238,7 @@
"editCategories": "Rinomina categoria",
"setCategories": "Imposta categorie",
"header": "Modifica categorie",
"emptyMsg": "Non hai categorie. Tocca il pulsante più per crearne una per organizzare la tua libreria",
"emptyMsg": "Non hai categorie. Tocca il pulsante + per crearne una per organizzare la tua libreria",
"setModalEmptyMsg": "Non hai categorie. Tocca il pulsante Modifica per crearne una per organizzare la tua libreria",
"deleteModal": {
"header": "Elimina categoria",
+7 -4
View File
@@ -134,9 +134,10 @@
"chooseLocation": "Select EPUB storage directory",
"pathToFolder": "Path to EPUB save folder",
"useReaderTheme": "Use reader theme",
"useCustomCSS": "Use customCSS",
"useCustomJS": "Use customJS",
"useCustomJSWarning": "May not be supported by all EPUB readers"
"useCustomCSS": "Use custom CSS",
"useCustomJS": "Use custom JS",
"useCustomJSWarning": "May not be supported by all EPUB readers",
"chaptersWarning": "Only downloaded chapters are included in the EPUB"
}
},
"more": "More",
@@ -189,7 +190,9 @@
"openJSFile": "Open JS file",
"notSaved": "Not saved",
"cssHint": "Hint: You can create source specific CSS configuration with the source ID. (#sourceId-[SOURCEID])",
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId"
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId",
"clearCustomCSS": "Are you sure? Your custom CSS would be reset.",
"clearCustomJS": "Are you sure? Your custom JS would be reset."
},
"browseSettings": "Browse Settings",
"browseSettingsScreen": {
+7 -4
View File
@@ -134,9 +134,10 @@
"chooseLocation": "Select EPUB storage directory",
"pathToFolder": "Path to EPUB save folder",
"useReaderTheme": "Use reader theme",
"useCustomCSS": "Use customCSS",
"useCustomJS": "Use customJS",
"useCustomJSWarning": "May not be supported by all EPUB readers"
"useCustomCSS": "Use custom CSS",
"useCustomJS": "Use custom JS",
"useCustomJSWarning": "May not be supported by all EPUB readers",
"chaptersWarning": "Only downloaded chapters are included in the EPUB"
}
},
"more": "More",
@@ -189,7 +190,9 @@
"openJSFile": "Open JS file",
"notSaved": "Not saved",
"cssHint": "Hint: You can create source specific CSS configuration with the source ID. (#sourceId-[SOURCEID])",
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId"
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId",
"clearCustomCSS": "Are you sure? Your custom CSS would be reset.",
"clearCustomJS": "Are you sure? Your custom JS would be reset."
},
"browseSettings": "Browse Settings",
"browseSettingsScreen": {
+7 -4
View File
@@ -134,9 +134,10 @@
"chooseLocation": "Select EPUB storage directory",
"pathToFolder": "Path to EPUB save folder",
"useReaderTheme": "Use reader theme",
"useCustomCSS": "Use customCSS",
"useCustomJS": "Use customJS",
"useCustomJSWarning": "May not be supported by all EPUB readers"
"useCustomCSS": "Use custom CSS",
"useCustomJS": "Use custom JS",
"useCustomJSWarning": "May not be supported by all EPUB readers",
"chaptersWarning": "Only downloaded chapters are included in the EPUB"
}
},
"more": "Meer",
@@ -189,7 +190,9 @@
"openJSFile": "Open JS file",
"notSaved": "Not saved",
"cssHint": "Hint: You can create source specific CSS configuration with the source ID. (#sourceId-[SOURCEID])",
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId"
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId",
"clearCustomCSS": "Are you sure? Your custom CSS would be reset.",
"clearCustomJS": "Are you sure? Your custom JS would be reset."
},
"browseSettings": "Blader instellingen",
"browseSettingsScreen": {
+7 -4
View File
@@ -134,9 +134,10 @@
"chooseLocation": "Select EPUB storage directory",
"pathToFolder": "Path to EPUB save folder",
"useReaderTheme": "Use reader theme",
"useCustomCSS": "Use customCSS",
"useCustomJS": "Use customJS",
"useCustomJSWarning": "May not be supported by all EPUB readers"
"useCustomCSS": "Use custom CSS",
"useCustomJS": "Use custom JS",
"useCustomJSWarning": "May not be supported by all EPUB readers",
"chaptersWarning": "Only downloaded chapters are included in the EPUB"
}
},
"more": "More",
@@ -189,7 +190,9 @@
"openJSFile": "Open JS file",
"notSaved": "Not saved",
"cssHint": "Hint: You can create source specific CSS configuration with the source ID. (#sourceId-[SOURCEID])",
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId"
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId",
"clearCustomCSS": "Are you sure? Your custom CSS would be reset.",
"clearCustomJS": "Are you sure? Your custom JS would be reset."
},
"browseSettings": "Browse Settings",
"browseSettingsScreen": {
+260
View File
@@ -0,0 +1,260 @@
{
"common": {
"cancel": "Cancel",
"ok": "ଠିକ୍ ଅଛି",
"save": "ସଞ୍ଚୟ କରିବା",
"clear": "ସଫା କରିବା",
"reset": "Reset",
"search": "ସନ୍ଧାନ",
"install": "Install",
"newUpdateAvailable": "ନୂଆ ଅଦ୍ୟତନ ଉପଲବ୍ଧ",
"categories": "ବର୍ଗ",
"add": "ଯୋଡ଼ନ୍ତୁ",
"edit": "ସମ୍ପାଦନା",
"name": "ନାମ",
"searchFor": "Search for",
"globally": "globally",
"searchResults": "Search results",
"chapters": "ଅଧ୍ୟାୟ",
"submit": "Submit",
"retry": "ପୁନଃ ଚେଷ୍ଟା କରିବା"
},
"library": "Library",
"libraryScreen": {
"searchbar": "Search library",
"empty": "Your library is empty. Add series to your library from Browse.",
"bottomSheet": {
"filters": {
"downloaded": "Downloaded",
"unread": "Unread",
"completed": "Completed",
"started": "Started"
},
"sortOrders": {
"dateAdded": "ଯୋଡ଼ାଯିବା ତାରିଖ",
"alphabetically": "Alphabetically",
"totalChapters": "Total chapters",
"download": "Downloaded",
"unread": "Unread",
"lastRead": "Last read",
"lastUpdated": "Last updated"
},
"display": {
"displayMode": "Display mode",
"compact": "Compact grid",
"comfortable": "Comfortable grid",
"noTitle": "Cover only grid",
"list": "ତାଲିକା",
"badges": "Badges",
"downloadBadges": "Download badges",
"unreadBadges": "Unread badges",
"showNoOfItems": "Show number of items"
}
}
},
"updates": "Updates",
"updatesScreen": {
"searchbar": "Search updates",
"lastUpdatedAt": "Library last updated:",
"newChapters": "new Chapters",
"emptyView": "No recent updates",
"updatesLower": "updates"
},
"history": "History",
"historyScreen": {
"searchbar": "Search history",
"clearHistorWarning": "Are you sure? All history will be lost."
},
"browse": "Browse",
"browseScreen": {
"discover": "Discover",
"searchbar": "Search sources",
"globalSearch": "Global search",
"listEmpty": "Enable languages from settings",
"lastUsed": "Last used",
"pinned": "Pinned",
"all": "All",
"latest": "Latest",
"addedToLibrary": "Added to library",
"removeFromLibrary": "Removed from library"
},
"readerScreen": {
"finished": "Finished",
"noNextChapter": "There's no next chapter",
"bottomSheet": {
"textSize": "Text size",
"color": "Color",
"textAlign": "Text align",
"lineHeight": "Line Height",
"fontStyle": "Font style",
"fullscreen": "Fullscreen",
"bionicReading": "Bionic Reading",
"renderHml": "Render HTML",
"autoscroll": "AutoScroll",
"verticalSeekbar": "Vertical Seekbar",
"showBatteryAndTime": "Show battery and time",
"showProgressPercentage": "Show progress percentage",
"swipeGestures": "ଅଧ୍ୟାୟଗୁଡ଼ିକ ମଧ୍ୟରେ ଚଳନ କରିବାକୁ ବାମ କିମ୍ବା ଡାହାଣକୁ ସ୍ୱାଇପ୍ କରନ୍ତୁ",
"allowTextSelection": "Allow text selection",
"useChapterDrawerSwipeNavigation": "ଡ୍ରର୍ ଖୋଲିବାକୁ ଡାହାଣକୁ ସ୍ୱାଇପ୍ କରନ୍ତୁ",
"removeExtraSpacing": "Remove extra paragraph spacing",
"volumeButtonsScroll": "Volume buttons scroll",
"scrollAmount": "Scroll amount (screen height by default)",
"showSwipeMargins": "Show swipe margins"
},
"drawer": {
"scrollToCurrentChapter": "Scroll to current chapter",
"scrollToTop": "Scroll to top",
"scrollToBottom": "Scroll to bottom"
}
},
"novelScreen": {
"addToLibaray": "ଲାଇବ୍ରେରୀରେ ଯୋଡ଼ନ୍ତୁ",
"inLibaray": "In library",
"continueReading": "Continue reading",
"chapters": "chapters",
"migrate": "Migrate",
"noSummary": "No summary",
"bottomSheet": {
"filter": "Filter",
"sort": "Sort",
"display": "Display"
},
"jumpToChapterModal": {
"jumpToChapter": "Jump to Chapter",
"openChapter": "Open Chapter",
"chapterName": "Chapter Name",
"chapterNumber": "Chapter Number",
"error": {
"validChapterName": "Enter a valid chapter name",
"validChapterNumber": "Enter a valid chapter number"
}
},
"convertToEpubModal": {
"chooseLocation": "Select EPUB storage directory",
"pathToFolder": "Path to EPUB save folder",
"useReaderTheme": "Use reader theme",
"useCustomCSS": "Use custom CSS",
"useCustomJS": "Use custom JS",
"useCustomJSWarning": "May not be supported by all EPUB readers",
"chaptersWarning": "Only downloaded chapters are included in the EPUB"
}
},
"more": "More",
"moreScreen": {
"settings": "Settings",
"settingsScreen": {
"generalSettings": "General",
"generalSettingsScreen": {
"display": "Display",
"displayMode": "Display Mode",
"itemsPerRow": "Items per row",
"itemsPerRowLibrary": "Items per row in library",
"novelBadges": "Novel Badges",
"novelSort": "Novel Sort",
"updateLibrary": "Update library on launch",
"useFAB": "Use FAB in Library",
"novel": "Novel",
"chapterSort": "Default chapter sort",
"bySource": "By source",
"asc": "(Ascending)",
"desc": "(Descending)",
"globalUpdate": "Global update",
"updateOngoing": "Only update ongoing novels",
"refreshMetadata": "Automatically refresh metadata",
"refreshMetadataDescription": "Check for new cover and details when updating library",
"updateTime": "Show last update time",
"autoDownload": "Auto-download",
"epub": "EPUB",
"epubLocation": "EPUB Location",
"epubLocationDescription": "The place where you open and export your EPUB files.",
"downloadNewChapters": "Download new chapters",
"disableHapticFeedback": "Disable haptic feedback"
},
"readerSettings": {
"title": "Reader",
"readerTheme": "Reader theme",
"preset": "Preset",
"backgroundColor": "Background color",
"textColor": "Text color",
"backgroundColorModal": "Reader background color",
"textColorModal": "Reader text color",
"verticalSeekbarDesc": "Switch between vertical and horizontal seekbar",
"autoScrollInterval": "Auto scroll interval (in seconds)",
"autoScrollOffset": "Auto scroll offset (screen height by default)",
"saveCustomTheme": "Save custom theme",
"deleteCustomTheme": "Delete custom theme",
"customCSS": "Custom CSS",
"customJS": "Custom JS",
"openCSSFile": "Open CSS file",
"openJSFile": "Open JS file",
"notSaved": "Not saved",
"cssHint": "Hint: You can create source specific CSS configuration with the source ID. (#sourceId-[SOURCEID])",
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId",
"clearCustomCSS": "Are you sure? Your custom CSS would be reset.",
"clearCustomJS": "Are you sure? Your custom JS would be reset."
},
"browseSettings": "Browse Settings",
"browseSettingsScreen": {
"onlyShowPinnedSources": "Only show pinned sources",
"languages": "Languages",
"searchAllSources": "Search all sources",
"searchAllWarning": "Searching a large number of sources may freeze the app till searching is finished."
}
}
},
"sourceScreen": {
"noResultsFound": "No results found"
},
"statsScreen": {
"title": "Statistics",
"titlesInLibrary": "Titles in library",
"totalChapters": "Total chapters",
"readChapters": "Read chapters",
"unreadChapters": "Unread chapters",
"downloadedChapters": "Downloaded chapters",
"genreDistribution": "Genre distribution",
"statusDistribution": "Status distribution"
},
"globalSearch": {
"searchIn": "Search a novel in",
"allSources": "all sources",
"pinnedSources": "pinned sources"
},
"advancedSettings": {
"deleteReadChapters": "Delete read chapters",
"deleteReadChaptersDialogTitle": "Are you sure? All chapters marked as read will be deleted."
},
"date": {
"calendar": {
"sameDay": "[Today]",
"nextDay": "[Tomorrow]",
"lastDay": "[Yesterday]",
"lastWeek": "[Last] dddd"
}
},
"categories": {
"addCategories": "Add category",
"editCategories": "Rename category",
"setCategories": "Set categories",
"header": "Edit categories",
"emptyMsg": "You have no categories. Tap the plus button to create one for organizing your library",
"setModalEmptyMsg": "You have no categories. Tap the Edit button to create one for organizing your library",
"deleteModal": {
"header": "Delete category",
"desc": "Do you wish to delete category"
},
"duplicateError": "A category with this name already exists!",
"defaultCategory": "Default category"
},
"settings": {
"icognitoMode": "Incognito mode",
"downloadedOnly": "Downloaded only"
},
"downloadScreen": {
"dbInfo": "Downloads are saved in a SQLite Database.",
"downloadChapters": "downloaded Chapters",
"noDownloads": "No downloads",
"downloadsLower": "downloads"
}
}
+7 -4
View File
@@ -134,9 +134,10 @@
"chooseLocation": "Select EPUB storage directory",
"pathToFolder": "Path to EPUB save folder",
"useReaderTheme": "Use reader theme",
"useCustomCSS": "Use customCSS",
"useCustomJS": "Use customJS",
"useCustomJSWarning": "May not be supported by all EPUB readers"
"useCustomCSS": "Use custom CSS",
"useCustomJS": "Use custom JS",
"useCustomJSWarning": "May not be supported by all EPUB readers",
"chaptersWarning": "Only downloaded chapters are included in the EPUB"
}
},
"more": "More",
@@ -189,7 +190,9 @@
"openJSFile": "Open JS file",
"notSaved": "Not saved",
"cssHint": "Hint: You can create source specific CSS configuration with the source ID. (#sourceId-[SOURCEID])",
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId"
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId",
"clearCustomCSS": "Are you sure? Your custom CSS would be reset.",
"clearCustomJS": "Are you sure? Your custom JS would be reset."
},
"browseSettings": "Browse Settings",
"browseSettingsScreen": {
+35 -32
View File
@@ -37,13 +37,13 @@
"download": "Baixado",
"unread": "Não lido",
"lastRead": "Última leitura",
"lastUpdated": "Últimas atualizações"
"lastUpdated": "Última atualização"
},
"display": {
"displayMode": "Modo de exibição",
"compact": "Grade compacta",
"comfortable": "Grade confortável",
"noTitle": "Grade somente da capa",
"noTitle": "Grade de capas apenas",
"list": "Lista",
"badges": "Emblemas",
"downloadBadges": "Baixar emblemas",
@@ -56,7 +56,7 @@
"updatesScreen": {
"searchbar": "Procurar por atualizações",
"lastUpdatedAt": "Biblioteca atualizada pela última vez:",
"newChapters": "novo Capítulos",
"newChapters": "novos Capítulos",
"emptyView": "Sem atualizações recentes",
"updatesLower": "atualizações"
},
@@ -88,7 +88,7 @@
"lineHeight": "Altura da linha",
"fontStyle": "Estilo da fonte",
"fullscreen": "Tela cheia",
"bionicReading": "Bionic Reading",
"bionicReading": "Leitura Biônica",
"renderHml": "Renderizar HTML",
"autoscroll": "Rolagem automática",
"verticalSeekbar": "Barra de Busca Vertical",
@@ -99,13 +99,13 @@
"useChapterDrawerSwipeNavigation": "Deslize para a direita para abrir a gaveta",
"removeExtraSpacing": "Remover espaçamento extra dos parágrafos",
"volumeButtonsScroll": "Deslizar com os botões de volume",
"scrollAmount": "Scroll amount (screen height by default)",
"scrollAmount": "Valor da rolagem (Padrão: altura da tela)",
"showSwipeMargins": "Mostrar margens de deslize"
},
"drawer": {
"scrollToCurrentChapter": "Rolar para o capítulo atual",
"scrollToTop": "Voltar ao topo",
"scrollToBottom": "Rolar para baixo"
"scrollToTop": "Rolar para o topo",
"scrollToBottom": "Rolar para o final"
}
},
"novelScreen": {
@@ -124,19 +124,20 @@
"jumpToChapter": "Pular para o Capítulo",
"openChapter": "Abrir Capítulo",
"chapterName": "Nome do capítulo",
"chapterNumber": "Número do capítulo",
"chapterNumber": "Número do Capítulo",
"error": {
"validChapterName": "Insira um nome de capítulo válido",
"validChapterNumber": "Insira um número de capítulo válido"
}
},
"convertToEpubModal": {
"chooseLocation": "Select EPUB storage directory",
"pathToFolder": "Path to EPUB save folder",
"useReaderTheme": "Use reader theme",
"useCustomCSS": "Use customCSS",
"useCustomJS": "Use customJS",
"useCustomJSWarning": "May not be supported by all EPUB readers"
"chooseLocation": "Selecionar pasta de armazenamento EPUB",
"pathToFolder": "Caminho para pasta de salvamento de EPUB",
"useReaderTheme": "Usar tema de leitor",
"useCustomCSS": "Usar CSS personalizado",
"useCustomJS": "Usar JS personalizado",
"useCustomJSWarning": "Pode não ser suportado por todos os leitores de EPUB",
"chaptersWarning": "Apenas capítulos baixados são incluídos no EPUB"
}
},
"more": "Mais",
@@ -145,17 +146,17 @@
"settingsScreen": {
"generalSettings": "Geral",
"generalSettingsScreen": {
"display": "Exibição",
"displayMode": "Modo de visualização",
"display": "Exibir",
"displayMode": "Modo de exibição",
"itemsPerRow": "Itens por linha",
"itemsPerRowLibrary": "Itens por linha na biblioteca",
"novelBadges": "Novel Badges",
"novelSort": "Ordem da novel",
"novelBadges": "Insígnias da Novel",
"novelSort": "Ordenar Novel",
"updateLibrary": "Atualizar biblioteca ao iniciar",
"useFAB": "Usar FAB na Biblioteca",
"useFAB": "Usar Botões flutuantes na Biblioteca",
"novel": "Novel",
"chapterSort": "Ordenação padrão de capítulos",
"bySource": "Pela fonte",
"chapterSort": "Ordem padrão dos capítulos",
"bySource": "Por fonte",
"asc": "(Crescente)",
"desc": "(Decrescente)",
"globalUpdate": "Atualização global",
@@ -165,8 +166,8 @@
"updateTime": "Mostrar horário da última atualização",
"autoDownload": "Download automático",
"epub": "EPUB",
"epubLocation": "EPUB Location",
"epubLocationDescription": "The place where you open and export your EPUB files.",
"epubLocation": "Localização do EPUB",
"epubLocationDescription": "O local onde você abre e exporta seus arquivos EPUB.",
"downloadNewChapters": "Baixar novos capítulos",
"disableHapticFeedback": "Desativar o feedback tátil"
},
@@ -184,12 +185,14 @@
"saveCustomTheme": "Salvar tema personalizado",
"deleteCustomTheme": "Excluir tema personalizado",
"customCSS": "CSS Personalizado",
"customJS": "Custom JS",
"openCSSFile": "Open CSS file",
"openJSFile": "Open JS file",
"notSaved": "Not saved",
"cssHint": "Hint: You can create source specific CSS configuration with the source ID. (#sourceId-[SOURCEID])",
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId"
"customJS": "CSS Personalizado",
"openCSSFile": "Abrir arquivo CSS",
"openJSFile": "Abrir arquivo JS",
"notSaved": "Não foi salvo",
"cssHint": "Dica: Você pode criar uma configuração CSS específica para cada origem com a ID da origem. (#sourceId-[SOURCEID])",
"jsHint": "Dica: Você tem acesso às seguintes variáveis: html, novelName, chapterName, sourceId, chapterId, novelId",
"clearCustomCSS": "Você tem certeza? Seu CSS personalizado será resetado.",
"clearCustomJS": "Você tem certeza? Seu JS personalizado será resetado."
},
"browseSettings": "Configurações de navegação",
"browseSettingsScreen": {
@@ -227,7 +230,7 @@
"sameDay": "[Hoje]",
"nextDay": "[Amanhã]",
"lastDay": "[Ontem]",
"lastWeek": "[Last] dddd"
"lastWeek": "[Último] dddd"
}
},
"categories": {
@@ -236,7 +239,7 @@
"setCategories": "Definir categorias",
"header": "Editar categorias",
"emptyMsg": "Você não possui categorias. Toque no botão de mais para criar uma para organizar sua biblioteca",
"setModalEmptyMsg": "Você não possui categorias. Toque no botão de editar para criar uma para organizar sua biblioteca",
"setModalEmptyMsg": "Você não possui categorias. Clique no botão de editar para criar uma para organizar sua biblioteca",
"deleteModal": {
"header": "Deletar categoria",
"desc": "Você quer deletar esta categoria"
@@ -249,7 +252,7 @@
"downloadedOnly": "Apenas baixados"
},
"downloadScreen": {
"dbInfo": "Transferências são salvos em banco de dados SQLite.",
"dbInfo": "Downloads são salvos em banco de dados SQLite.",
"downloadChapters": "Capítulos baixados",
"noDownloads": "Sem transferências",
"downloadsLower": "baixados"
+7 -4
View File
@@ -134,9 +134,10 @@
"chooseLocation": "Select EPUB storage directory",
"pathToFolder": "Path to EPUB save folder",
"useReaderTheme": "Use reader theme",
"useCustomCSS": "Use customCSS",
"useCustomJS": "Use customJS",
"useCustomJSWarning": "May not be supported by all EPUB readers"
"useCustomCSS": "Use custom CSS",
"useCustomJS": "Use custom JS",
"useCustomJSWarning": "May not be supported by all EPUB readers",
"chaptersWarning": "Only downloaded chapters are included in the EPUB"
}
},
"more": "More",
@@ -189,7 +190,9 @@
"openJSFile": "Open JS file",
"notSaved": "Not saved",
"cssHint": "Hint: You can create source specific CSS configuration with the source ID. (#sourceId-[SOURCEID])",
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId"
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId",
"clearCustomCSS": "Are you sure? Your custom CSS would be reset.",
"clearCustomJS": "Are you sure? Your custom JS would be reset."
},
"browseSettings": "Browse Settings",
"browseSettingsScreen": {
+7 -4
View File
@@ -134,9 +134,10 @@
"chooseLocation": "Select EPUB storage directory",
"pathToFolder": "Path to EPUB save folder",
"useReaderTheme": "Use reader theme",
"useCustomCSS": "Use customCSS",
"useCustomJS": "Use customJS",
"useCustomJSWarning": "May not be supported by all EPUB readers"
"useCustomCSS": "Use custom CSS",
"useCustomJS": "Use custom JS",
"useCustomJSWarning": "May not be supported by all EPUB readers",
"chaptersWarning": "Only downloaded chapters are included in the EPUB"
}
},
"more": "More",
@@ -189,7 +190,9 @@
"openJSFile": "Open JS file",
"notSaved": "Not saved",
"cssHint": "Hint: You can create source specific CSS configuration with the source ID. (#sourceId-[SOURCEID])",
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId"
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId",
"clearCustomCSS": "Are you sure? Your custom CSS would be reset.",
"clearCustomJS": "Are you sure? Your custom JS would be reset."
},
"browseSettings": "Browse Settings",
"browseSettingsScreen": {
+14 -11
View File
@@ -46,7 +46,7 @@
"noTitle": "Только сетка обложки",
"list": "Список",
"badges": "Значки",
"downloadBadges": "Загружено Значки",
"downloadBadges": "Значок загруженных глав",
"unreadBadges": "Не прочитано Значки",
"showNoOfItems": "Показывать количество элементов"
}
@@ -88,7 +88,7 @@
"lineHeight": "Высота строки",
"fontStyle": "Шрифт",
"fullscreen": "Полноэкранный",
"bionicReading": "Bionic Reading",
"bionicReading": "Бионическое чтение",
"renderHml": "Рендер HTML",
"autoscroll": "Автопрокрутка",
"verticalSeekbar": "Вертикальная панель навигации",
@@ -131,12 +131,13 @@
}
},
"convertToEpubModal": {
"chooseLocation": "Select EPUB storage directory",
"pathToFolder": "Path to EPUB save folder",
"useReaderTheme": "Use reader theme",
"useCustomCSS": "Use customCSS",
"useCustomJS": "Use customJS",
"useCustomJSWarning": "May not be supported by all EPUB readers"
"chooseLocation": "Выберите каталог хранения EPUB",
"pathToFolder": "Путь к папке сохранения EPUB",
"useReaderTheme": "Использовать тему для чтения",
"useCustomCSS": "Использовать пользовательский CSS",
"useCustomJS": "Использовать пользовательский JS",
"useCustomJSWarning": "Может поддерживаться не всеми читателями EPUB",
"chaptersWarning": "Включать в EPUB только загруженные главы"
}
},
"more": "Ещё",
@@ -165,8 +166,8 @@
"updateTime": "Показать время последнего обновления",
"autoDownload": "Автозагрузка",
"epub": "EPUB",
"epubLocation": "EPUB Location",
"epubLocationDescription": "The place where you open and export your EPUB files.",
"epubLocation": "Расположение EPUB",
"epubLocationDescription": "Место открытия и экспорта файлов EPUB.",
"downloadNewChapters": "Скачивать новые главы",
"disableHapticFeedback": "Отключить виброотклик"
},
@@ -189,7 +190,9 @@
"openJSFile": "Открыть JS-файл",
"notSaved": "Не сохранено",
"cssHint": "Подсказка: Вы можете создать исходный код конфигурации с исходным ID. (#sourceId-[SOURCEID])",
"jsHint": "Подсказка: У вас есть доступ к следующим переменным: html, novelName, chapterName, sourceId, chapterId, novelId"
"jsHint": "Подсказка: У вас есть доступ к следующим переменным: html, novelName, chapterName, sourceId, chapterId, novelId",
"clearCustomCSS": "Вы уверены? Ваш пользовательский CSS будет сброшен.",
"clearCustomJS": "Вы уверены? Ваш пользовательский JS будет сброшен."
},
"browseSettings": "Настройки обзора",
"browseSettingsScreen": {
+7 -4
View File
@@ -134,9 +134,10 @@
"chooseLocation": "Select EPUB storage directory",
"pathToFolder": "Path to EPUB save folder",
"useReaderTheme": "Use reader theme",
"useCustomCSS": "Use customCSS",
"useCustomJS": "Use customJS",
"useCustomJSWarning": "May not be supported by all EPUB readers"
"useCustomCSS": "Use custom CSS",
"useCustomJS": "Use custom JS",
"useCustomJSWarning": "May not be supported by all EPUB readers",
"chaptersWarning": "Only downloaded chapters are included in the EPUB"
}
},
"more": "More",
@@ -189,7 +190,9 @@
"openJSFile": "Open JS file",
"notSaved": "Not saved",
"cssHint": "Hint: You can create source specific CSS configuration with the source ID. (#sourceId-[SOURCEID])",
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId"
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId",
"clearCustomCSS": "Are you sure? Your custom CSS would be reset.",
"clearCustomJS": "Are you sure? Your custom JS would be reset."
},
"browseSettings": "Browse Settings",
"browseSettingsScreen": {
+7 -4
View File
@@ -134,9 +134,10 @@
"chooseLocation": "Select EPUB storage directory",
"pathToFolder": "Path to EPUB save folder",
"useReaderTheme": "Use reader theme",
"useCustomCSS": "Use customCSS",
"useCustomJS": "Use customJS",
"useCustomJSWarning": "May not be supported by all EPUB readers"
"useCustomCSS": "Use custom CSS",
"useCustomJS": "Use custom JS",
"useCustomJSWarning": "May not be supported by all EPUB readers",
"chaptersWarning": "Only downloaded chapters are included in the EPUB"
}
},
"more": "More",
@@ -189,7 +190,9 @@
"openJSFile": "Open JS file",
"notSaved": "Not saved",
"cssHint": "Hint: You can create source specific CSS configuration with the source ID. (#sourceId-[SOURCEID])",
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId"
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId",
"clearCustomCSS": "Are you sure? Your custom CSS would be reset.",
"clearCustomJS": "Are you sure? Your custom JS would be reset."
},
"browseSettings": "Browse Settings",
"browseSettingsScreen": {
+7 -4
View File
@@ -134,9 +134,10 @@
"chooseLocation": "Select EPUB storage directory",
"pathToFolder": "Path to EPUB save folder",
"useReaderTheme": "Use reader theme",
"useCustomCSS": "Use customCSS",
"useCustomJS": "Use customJS",
"useCustomJSWarning": "May not be supported by all EPUB readers"
"useCustomCSS": "Use custom CSS",
"useCustomJS": "Use custom JS",
"useCustomJSWarning": "May not be supported by all EPUB readers",
"chaptersWarning": "Only downloaded chapters are included in the EPUB"
}
},
"more": "More",
@@ -189,7 +190,9 @@
"openJSFile": "Open JS file",
"notSaved": "Not saved",
"cssHint": "Hint: You can create source specific CSS configuration with the source ID. (#sourceId-[SOURCEID])",
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId"
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId",
"clearCustomCSS": "Are you sure? Your custom CSS would be reset.",
"clearCustomJS": "Are you sure? Your custom JS would be reset."
},
"browseSettings": "Browse Settings",
"browseSettingsScreen": {
+9 -6
View File
@@ -131,12 +131,13 @@
}
},
"convertToEpubModal": {
"chooseLocation": "Select EPUB storage directory",
"chooseLocation": "EPUB depolama dizinini seç",
"pathToFolder": "EPUB dosyalarını kaydetme klasörünün yolu",
"useReaderTheme": "Use reader theme",
"useCustomCSS": "Use customCSS",
"useCustomJS": "Use customJS",
"useCustomJSWarning": "May not be supported by all EPUB readers"
"useReaderTheme": "Okuyucu temasını kullan",
"useCustomCSS": "Özel CSS kullan",
"useCustomJS": "Özel JS kullan",
"useCustomJSWarning": "Tüm EPUB okuyucuları tarafından desteklenmeyebilir",
"chaptersWarning": "EPUB'da yalnızca, indirilen bölümler dahil edilir"
}
},
"more": "Daha fazla",
@@ -189,7 +190,9 @@
"openJSFile": "JS dosyası aç",
"notSaved": "Kaydedilmedi",
"cssHint": "İpucu: Kaynak ID'si ile kaynağa özel CSS yapılandırması oluşturabilirsiniz.\n(#kaynakId-[KAYNAKID])",
"jsHint": "İpucu: Aşağıdaki değişkenlere erişiminiz bulunmakta:\nhtml, novelName, chapterName, sourceId, chapterId, novelId"
"jsHint": "İpucu: Aşağıdaki değişkenlere erişiminiz bulunmakta:\nhtml, novelName, chapterName, sourceId, chapterId, novelId",
"clearCustomCSS": "Emin misiniz? Özel CSS'niz sıfırlanır.",
"clearCustomJS": "Emin misiniz? Özel JS kodlarınız sıfırlanır."
},
"browseSettings": "Göz Atma Seçenekleri",
"browseSettingsScreen": {
+8 -5
View File
@@ -15,7 +15,7 @@
"searchFor": "Шукати",
"globally": "глобально",
"searchResults": "Результати пошуку",
"chapters": "Chapters",
"chapters": "Глави",
"submit": "Submit",
"retry": "Retry"
},
@@ -134,9 +134,10 @@
"chooseLocation": "Select EPUB storage directory",
"pathToFolder": "Path to EPUB save folder",
"useReaderTheme": "Use reader theme",
"useCustomCSS": "Use customCSS",
"useCustomJS": "Use customJS",
"useCustomJSWarning": "May not be supported by all EPUB readers"
"useCustomCSS": "Use custom CSS",
"useCustomJS": "Use custom JS",
"useCustomJSWarning": "May not be supported by all EPUB readers",
"chaptersWarning": "Only downloaded chapters are included in the EPUB"
}
},
"more": "Більше",
@@ -189,7 +190,9 @@
"openJSFile": "Open JS file",
"notSaved": "Not saved",
"cssHint": "Hint: You can create source specific CSS configuration with the source ID. (#sourceId-[SOURCEID])",
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId"
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId",
"clearCustomCSS": "Are you sure? Your custom CSS would be reset.",
"clearCustomJS": "Are you sure? Your custom JS would be reset."
},
"browseSettings": "Налаштування джерел",
"browseSettingsScreen": {
+8 -5
View File
@@ -4,7 +4,7 @@
"ok": "Ok",
"save": "Lưu",
"clear": "Xóa",
"reset": "Thiết lập lại",
"reset": "Đặt lại",
"search": "Tìm kiếm",
"install": "Tải về",
"newUpdateAvailable": "Đã có bản cập nhật mới",
@@ -134,9 +134,10 @@
"chooseLocation": "Select EPUB storage directory",
"pathToFolder": "Path to EPUB save folder",
"useReaderTheme": "Use reader theme",
"useCustomCSS": "Use customCSS",
"useCustomJS": "Use customJS",
"useCustomJSWarning": "May not be supported by all EPUB readers"
"useCustomCSS": "Use custom CSS",
"useCustomJS": "Use custom JS",
"useCustomJSWarning": "May not be supported by all EPUB readers",
"chaptersWarning": "Only downloaded chapters are included in the EPUB"
}
},
"more": "Khác",
@@ -189,7 +190,9 @@
"openJSFile": "Open JS file",
"notSaved": "Not saved",
"cssHint": "Hint: You can create source specific CSS configuration with the source ID. (#sourceId-[SOURCEID])",
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId"
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId",
"clearCustomCSS": "Are you sure? Your custom CSS would be reset.",
"clearCustomJS": "Are you sure? Your custom JS would be reset."
},
"browseSettings": "Cài đặt trình tìm kiếm",
"browseSettingsScreen": {
+9 -6
View File
@@ -36,8 +36,8 @@
"totalChapters": "章节总数",
"download": "Downloaded",
"unread": "Unread",
"lastRead": "Last read",
"lastUpdated": "Last updated"
"lastRead": "最后读",
"lastUpdated": "最后更新"
},
"display": {
"displayMode": "显示模式",
@@ -134,9 +134,10 @@
"chooseLocation": "Select EPUB storage directory",
"pathToFolder": "Path to EPUB save folder",
"useReaderTheme": "Use reader theme",
"useCustomCSS": "Use customCSS",
"useCustomJS": "Use customJS",
"useCustomJSWarning": "May not be supported by all EPUB readers"
"useCustomCSS": "Use custom CSS",
"useCustomJS": "Use custom JS",
"useCustomJSWarning": "May not be supported by all EPUB readers",
"chaptersWarning": "Only downloaded chapters are included in the EPUB"
}
},
"more": "更多",
@@ -189,7 +190,9 @@
"openJSFile": "Open JS file",
"notSaved": "Not saved",
"cssHint": "Hint: You can create source specific CSS configuration with the source ID. (#sourceId-[SOURCEID])",
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId"
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId",
"clearCustomCSS": "Are you sure? Your custom CSS would be reset.",
"clearCustomJS": "Are you sure? Your custom JS would be reset."
},
"browseSettings": "浏览设置",
"browseSettingsScreen": {
+7 -4
View File
@@ -134,9 +134,10 @@
"chooseLocation": "Select EPUB storage directory",
"pathToFolder": "Path to EPUB save folder",
"useReaderTheme": "Use reader theme",
"useCustomCSS": "Use customCSS",
"useCustomJS": "Use customJS",
"useCustomJSWarning": "May not be supported by all EPUB readers"
"useCustomCSS": "Use custom CSS",
"useCustomJS": "Use custom JS",
"useCustomJSWarning": "May not be supported by all EPUB readers",
"chaptersWarning": "Only downloaded chapters are included in the EPUB"
}
},
"more": "更多",
@@ -189,7 +190,9 @@
"openJSFile": "Open JS file",
"notSaved": "Not saved",
"cssHint": "Hint: You can create source specific CSS configuration with the source ID. (#sourceId-[SOURCEID])",
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId"
"jsHint": "Hint: You have access to following variables: html, novelName, chapterName, sourceId, chapterId, novelId",
"clearCustomCSS": "Are you sure? Your custom CSS would be reset.",
"clearCustomJS": "Are you sure? Your custom JS would be reset."
},
"browseSettings": "浏览设置",
"browseSettingsScreen": {
+2
View File
@@ -79,6 +79,7 @@ export interface StringMap {
'readerScreen.bottomSheet.showBatteryAndTime': 'string';
'readerScreen.bottomSheet.showProgressPercentage': 'string';
'readerScreen.bottomSheet.swipeGestures': 'string';
'readerScreen.bottomSheet.readerPages': 'string';
'readerScreen.bottomSheet.allowTextSelection': 'string';
'readerScreen.bottomSheet.useChapterDrawerSwipeNavigation': 'string';
'readerScreen.bottomSheet.removeExtraSpacing': 'string';
@@ -137,6 +138,7 @@ export interface StringMap {
'moreScreen.settingsScreen.generalSettingsScreen.epubLocationDescription': 'string';
'moreScreen.settingsScreen.generalSettingsScreen.downloadNewChapters': 'string';
'moreScreen.settingsScreen.generalSettingsScreen.disableHapticFeedback': 'string';
'moreScreen.settingsScreen.generalSettingsScreen.jumpToLastReadChapter': 'string';
'moreScreen.settingsScreen.readerSettings.title': 'string';
'moreScreen.settingsScreen.readerSettings.readerTheme': 'string';
'moreScreen.settingsScreen.readerSettings.preset': 'string';