Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 64ef4bae1f | |||
| 88a70cbcea | |||
| cfcc4bd875 | |||
| 02b058dac2 | |||
| 36cb7fc391 | |||
| 8613a088d2 | |||
| e600828bc2 | |||
| e4364aea7c | |||
| df8da48c32 | |||
| a83b4b1a32 | |||
| 8a85f4eaf1 | |||
| 2c1cce7fbe | |||
| e4ca751fb0 | |||
| c3543f8186 | |||
| 9253249ef6 | |||
| e0e593bf9f | |||
| a298ed81d7 | |||
| 60cf358e57 | |||
| 4548cdf8db | |||
| ee9391bd9f | |||
| f069a67660 | |||
| 7ee843f7b8 | |||
| 757c1eae5f |
@@ -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
@@ -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
|
||||
```
|
||||
```
|
||||
Generated
+721
-845
File diff suppressed because it is too large
Load Diff
@@ -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',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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 }]}
|
||||
/>
|
||||
|
||||
@@ -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={[
|
||||
{
|
||||
|
||||
@@ -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] || {};
|
||||
};
|
||||
@@ -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 }]}
|
||||
/>
|
||||
|
||||
@@ -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
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+27
-7
@@ -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)}
|
||||
@@ -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);
|
||||
;`;
|
||||
};
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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() || '';
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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 },
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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' },
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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
@@ -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',
|
||||
|
||||
@@ -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('');
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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 });
|
||||
};
|
||||
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": "المصادر المثبتة"
|
||||
},
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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": {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user