From 663c5f651fd7ef521652e6c8da7245be32d2ac62 Mon Sep 17 00:00:00 2001 From: Raiyn Aydin Date: Wed, 7 Oct 2026 11:41:27 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(turkish/rezeroturkce):=20add=20Re:Zero?= =?UTF-8?q?=20T=C3=BCrk=C3=A7e=20source?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a plugin for rezeroturkce.com. Each story page (main-story arcs, IF routes, EX novels) is a novel and the side-story index is one novel; chapters are the on-site links on each story page, with release dates from the site's reading-progress table. Links that point to EpikNovel are skipped because that site does not respond. Closes #2637 --- plugins/turkish/rezeroturkce.ts | 248 +++++++++++++++++++++ public/static/src/tr/rezeroturkce/icon.png | Bin 0 -> 19369 bytes 2 files changed, 248 insertions(+) create mode 100644 plugins/turkish/rezeroturkce.ts create mode 100644 public/static/src/tr/rezeroturkce/icon.png diff --git a/plugins/turkish/rezeroturkce.ts b/plugins/turkish/rezeroturkce.ts new file mode 100644 index 000000000..8a988daff --- /dev/null +++ b/plugins/turkish/rezeroturkce.ts @@ -0,0 +1,248 @@ +import { CheerioAPI, load } from 'cheerio'; +import { fetchApi } from '@libs/fetch'; +import { Plugin } from '@/types/plugin'; +import { defaultCover } from '@libs/defaultCover'; +import { NovelStatus } from '@libs/novelStatus'; + +const SITE_URL = /^https?:\/\/(?:www\.)?rezeroturkce\.com\//i; + +// Sections that hold the translated stories; anything else on the site +// (anime, manga, announcements, info pages) is not a novel. +const STORY_SECTIONS = [ + 'ana-hikaye', + 'if-hikayeleri', + 'yan-hikayeler', + 'ex-romanlari', +]; + +const CHALLENGE_TITLES = [ + 'bot verification', + 'you are being redirected...', + 'just a moment...', + 'bir dakika lütfen...', + 'redirecting...', +]; + +const MONTHS = [ + 'ocak', + 'şubat', + 'mart', + 'nisan', + 'mayıs', + 'haziran', + 'temmuz', + 'ağustos', + 'eylül', + 'ekim', + 'kasım', + 'aralık', +]; + +const normalize = (text: string) => + text + .toLowerCase() + .replace(/ı/g, 'i') + .normalize('NFD') + .replace(/[̀-ͯ]/g, '') + .trim(); + +const cleanText = (text: string) => text.replace(/\s+/g, ' ').trim(); + +class ReZeroTurkce implements Plugin.PluginBase { + id = 'rezeroturkce'; + name = 'Re:Zero Türkçe'; + icon = 'src/tr/rezeroturkce/icon.png'; + site = 'https://www.rezeroturkce.com/'; + version = '1.0.0'; + + async getCheerio(path: string): Promise { + const res = await fetchApi(this.site + path); + const html = await res.text(); + const title = html.match(/]*>([^<]*)<\/title>/i)?.[1]; + if (title && CHALLENGE_TITLES.includes(cleanText(title).toLowerCase())) + throw new Error('Captcha error, please open in webview'); + if (!res.ok) + throw new Error( + 'Could not reach site (' + res.status + '), try to open in webview', + ); + return load(html); + } + + /** Site-relative path of an on-site link, or undefined for anything else. */ + toPath(href: string | undefined): string | undefined { + if (!href || !SITE_URL.test(href)) return undefined; + const path = href.replace(SITE_URL, '').split(/[?#]/)[0]; + if (!path) return undefined; + return path.endsWith('/') ? path : path + '/'; + } + + /** A story page lives directly under one of the story sections. */ + isStoryPath(path: string | undefined): path is string { + if (!path) return false; + const parts = path.split('/').filter(Boolean); + return parts.length === 2 && STORY_SECTIONS.includes(parts[0]); + } + + /** Index pages show each story as a column block with a cover and a link. */ + parseStoryIndex($: CheerioAPI, seen: Set): Plugin.NovelItem[] { + const novels: Plugin.NovelItem[] = []; + $('article .wp-block-columns').each((_i, el) => { + const block = $(el); + // Bare-domain images only redirect to www, so request www directly. + const cover = block + .find('img') + .first() + .attr('src') + ?.replace(SITE_URL, this.site); + const name = cleanText(block.find('h2').first().text()); + if (!cover || !name) return; + const path = block + .find('a') + .toArray() + .map(a => this.toPath($(a).attr('href'))) + .find(p => this.isStoryPath(p)); + if (!path || seen.has(path)) return; + seen.add(path); + novels.push({ name, path, cover }); + }); + return novels; + } + + async popularNovels(pageNo: number): Promise { + if (pageNo > 1) return []; + const [main, ifStories, exNovels, sideStories] = await Promise.all( + ['ana-hikaye/', 'if-hikayeleri/', 'ex-romanlari/', 'yan-hikayeler/'].map( + path => this.getCheerio(path), + ), + ); + const seen = new Set(); + const novels = [ + ...this.parseStoryIndex(main, seen), + ...this.parseStoryIndex(ifStories, seen), + ...this.parseStoryIndex(exNovels, seen), + ]; + // Side stories are single pages, so their index page is one novel. + novels.push({ + name: cleanText(sideStories('main h1').first().text()) || 'Yan Hikâyeler', + path: 'yan-hikayeler/', + cover: + sideStories('meta[property="og:image"]').attr('content') || + defaultCover, + }); + return novels; + } + + parseDate(text: string): string | undefined { + const match = text.trim().match(/^(\d{1,2})\s+(\S+)\s+(\d{4})$/); + if (!match) return undefined; + const month = MONTHS.indexOf(match[2].toLowerCase()); + if (month < 0) return undefined; + return ( + match[3] + + '-' + + String(month + 1).padStart(2, '0') + + '-' + + match[1].padStart(2, '0') + ); + } + + async parseNovel(novelPath: string): Promise { + const $ = await this.getCheerio(novelPath); + const article = $('main article').first(); + + const novel: Plugin.SourceNovel = { + path: novelPath, + name: cleanText($('main h1').first().text()) || 'Untitled', + cover: $('meta[property="og:image"]').attr('content') || defaultCover, + status: NovelStatus.Unknown, + }; + + // The story notes and "Ön Bakış" (overview) come before the chapter list. + const summary: string[] = []; + article.find('br').replaceWith(' '); + for (const el of article.find('h2, p, ul, ol, table').toArray()) { + const node = $(el); + if (node.is('ul, ol, table')) break; + const text = cleanText(node.text()); + if (/^bolumler/.test(normalize(text))) break; + if (text) summary.push(text); + } + if (summary.length) novel.summary = summary.join('\n\n'); + if (/cevirisi tamamlan/.test(normalize(article.find('h2').text()))) + novel.status = NovelStatus.Completed; + + // The reading-progress table carries each chapter's publication date. + const dates = new Map(); + article.find('tr').each((_i, el) => { + const path = this.toPath($(el).find('a').attr('href')); + const date = this.parseDate($(el).find('td').last().text()); + if (path && date) dates.set(path, date); + }); + + // Chapters live under the story page (EX volumes use "-"). + const siblingPrefix = novelPath.replace(/\/$/, '-'); + const chapters: Plugin.ChapterItem[] = []; + const seen = new Set(); + const addChapter = (href: string | undefined, name: string) => { + const path = this.toPath(href); + // Only chapters hosted on this site; older arcs also link out to + // EpikNovel, which this plugin cannot read. + if (!path || path === novelPath) return; + if (!path.startsWith(novelPath) && !path.startsWith(siblingPrefix)) + return; + if (seen.has(path) || !name) return; + seen.add(path); + chapters.push({ name, path, releaseTime: dates.get(path) }); + }; + + article.find('a').each((_i, el) => { + const link = $(el); + // A list item can split one title over several links to the same + // chapter, so name it after the whole item. + const item = link.closest('li'); + if (item.length && item.find('a').length > 1) { + if (link.is(item.find('a').last())) + addChapter(link.attr('href'), cleanText(item.text())); + return; + } + addChapter(link.attr('href'), cleanText(link.text()).replace(/:$/, '')); + }); + + novel.chapters = chapters; + return novel; + } + + async parseChapter(chapterPath: string): Promise { + const $ = await this.getCheerio(chapterPath); + const content = $('article.entry-content').first(); + content + .find( + 'script, style, noscript, iframe, ins, .wp-block-post-navigation-link', + ) + .remove(); + // Previous/next navigation rows are left empty once their links go. + content.find('.wp-block-columns').each((_i, el) => { + const block = $(el); + if (!cleanText(block.text()) && !block.find('img').length) block.remove(); + }); + content + .find('p') + .filter((_i, el) => + /^\[adinserter[^\]]*\]$/.test(cleanText($(el).text())), + ) + .remove(); + return content.html() || ''; + } + + async searchNovels( + searchTerm: string, + pageNo: number, + ): Promise { + if (pageNo > 1) return []; + const term = normalize(searchTerm); + const novels = await this.popularNovels(1); + return novels.filter(novel => normalize(novel.name).includes(term)); + } +} + +export default new ReZeroTurkce(); diff --git a/public/static/src/tr/rezeroturkce/icon.png b/public/static/src/tr/rezeroturkce/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..86b746ddf21b66a0610b24178f0dc7bba0652cf7 GIT binary patch literal 19369 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 literal 0 HcmV?d00001 From 40a4bcc47437a6a229b27d37e47e290171577c6d Mon Sep 17 00:00:00 2001 From: Raiyn Aydin Date: Wed, 7 Oct 2026 11:49:21 +0800 Subject: [PATCH 2/2] fix(turkish/rezeroturkce): strip inline handlers and tighten status match MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove on* attributes and javascript: URLs from chapter HTML, and only mark a story completed for "tamamlanmıştır"/"tamamlandı", not for wording such as "tamamlanmadı". --- plugins/turkish/rezeroturkce.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/plugins/turkish/rezeroturkce.ts b/plugins/turkish/rezeroturkce.ts index 8a988daff..038d7df10 100644 --- a/plugins/turkish/rezeroturkce.ts +++ b/plugins/turkish/rezeroturkce.ts @@ -38,6 +38,8 @@ const MONTHS = [ 'aralık', ]; +const COMPLETED = /cevirisi tamamlan(?:mistir|di)(?![a-z])/; + const normalize = (text: string) => text .toLowerCase() @@ -168,7 +170,8 @@ class ReZeroTurkce implements Plugin.PluginBase { if (text) summary.push(text); } if (summary.length) novel.summary = summary.join('\n\n'); - if (/cevirisi tamamlan/.test(normalize(article.find('h2').text()))) + // Only "tamamlanmıştır"/"tamamlandı" (completed), not "tamamlanmadı" etc. + if (COMPLETED.test(normalize(article.find('h2').text()))) novel.status = NovelStatus.Completed; // The reading-progress table carries each chapter's publication date. @@ -231,6 +234,13 @@ class ReZeroTurkce implements Plugin.PluginBase { /^\[adinserter[^\]]*\]$/.test(cleanText($(el).text())), ) .remove(); + // Drop inline event handlers and script URLs from the site's markup. + content.find('*').each((_i, el) => { + for (const [name, value] of Object.entries(el.attribs)) { + if (/^on/i.test(name) || /^\s*javascript:/i.test(value)) + $(el).removeAttr(name); + } + }); return content.html() || ''; }