From e4a47d8455ccd8f01aca626b33c017552fbf283a Mon Sep 17 00:00:00 2001 From: 7ui77 Date: Tue, 6 Oct 2026 20:03:01 +0700 Subject: [PATCH 1/2] fix(english): fix WTR-LAB only showing [image] instead of images --- plugins/english/wtrlab.ts | 70 +++++++++++++++++++++++++++++++++++---- 1 file changed, 63 insertions(+), 7 deletions(-) diff --git a/plugins/english/wtrlab.ts b/plugins/english/wtrlab.ts index 0bb43044c..4ebc45d8b 100644 --- a/plugins/english/wtrlab.ts +++ b/plugins/english/wtrlab.ts @@ -9,7 +9,7 @@ class WTRLAB implements Plugin.PluginBase { id = 'WTRLAB'; name = 'WTR-LAB'; site = 'https://wtr-lab.com/'; - version = '1.2.3'; + version = '1.2.4'; icon = 'src/en/wtrlab/icon.png'; sourceLang = 'en/'; baggage = ''; @@ -831,10 +831,18 @@ class WTRLAB implements Plugin.PluginBase { } let chapterContent: any = content.body; - const chapterTitle: string | undefined = - parsedJson?.chapter?.title || content.title; const chapterGlossary: ChapterContent['glossary_data'] | undefined = content.glossary_data; + const dictionary = chapterGlossary?.terms?.map(t => t[0]) || []; + + let chapterTitle: string | undefined = + parsedJson?.chapter?.title || content.title; + if (chapterTitle && dictionary.length > 0) { + chapterTitle = chapterTitle.replaceAll( + /(?:wtr-lab\s+)?※([0-9]+)[⛬〓]/g, + (m: string, index: string) => dictionary[parseInt(index)] || m, + ); + } let htmlString = ''; if (chapterTitle) { @@ -870,16 +878,63 @@ class WTRLAB implements Plugin.PluginBase { )}

`; } - const dictionary = chapterGlossary?.terms?.map(t => t[0]) || []; + const images: string[] = Array.isArray(content.images) + ? content.images + : Array.isArray((parsedJson as any)?.images) + ? (parsedJson as any).images + : []; + let imageIndex = 0; + + const resolveImageUrl = (imgUrl: string): string => { + if (!imgUrl) return ''; + if (/^https?:\/\//i.test(imgUrl)) return imgUrl; + if (imgUrl.startsWith('//')) return 'https:' + imgUrl; + return this.site.replace(/\/$/, '') + '/' + imgUrl.replace(/^\//, ''); + }; + + const paragraphs: string[] = Array.isArray(chapterContent) + ? chapterContent + : typeof chapterContent === 'string' + ? chapterContent.split('\n') + : []; - for (let text of chapterContent) { + for (let text of paragraphs) { + if (typeof text !== 'string') continue; if (dictionary.length > 0) { text = text.replaceAll( /(?:wtr-lab\s+)?※([0-9]+)[⛬〓]/g, (m: string, index: string) => dictionary[parseInt(index)] || m, ); } - htmlString += `

${text}

`; + + if (/\[\s*image\s*\]/i.test(text)) { + const parts = text.split(/(\[\s*image\s*\])/gi); + let paragraphHtml = ''; + for (const part of parts) { + if (/^\[\s*image\s*\]$/i.test(part.trim())) { + const nextImg = images[imageIndex++]; + if (nextImg) { + paragraphHtml += ``; + } else { + paragraphHtml += '[image]'; + } + } else if (part) { + paragraphHtml += part; + } + } + if (paragraphHtml.trim()) { + htmlString += `

${paragraphHtml}

`; + } + } else { + htmlString += `

${text}

`; + } + } + + while (imageIndex < images.length) { + const remainingImg = images[imageIndex++]; + if (remainingImg) { + htmlString += `

`; + } } return htmlString; @@ -2045,7 +2100,8 @@ type ReaderResponse = { type ChapterContent = { title: string; - body: string; + body: string | string[]; + images?: string[]; glossary_data?: { terms: string[][]; }; From 53b566947cf96d66dac76bf6ba6e1921f009168b Mon Sep 17 00:00:00 2001 From: 7ui77 Date: Tue, 6 Oct 2026 20:33:44 +0700 Subject: [PATCH 2/2] fix(english): escape image URLs in HTML attributes --- plugins/english/wtrlab.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/plugins/english/wtrlab.ts b/plugins/english/wtrlab.ts index 4ebc45d8b..dfa4050af 100644 --- a/plugins/english/wtrlab.ts +++ b/plugins/english/wtrlab.ts @@ -892,6 +892,14 @@ class WTRLAB implements Plugin.PluginBase { return this.site.replace(/\/$/, '') + '/' + imgUrl.replace(/^\//, ''); }; + const escapeAttr = (str: string): string => + str + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/'/g, ''') + .replace(//g, '>'); + const paragraphs: string[] = Array.isArray(chapterContent) ? chapterContent : typeof chapterContent === 'string' @@ -914,7 +922,9 @@ class WTRLAB implements Plugin.PluginBase { if (/^\[\s*image\s*\]$/i.test(part.trim())) { const nextImg = images[imageIndex++]; if (nextImg) { - paragraphHtml += ``; + paragraphHtml += ``; } else { paragraphHtml += '[image]'; } @@ -933,7 +943,9 @@ class WTRLAB implements Plugin.PluginBase { while (imageIndex < images.length) { const remainingImg = images[imageIndex++]; if (remainingImg) { - htmlString += `

`; + htmlString += `

`; } }