diff --git a/public/static/multisrc/madara/1stkissnovel/icon.png b/public/static/multisrc/madara/1stkissnovel/icon.png index 188d4c1..96706e8 100644 Binary files a/public/static/multisrc/madara/1stkissnovel/icon.png and b/public/static/multisrc/madara/1stkissnovel/icon.png differ diff --git a/public/static/src/en/vynovel/icon.png b/public/static/src/en/vynovel/icon.png new file mode 100644 index 0000000..3467d07 Binary files /dev/null and b/public/static/src/en/vynovel/icon.png differ diff --git a/scripts/icon_reloading.js b/scripts/icon_reloading.js index 62375dd..c677222 100644 --- a/scripts/icon_reloading.js +++ b/scripts/icon_reloading.js @@ -27,13 +27,15 @@ const skip = new Set([ 'wuxiaworld.site', ]); +const folder = path.join('public', 'static'); + const used = new Set([ - path.join('public', 'icons', 'coverNotAvailable.webp'), - path.join('public', 'icons', 'siteNotAvailable.png'), + path.join(folder, 'coverNotAvailable.webp'), + path.join(folder, 'siteNotAvailable.png'), ]); const notAvailableImage = fs.readFileSync( - path.join('public', 'icons', 'siteNotAvailable.png'), + path.join(folder, 'siteNotAvailable.png'), ); (async () => { @@ -48,8 +50,9 @@ const notAvailableImage = fs.readFileSync( console.log('\nDownloading icons ⌛'); let language; for (let plugin in plugins) { - const { id, name, site, iconUrl, lang } = plugins[plugin]; - const icon = iconUrl && 'public/icons/' + iconUrl.split('icons/')[1]; + const { id, name, site, iconUrl, lang, customJS, customCSS } = + plugins[plugin]; + const icon = iconUrl && path.join(folder, iconUrl.split('/static/')[1]); if (language !== lang) { language = lang; @@ -61,7 +64,13 @@ const notAvailableImage = fs.readFileSync( } try { - if (icon) used.add(path.join(icon)); + if (customJS) { + used.add(path.join(folder, customJS.split('/static/')[1])); + } + if (customCSS) { + used.add(path.join(folder, customCSS.split('/static/')[1])); + } + if (icon) used.add(icon); if (!skip.has(id) && icon && site) { const image = await fetch( `https://www.google.com/s2/favicons?domain=${site}&sz=${size}&type=png`, @@ -121,7 +130,7 @@ const notAvailableImage = fs.readFileSync( } console.log('\nDeleting unused icons ⌛'); - fileList(path.join('public', 'icons')).forEach(path => { + fileList(folder).forEach(path => { if (!used.has(path)) { console.log('🗑️', path); fs.rmSync(path, { force: true }); diff --git a/scripts/multisrc/hotnovelpub/template.ts b/scripts/multisrc/hotnovelpub/template.ts index d8f043f..a0ee06b 100644 --- a/scripts/multisrc/hotnovelpub/template.ts +++ b/scripts/multisrc/hotnovelpub/template.ts @@ -115,7 +115,7 @@ class HotNovelPubPlugin implements Plugin.PluginBase { ); let chapterText = - body.match(/
/g)?.[0] || ''; + body.match(/
/)?.[0] || ''; if (chapterText) { const result = await fetchApi( diff --git a/scripts/multisrc/ifreedom/template.ts b/scripts/multisrc/ifreedom/template.ts index f81901a..b6199e3 100644 --- a/scripts/multisrc/ifreedom/template.ts +++ b/scripts/multisrc/ifreedom/template.ts @@ -140,7 +140,7 @@ class IfreedomPlugin implements Plugin.PluginBase { res.text(), ); let chapterText = - body.match(/
/)?.[0] || ''; chapterText = chapterText.replace(/]*>[\s\S]*?<\/script>/gim, ''); if (chapterText.includes(', + searchTerm?: string, + ): Promise { + const data = new URLSearchParams({ + sort: showLatestNovels ? 'updated_at' : filters?.sort?.value || 'viewed', + page, + }); + if (searchTerm) data.append('q', searchTerm); + + const url = this.site + '/search?' + data.toString(); + + const body = await fetchApi(url).then(res => res.text()); + const loadedCheerio = parseHTML(body); + + const novels: Plugin.NovelItem[] = []; + loadedCheerio('div[class="comic-item"] > a').each((index, element) => { + const name = loadedCheerio(element) + .find('div[class="comic-title"]') + .text() + ?.trim(); + const cover = + loadedCheerio(element) + .find('div[class="comic-image lozad "]') + .attr('data-background-image') || defaultCover; + const url = loadedCheerio(element).attr('href'); + + if (!name || !url) return; + + novels.push({ name, cover, path: url.replace('/novel/', '') }); + }); + + return novels; + } + + popularNovels = this.fetchNovels; + + async searchNovels( + searchTerm: string, + page: number, + ): Promise { + const defaultOptions: any = { + filters: undefined, + showLatestNovels: false, + }; + return this.fetchNovels(page, defaultOptions, searchTerm); + } + + async parseNovel(novelPath: string): Promise { + const body = await fetchApi(this.resolveUrl(novelPath, true)).then(res => + res.text(), + ); + const loadedCheerio = parseHTML(body); + + const novel: Plugin.SourceNovel = { + path: novelPath, + name: loadedCheerio('h1[class="title"]').text().trim(), + cover: + loadedCheerio('div[class="img-manga"] > img').attr('src') || + defaultCover, + summary: loadedCheerio('div[class="summary"] > p[class="content"]') + .text() + .trim(), + author: loadedCheerio('div[class="col-md-7"] > p:nth-child(5) > a') + .text() + .trim(), + status: + loadedCheerio('span[class="text-ongoing"]').text() === 'Ongoing' + ? NovelStatus.Ongoing + : NovelStatus.Completed, + }; + + const chapters: Plugin.ChapterItem[] = []; + const totalChapters = loadedCheerio('div[class="list-group"] > a').length; + + loadedCheerio('div[class="list-group"] > a').each( + (chapterIndex, element) => { + const name = loadedCheerio(element).find('span').text().trim(); + const id = loadedCheerio(element).attr('id')?.replace(/\D/g, ''); + if (!name || !id) return; + + const releaseDate = loadedCheerio(element).find('p').text(); + chapters.push({ + name, + path: novelPath + '/' + id, + releaseTime: this.parseDate(releaseDate?.trim()), + chapterNumber: totalChapters - chapterIndex, + }); + }, + ); + + novel.chapters = chapters.reverse(); + return novel; + } + + async parseChapter(chapterPath: string): Promise { + const body = await fetchApi(this.resolveUrl(chapterPath)).then(res => + res.text(), + ); + const loadedCheerio = parseHTML(body); + + const chapterText = loadedCheerio('.content').html(); + return chapterText || ''; + } + + parseDate = (date = '') => { + if (!date) return null; + if (date.includes('ago')) { + const [value, type] = date.split(' '); + if (!value || !type) return null; + + switch (type.toLowerCase()) { + case 'minutes': { + const minutes = parseInt(value, 10); + date = Date.now() - minutes * 60 * 1000; + break; + } + case 'hour': + case 'hours': { + const hours = parseInt(value, 10); + date = Date.now() - hours * 60 * 60 * 1000; + break; + } + case 'day': + case 'days': { + const days = parseInt(value, 10); + date = Date.now() - days * 24 * 60 * 60 * 1000; + break; + } + default: + console.log(date); + date = undefined; + } + return dayjs(date).format('LLL'); + } + return date; + }; + + resolveUrl = (path: string, isNovel?: boolean) => + this.site + (isNovel ? '/novel/' : '/read/') + path; + + filters = { + sort: { + label: 'Sort By:', + value: 'viewed', + options: [ + { label: 'Viewed', value: 'viewed' }, + { label: 'Scored', value: 'scored' }, + { label: 'Newest', value: 'created_at' }, + { label: 'Latest Update', value: 'updated_at' }, + ], + type: FilterTypes.Picker, + }, + } satisfies Filters; +} + +export default new VyNovel(); diff --git a/src/plugins/russian/authortoday.ts b/src/plugins/russian/authortoday.ts index 552103d..2a36582 100644 --- a/src/plugins/russian/authortoday.ts +++ b/src/plugins/russian/authortoday.ts @@ -175,7 +175,7 @@ class AuthorToday implements Plugin.PluginBase { } resolveUrl = (path: string, isNovel?: boolean) => - isNovel ? this.site + '/work/' + path : this.site + '/reader/' + path; + this.site + (isNovel ? '/work/' : '/reader/') + path; user: authorization | undefined; getUser = async () => {