From fffe52e5efbc4e0e05847a92c062b58297ffb9bf Mon Sep 17 00:00:00 2001 From: Soopyboo32 <49228220+Soopyboo32@users.noreply.github.com> Date: Sat, 4 Jan 2025 20:04:48 +0800 Subject: [PATCH] Improve plugin settings, standardized captcha errors, hide chapters that no longer exist --- src/database/db.ts | 2 + src/database/queries/ChapterQueries.ts | 20 ++++++++- src/database/queries/LibraryQueries.ts | 1 + src/database/queries/StatsQueries.ts | 6 +-- src/database/tables/ChapterTable.ts | 5 +++ src/hooks/persisted/usePlugins.ts | 1 + src/plugins/helpers/fetch.ts | 21 +++++++++- src/plugins/pluginManager.ts | 5 +++ .../components/Modals/SourceSettings.tsx | 42 ++++++++++++------- src/screens/novel/NovelScreen.tsx | 3 +- .../novel/components/Info/NovelInfoHeader.tsx | 6 ++- .../novel/components/NovelBottomSheet.tsx | 12 +++++- src/services/updates/LibraryUpdateQueries.ts | 23 ++++++++-- strings/languages/en/strings.json | 5 ++- strings/types/index.ts | 1 + 15 files changed, 124 insertions(+), 29 deletions(-) diff --git a/src/database/db.ts b/src/database/db.ts index 6b52ca2ad..c30b09804 100644 --- a/src/database/db.ts +++ b/src/database/db.ts @@ -9,6 +9,7 @@ import { createNovelCategoryTableQuery } from './tables/NovelCategoryTable'; import { createChapterTableQuery, createChapterNovelIdIndexQuery, + addHiddenColumnQuery, } from './tables/ChapterTable'; import { dbTxnErrorCallback } from './utils/helpers'; import { noop } from 'lodash-es'; @@ -32,6 +33,7 @@ export const createTables = () => { db.transaction(tx => { tx.executeSql(createRepositoryTableQuery); + tx.executeSql(addHiddenColumnQuery); }); }; diff --git a/src/database/queries/ChapterQueries.ts b/src/database/queries/ChapterQueries.ts index a96a07f1a..082c90e26 100644 --- a/src/database/queries/ChapterQueries.ts +++ b/src/database/queries/ChapterQueries.ts @@ -22,6 +22,16 @@ export const insertChapters = async ( if (!chapters?.length) { return; } + const existingChapters = await getPageChapters( + novelId, + '', + '', + chapters[0].page || '1', + ); + const chaptersToHide = existingChapters.filter( + c => !chapters.some(ch => ch.path === c.path), + ); + db.transaction(tx => { chapters.forEach((chapter, index) => { tx.executeSql( @@ -40,8 +50,8 @@ export const insertChapters = async ( tx.executeSql( ` UPDATE Chapter SET - page = ?, position = ? - WHERE path = ? AND novelId = ? AND (page != ? OR position != ?) + page = ?, position = ?, hidden = 0 + WHERE path = ? AND novelId = ? AND (page != ? OR position != ? OR hidden != 0) `, [ chapter.page || '1', @@ -56,6 +66,12 @@ export const insertChapters = async ( }, ); }); + chaptersToHide.forEach(chapter => { + tx.executeSql( + 'UPDATE Chapter SET hidden = 1 WHERE path = ? AND novelId = ?', + [chapter.path, novelId], + ); + }); }); }; diff --git a/src/database/queries/LibraryQueries.ts b/src/database/queries/LibraryQueries.ts index 73783f5a1..2fc9ee513 100644 --- a/src/database/queries/LibraryQueries.ts +++ b/src/database/queries/LibraryQueries.ts @@ -77,6 +77,7 @@ const getLibraryWithCategoryQuery = ` SUM(unread) as chaptersUnread, SUM(isDownloaded) as chaptersDownloaded, novelId, MAX(readTime) as lastReadAt, MAX(updatedTime) as lastUpdatedAt FROM Chapter + WHERE hidden = 0 GROUP BY novelId ) as C ON NIL.id = C.novelId ) WHERE 1 = 1 diff --git a/src/database/queries/StatsQueries.ts b/src/database/queries/StatsQueries.ts index d8f4f4912..22bbdd7ce 100644 --- a/src/database/queries/StatsQueries.ts +++ b/src/database/queries/StatsQueries.ts @@ -14,7 +14,7 @@ const getChaptersReadCountQuery = ` FROM Chapter JOIN Novel ON Chapter.novelId = Novel.id - WHERE Chapter.unread = 0 AND Novel.inLibrary = 1 + WHERE Chapter.unread = 0 AND Novel.inLibrary = 1 AND Chapter.hidden = 0 `; const getChaptersTotalCountQuery = ` @@ -22,7 +22,7 @@ const getChaptersTotalCountQuery = ` FROM Chapter JOIN Novel ON Chapter.novelId = Novel.id - WHERE Novel.inLibrary = 1 + WHERE Novel.inLibrary = 1 AND Chapter.hidden = 0 `; const getChaptersUnreadCountQuery = ` @@ -30,7 +30,7 @@ const getChaptersUnreadCountQuery = ` FROM Chapter JOIN Novel ON Chapter.novelId = Novel.id - WHERE Chapter.unread = 1 AND Novel.inLibrary = 1 + WHERE Chapter.unread = 1 AND Novel.inLibrary = 1 AND Chapter.hidden = 0 `; const getChaptersDownloadedCountQuery = ` diff --git a/src/database/tables/ChapterTable.ts b/src/database/tables/ChapterTable.ts index a3d555bca..679c78b62 100644 --- a/src/database/tables/ChapterTable.ts +++ b/src/database/tables/ChapterTable.ts @@ -14,11 +14,16 @@ export const createChapterTableQuery = ` page TEXT DEFAULT "1", position INTEGER DEFAULT 0, progress INTEGER, + hidden INTEGER DEFAULT 0, UNIQUE(path, novelId), FOREIGN KEY (novelId) REFERENCES Novel(id) ON DELETE CASCADE ) `; +export const addHiddenColumnQuery = ` + ALTER TABLE Chapter ADD COLUMN hidden INTEGER DEFAULT 0 +`; + export const createChapterNovelIdIndexQuery = ` CREATE INDEX IF NOT EXISTS diff --git a/src/hooks/persisted/usePlugins.ts b/src/hooks/persisted/usePlugins.ts index c135ae044..3f4ad5516 100644 --- a/src/hooks/persisted/usePlugins.ts +++ b/src/hooks/persisted/usePlugins.ts @@ -157,6 +157,7 @@ export default function usePlugins() { name: _plg.name, version: _plg.version, hasUpdate: false, + hasSettings: !!_plg.pluginSettings, }; if (newPlugin.id === lastUsedPlugin?.id) { setLastUsedPlugin(newPlugin); diff --git a/src/plugins/helpers/fetch.ts b/src/plugins/helpers/fetch.ts index 8065bc264..adb9a8390 100644 --- a/src/plugins/helpers/fetch.ts +++ b/src/plugins/helpers/fetch.ts @@ -44,9 +44,25 @@ export const fetchApi = async ( init?: FetchInit, ): Promise => { init = makeInit(init); - return await fetch(url, init); + return await fetch(url, init).then(checkCloudflareError); }; +async function checkCloudflareError(res: Response) { + const text = await res.clone().text(); + const title = text.match(/(.*?)<\/title>/)?.[1]; + if ( + title == 'Bot Verification' || + title == 'You are being redirected...' || + title == 'Un instant...' || + title == 'Just a moment...' || + title == 'Redirecting...' + ) { + throw new Error('Captcha error, please open in webview'); + } + + return res; +} + const FILE_READER_PREFIX_LENGTH = 'data:application/octet-stream;base64,' .length; @@ -79,7 +95,7 @@ export const fetchText = async ( ): Promise<string> => { init = makeInit(init); try { - const res = await fetch(url, init); + const res = await fetch(url, init).then(checkCloudflareError); if (!res.ok) { throw new Error(); } @@ -163,6 +179,7 @@ export const fetchProto = async function ( ...init, body: bodyArray, } as RequestInit) + .then(checkCloudflareError) .then(r => r.blob()) .then(blob => { // decode response data diff --git a/src/plugins/pluginManager.ts b/src/plugins/pluginManager.ts index 5a73f6df4..a79c13cf4 100644 --- a/src/plugins/pluginManager.ts +++ b/src/plugins/pluginManager.ts @@ -148,12 +148,17 @@ const getPlugin = (pluginId: string) => { return plugins[pluginId]; }; +const unloadPlugin = (pluginId: string) => { + plugins[pluginId] = undefined; +}; + const LOCAL_PLUGIN_ID = 'local'; export { getPlugin, installPlugin, uninstallPlugin, + unloadPlugin, updatePlugin, fetchPlugins, LOCAL_PLUGIN_ID, diff --git a/src/screens/browse/components/Modals/SourceSettings.tsx b/src/screens/browse/components/Modals/SourceSettings.tsx index 45efcbfc2..c4014078f 100644 --- a/src/screens/browse/components/Modals/SourceSettings.tsx +++ b/src/screens/browse/components/Modals/SourceSettings.tsx @@ -5,10 +5,13 @@ import { Button } from '@components/index'; import { useTheme } from '@hooks/persisted'; import { getString } from '@strings/translations'; import { Storage } from '@plugins/helpers/storage'; +import { unloadPlugin } from '@plugins/pluginManager'; +import { SwitchItem } from '@components'; interface PluginSetting { value: string; label: string; + type?: 'Switch'; } interface PluginSettings { @@ -75,6 +78,7 @@ const SourceSettingsModal: React.FC<SourceSettingsModal> = ({ Object.entries(formValues).forEach(([key, value]) => { storage.set(key, value); }); + unloadPlugin(pluginId); onDismiss(); }; @@ -112,20 +116,30 @@ const SourceSettingsModal: React.FC<SourceSettingsModal> = ({ </Text> <Text style={[{ color: theme.onSurfaceVariant }]}>{description}</Text> - {Object.entries(pluginSettings).map(([key, setting]) => ( - <TextInput - key={key} - mode="outlined" - label={setting.label} - value={formValues[key] || ''} - onChangeText={value => handleChange(key, value)} - placeholder={`Enter ${setting.label}`} - placeholderTextColor={theme.onSurfaceDisabled} - underlineColor={theme.outline} - style={[{ color: theme.onSurface }, styles.textInput]} - theme={{ colors: { ...theme } }} - /> - ))} + {Object.entries(pluginSettings).map(([key, setting]) => + setting?.type === 'Switch' ? ( + <SwitchItem + key={key} + label={setting.label} + value={!!formValues[key]} + onPress={() => handleChange(key, formValues[key] ? '' : 'true')} + theme={theme} + /> + ) : ( + <TextInput + key={key} + mode="outlined" + label={setting.label} + value={formValues[key] || ''} + onChangeText={value => handleChange(key, value)} + placeholder={`Enter ${setting.label}`} + placeholderTextColor={theme.onSurfaceDisabled} + underlineColor={theme.outline} + style={[{ color: theme.onSurface }, styles.textInput]} + theme={{ colors: { ...theme } }} + /> + ), + )} <View style={styles.customCSSButtons}> <Button diff --git a/src/screens/novel/NovelScreen.tsx b/src/screens/novel/NovelScreen.tsx index 4937dc824..5aca75d18 100644 --- a/src/screens/novel/NovelScreen.tsx +++ b/src/screens/novel/NovelScreen.tsx @@ -71,7 +71,7 @@ const Novel = ({ route, navigation }: NovelScreenProps) => { lastRead, novelSettings: { sort = defaultChapterSort, - filter = '', + filter = 'AND hidden=0', showChapterTitles = false, }, openPage, @@ -89,6 +89,7 @@ const Novel = ({ route, navigation }: NovelScreenProps) => { refreshChapters, deleteChapters, } = useNovel(path, pluginId); + const theme = useTheme(); const { top: topInset, bottom: bottomInset } = useSafeAreaInsets(); diff --git a/src/screens/novel/components/Info/NovelInfoHeader.tsx b/src/screens/novel/components/Info/NovelInfoHeader.tsx index 9cfe35c92..097cd6eb5 100644 --- a/src/screens/novel/components/Info/NovelInfoHeader.tsx +++ b/src/screens/novel/components/Info/NovelInfoHeader.tsx @@ -223,7 +223,11 @@ const NovelInfoHeader = ({ ) : null} <IconButton icon="filter-variant" - iconColor={filter ? filterColor(theme.isDark) : theme.onSurface} + iconColor={ + filter.trim() !== 'AND hidden=0' + ? filterColor(theme.isDark) + : theme.onSurface + } size={24} onPress={() => novelBottomSheetRef.current?.present()} /> diff --git a/src/screens/novel/components/NovelBottomSheet.tsx b/src/screens/novel/components/NovelBottomSheet.tsx index 9a2357944..67842c3ca 100644 --- a/src/screens/novel/components/NovelBottomSheet.tsx +++ b/src/screens/novel/components/NovelBottomSheet.tsx @@ -92,6 +92,16 @@ const ChaptersSettingsSheet = ({ : filterChapters(filter + ' AND bookmark=1'); }} /> + <Checkbox + theme={theme} + label={getString('novelScreen.bottomSheet.filters.dontExist')} + status={!filter.match('AND hidden=0')} + onPress={() => { + filter.match('AND hidden=0') + ? filterChapters(filter.replace(/ ?AND hidden=0/, '')) + : filterChapters(filter + ' AND hidden=0'); + }} + /> </View> ); @@ -184,7 +194,7 @@ const ChaptersSettingsSheet = ({ ); return ( - <BottomSheet snapPoints={[240]} bottomSheetRef={bottomSheetRef}> + <BottomSheet snapPoints={[280]} bottomSheetRef={bottomSheetRef}> <BottomSheetView style={[ styles.contentContainer, diff --git a/src/services/updates/LibraryUpdateQueries.ts b/src/services/updates/LibraryUpdateQueries.ts index 6d1c2a835..429ab93a4 100644 --- a/src/services/updates/LibraryUpdateQueries.ts +++ b/src/services/updates/LibraryUpdateQueries.ts @@ -6,6 +6,7 @@ import { NOVEL_STORAGE } from '@utils/Storages'; import { downloadFile } from '@plugins/helpers/fetch'; import ServiceManager from '@services/ServiceManager'; import { db } from '@database/db'; +import { getPageChapters } from '@database/queries/ChapterQueries'; const updateNovelMetadata = ( pluginId: string, @@ -73,13 +74,23 @@ const updateNovelTotalPages = (novelId: number, totalPages: number) => { }); }; -const updateNovelChapters = ( +const updateNovelChapters = async ( novelName: string, novelId: number, chapters: ChapterItem[], downloadNewChapters?: boolean, page?: string, ) => { + const existingChapters = await getPageChapters( + novelId, + '', + '', + chapters[0].page || '1', + ); + const chaptersToHide = existingChapters.filter( + c => !chapters.some(ch => ch.path === c.path), + ); + return new Promise((resolve, reject) => { db.transaction(async tx => { for (let position = 0; position < chapters.length; position++) { @@ -124,8 +135,8 @@ const updateNovelChapters = ( tx.executeSql( ` UPDATE Chapter SET - name = ?, releaseTime = ?, updatedTime = datetime('now','localtime'), page = ?, position = ? - WHERE path = ? AND novelId = ? AND (name != ? OR releaseTime != ? OR page != ? OR position != ?); + name = ?, releaseTime = ?, updatedTime = datetime('now','localtime'), page = ?, position = ?, hidden = 0 + WHERE path = ? AND novelId = ? AND (name != ? OR releaseTime != ? OR page != ? OR position != ? OR hidden != 0); `, [ name, @@ -153,6 +164,12 @@ const updateNovelChapters = ( }, ); } + chaptersToHide.forEach(chapter => { + tx.executeSql( + 'UPDATE Chapter SET hidden = 1 WHERE path = ? AND novelId = ?', + [chapter.path, novelId], + ); + }); resolve(null); }); }); diff --git a/strings/languages/en/strings.json b/strings/languages/en/strings.json index eae20b9cc..c8d9f9f28 100644 --- a/strings/languages/en/strings.json +++ b/strings/languages/en/strings.json @@ -124,7 +124,7 @@ "updatedTo": "Updated to %{version}", "settings": { "title": "Plugin Settings", - "description": "Fill in the plugin settings. Restart app to apply the settings." + "description": "Fill in the plugin settings." } }, "browseSettings": "Browse Settings", @@ -333,7 +333,8 @@ "filters": { "bookmarked": "Bookmarked", "downloaded": "Downloaded", - "unread": "Unread" + "unread": "Unread", + "dontExist": "Show chapters that no longer exist" }, "order": { "byChapterName": "By chapter name", diff --git a/strings/types/index.ts b/strings/types/index.ts index 3ccc898bf..d631ce868 100644 --- a/strings/types/index.ts +++ b/strings/types/index.ts @@ -272,6 +272,7 @@ export interface StringMap { 'novelScreen.bottomSheet.filters.bookmarked': 'string'; 'novelScreen.bottomSheet.filters.downloaded': 'string'; 'novelScreen.bottomSheet.filters.unread': 'string'; + 'novelScreen.bottomSheet.filters.dontExist': 'string'; 'novelScreen.bottomSheet.order.byChapterName': 'string'; 'novelScreen.bottomSheet.order.bySource': 'string'; 'novelScreen.chapterChapnum': 'string';