From 9bcc5871ca748648efd17ec5f7c678dedad939ae Mon Sep 17 00:00:00 2001
From: 7ui77 <99854073+7ui77@users.noreply.github.com>
Date: Tue, 19 May 2026 21:29:33 +0700
Subject: [PATCH 01/23] NovelBuddy: refactor fwnRegex and bump version to 2.1.4
---
plugins/english/novelbuddy.ts | 21 ++++++++++++++++++---
1 file changed, 18 insertions(+), 3 deletions(-)
diff --git a/plugins/english/novelbuddy.ts b/plugins/english/novelbuddy.ts
index da81763d6..ad3517b38 100644
--- a/plugins/english/novelbuddy.ts
+++ b/plugins/english/novelbuddy.ts
@@ -4,15 +4,30 @@ import { Plugin } from '@/types/plugin';
import { Filters, FilterTypes } from '@libs/filterInputs';
import { NovelStatus } from '@libs/novelStatus';
-const fwnRegex =
- /(?:𝐟|ᵮ|𝑓|𝒇|𝒻|𝓯|𝔣|𝕗|𝖿|𝗳|𝙛|𝚏|ꬵ|ꞙ|ẝ|𝖋|ⓕ|f|ḟ|ʃ|բ|ᶠ|⒡|ſ|ꊰ|ʄ|∱|ᶂ|𝘧|\bf)(?:𝚛|ꭇ|ᣴ|ℾ|𝚪|𝛤|𝜞|𝝘|𝞒|Ⲅ|Г|Ꮁ|ᒥ|ꭈ|ⲅ|ꮁ|ⓡ|r|ŕ|ṙ|ř|ȑ|ȓ|ṛ|ṝ|ŗ|г|Ր|ɾ|ᥬ|ṟ|ɍ|ʳ|⒭|ɼ|ѓ|ᴦ|ᶉ|𝐫|𝑟|𝒓|𝓇|𝓻|𝔯|𝕣|𝖗|𝗋|𝗿|𝘳|𝙧|ᵲ|ґ|ᵣ|r)(?:ə|ә|ⅇ|ꬲ|ꞓ|⋴|𝛆|𝛜|𝜀|𝜖|𝜺|𝝐|𝝴|𝞊|𝞮|𝟄|ⲉ|ꮛ|𐐩|Ꞓ|Ⲉ|⍷|𝑒|𝓮|𝕖|𝖊|𝘦|𝗲|𝚎|𝙚|𝒆|𝔢|𝖾|𝐞|Ҿ|ҿ|ⓔ|e|⒠|è|ᧉ|é|ᶒ|ê|ɘ|ἔ|ề|ế|ễ|૯|ǝ|є|ε|ē|ҽ|ɛ|ể|ẽ|ḕ|ḗ|ĕ|ė|ë|ẻ|ě|ȅ|ȇ|ẹ|ệ|ȩ|ɇ|ₑ|ę|ḝ|ḙ|ḛ|℮|е|ԑ|ѐ|ӗ|ᥱ|ё|ἐ|ἑ|ἒ|ἓ|ἕ|ℯ|e)+(?:𝐰|ꝡ|𝑤|𝒘|𝓌|𝔀|𝔴|𝕨|𝖜|𝗐|𝘄|𝘸|𝙬|𝚠|ա|ẁ|ꮃ|ẃ|ⓦ|⍵|ŵ|ẇ|ẅ|ẘ|ẉ|ⱳ|ὼ|ὠ|ὡ|ὢ|ὣ|ω|ὤ|ὥ|ὦ|ὧ|ῲ|ῳ|ῴ|ῶ|ῷ|Ⱳ|ѡ|ԝ|ᴡ|ώ|ᾠ|ᾡ|ᾡ|ᾢ|ᾣ|ᾤ|ᾥ|ᾦ|ɯ|𝝕|𝟉|𝞏|w)(?:ə|ә|ⅇ|ꬲ|ꞓ|⋴|𝛆|𝛜|𝜀|𝜖|𝜺|𝝐|𝝴|𝞊|𝞮|𝟄|ⲉ|ꮛ|𐐩|Ꞓ|Ⲉ|⍷|𝑒|𝓮|𝕖|𝖊|𝘦|𝗲|𝚎|𝙚|𝒆|𝔢|𝖾|𝐞|Ҿ|ҿ|ⓔ|e|⒠|è|ᧉ|é|ᶒ|ê|ɘ|ἔ|ề|ế|ễ|૯|ǝ|є|ε|ē|ҽ|ɛ|ể|ẽ|ḕ|ḗ|ĕ|ė|ë|ẻ|ě|ȅ|ȇ|ẹ|ệ|ȩ|ɇ|ₑ|ę|ḝ|ḙ|ḛ|℮|е|ԑ|ѐ|ӗ|ᥱ|ё|ἐ|ἑ|ἒ|ἓ|ἕ|ℯ|e)(?:ꮟ|Ꮟ|𝐛|𝘣|𝒷|𝔟|𝓫|𝖇|𝖻|𝑏|𝙗|𝕓|𝒃|𝗯|𝚋|♭|ᑳ|ᒈ|b|ᖚ|ᕹ|ᕺ|ⓑ|ḃ|ḅ|ҍ|ъ|ḇ|ƃ|ɓ|ƅ|ᖯ|Ƅ|Ь|ᑲ|þ|Ƃ|⒝|Ъ|ᶀ|ᑿ|ᒀ|ᒂ|ᒁ|ᑾ|ь|ƀ|Ҍ|Ѣ|ѣ|ᔎ |b)(?:ո|ռ|ח|𝒏|𝓷|𝙣|𝑛|𝖓|𝔫|𝗇|𝚗|𝗻|ᥒ|ⓝ|ή|n|ǹ|ᴒ|ń|ñ|ᾗ|η|ṅ|ň|ṇ|ɲ|ņ|ṋ|ṉ|ղ|ຖ|Ռ|ƞ|ŋ|⒩|ภ|ก|ɳ|п|ʼn|л|ԉ|Ƞ|ἠ|ἡ|ῃ|դ|ᾐ|ᾑ|ᾒ|ᾓ|ᾔ|ᾕ|ᾖ|ῄ|ῆ|ῇ|ῂ|ἢ|ἣ|ἤ|ἥ|ἦ|ἧ|ὴ|ή|በ|ቡ|ቢ|ባ|ቤ|ብ|ቦ|ȵ|𝛈|𝜂|𝜼|𝝶|𝞰|𝕟|延|𝐧|𝔫|ᶇ|ᵰ|ᥥ|∩|n)(?:ం|ం|ം|ං|૦|௦|۵|ℴ|𝑜|𝒐|𝒐|ꬽ|𝝄|𝛔|𝜎|𝝈|𝞂|ჿ|𝚘|০|୦|ዐ|𝛐|𝗈|𝞼|ဝ|ⲟ|𝙤|၀|𐐬|𝔬|𐓪|𝓸|🇴|⍤|○|ϙ|🅾|𝒪|𝖮|𝟢|𝟶|𝙾|o|𝗼|𝕠|𝜊|𝐨|𝝾|𝞸|ᐤ|オ|ѳ|᧐|ᥲ|ð|o|ఠ|ᦞ|Փ|ò|ө|ӧ|ó|º|ō|ô|ǒ|ȏ|ŏ|ồ|ȭ|ṏ|ὄ|ṑ|ṓ|ȯ|ȫ|๏|ᴏ|ő|ö|ѻ|о|ዐ|ǭ|ȱ|০|୦|٥|౦|告知|๐|໐|ο|օ|ᴑ|०|੦|ỏ|ơ|ờ|ớ|ỡ|ở|ợ|ọ|ộ|ǫ|ø|ǿ|ɵ|ծ|ὀ|ὁ|ό|ὸ|ό|ὂ|ὃ|ὅ|o)(?:∨|⌄|\|ⅴ|𝐯|𝑣|𝒗|𝓋|𝔳|𝕧|𝖛|ꮩ|ሀ|ⓥ|v|𝜐|𝝊|ṽ|ṿ|౮|ง|ѵ|ע|ᴠ|ν|ט|ᵥ|ѷ|៴|ᘁ|𝙫|𝙫|𝛎|𝜈|𝝂|𝝼|𝞶|𝘷|𝘃|𝓿|v)(?:ə|ә|ⅇ|ꬲ|ꞓ|⋴|𝛆|𝛜|𝜀|𝜖|𝜺|𝝐|𝝴|𝞊|𝞮|𝟄|ⲉ|ꮛ|𐐩|Ꞓ|Ⲉ|⍷|𝑒|𝓮|𝕖|𝖊|𝘦|𝗲|𝚎|𝙚|𝒆|𝔢|𝖾|𝐞|Ҿ|ҿ|ⓔ|e|⒠|è|ᧉ|é|ᶒ|ê|ɘ|ἔ|ề|ế|ễ|૯|ǝ|є|ε|ē|ҽ|ɛ|ể|ẽ|ḕ|ḗ|ĕ|ė|ë|ẻ|ě|ȅ|ȇ|ẹ|ệ|ȩ|ɇ|ę|ḝ|ḙ|ḛ|℮|е|ԑ|ѐ|ӗ|ᥱ|ё|ἐ|ἑ|ἒ|ἓ|ἕ|ℯ|e)(?:ⓛ|l|ŀ|ĺ|ľ|ḷ|ḹ|ḷ|ļ|Ӏ|ℓ|ḽ|ḻ|ł|レ|ɭ|ƚ|ɫ|ⱡ|\||\\|Ɩ|⒧|ʅ|ǀ|ו|ן|Ι|І|||ᶩ|ӏ|𝓘|𝕀|𝖨|𝗜|𝘐|𝐥|𝑙|𝒍|𝓁|𝔩|𝕝|𝖑|ލ|𝗅|𝗹|ލ|𝗅|𝗹|𝘭|𝚕|𝜤|𝝞|ı|𝚤|ɩ|ι|𝛊|𝜄|𝜾|𝞲|I|l)(?:.?(?:🝌|c|ⅽ|𝐜|𝑐|𝒄|𝒸|𝓬|𝔠|𝕔|𝖈|𝖈|𝗰|𝘤|𝙘|𝚌|ᴄ|ϲ|ⲥ|с|ꮯ|𐐽|ⲥ|𐐽|ꮯ|ĉ|c|ⓒ|ć|č|ċ|ç|ҁ|ƈ|ḉ|ȼ|ↄ|с|ር|ᴄ|ϲ|ҫ|꒝|ς|ɽ|ϛ|𝙲|ᑦ|᧚|𝐜|𝑐|𝒄|𝒸|𝓬|𝔠|𝕔|𝖈|𝖈|𝗰|𝘤|𝙘|𝚌|₵|🇨|ᥴ|ᒼ|ⅽ|c)(?:ం|ం|ം|ං|૦|௦|۵|ℴ|𝑜|𝒐|𝒐|ꬽ|𝝄|𝛔|𝜎|𝝈|𝞂|ჿ|𝚘|০|୦|ዐ|𝗈|𝞼|ဝ|ⲟ|𝙤|၀|𐐬|𝔬|𐓪|𝓸|🇴|⍤|○|ϙ|🅾|𝒪|𝖮|𝟢|𝟶|𝙾|o|𝗼|𝕠|𝜊|𝐨|𝝾|𝞸|ᐤ|ⓞ|ѳ|᧐|ᥲ|ð|o|ఠ|ᦞ|Փ|ò|ө|ӧ|ó|º|ō|ô|ǒ|ȏ|ŏ|ồ|ȭ|ṏ|ὄ|ṑ|ṓ|ȯ|ȫ|๏|ᴏ|ő|ö|ѻ|о|ዐ|ǭ|ȱ|০|୦|٥|౦|告知|๐|໐|ο|օ|ᴑ|०|੦|ỏ|ơ|ờ|ớ|ỡ|ở|ợ|ọ|ộ|ǫ|ø|ǿ|ɵ|ծ|ὀ|ὁ|ό|ὸ|ό|ὂ|ὃ|ὅ|o)(?:₥|ᵯ|𝖒|𝐦|𝗆|𝔪|𝕞|𝕞|𝕞|ⓜ|m|ന|ᙢ|൩|ḿ|ṁ|ⅿ|ϻ|ṃ|ጠ|ɱ|៳|ᶆ|𝒎|🇲|𝙢|𝓶|𝚖|𝑚|𝗺|᧕|᧗|m))?/g;
+const fwnRegex = new RegExp(
+ [
+ '(?:𝐟|ᵮ|𝑓|𝒇|𝒻|𝓯|𝔣|𝕗|𝖿|𝗳|𝙛|𝚏|ꬵ|ꞙ|ẝ|𝖋|ⓕ|f|ḟ|ʃ|բ|ᶠ|⒡|ſ|ꊰ|ʄ|∱|ᶂ|𝘧|\\bf)',
+ '(?:𝚛|ꭇ|ᣴ|ℾ|𝚪|𝛤|𝜞|𝝘|𝞒|Ⲅ|Г|Ꮁ|ᒥ|ꭈ|ⲅ|ꮁ|ⓡ|r|ŕ|ṙ|ř|ȑ|ȓ|ṛ|ṝ|ŗ|г|Ր|ɾ|ᥬ|ṟ|ɍ|ʳ|⒭|ɼ|ѓ|ᴦ|ᶉ|𝐫|𝑟|𝒓|𝓇|𝓻|𝔯|𝕣|𝖗|𝗋|𝗿|𝘳|𝙧|ᵲ|ґ|ᵣ|r)',
+ '(?:ə|ә|ⅇ|ꬲ|ꞓ|⋴|𝛆|𝛜|𝜀|𝜖|𝜺|𝝐|𝝴|𝞊|𝞮|𝟄|ⲉ|ꮛ|𐐩|Ꞓ|Ⲉ|⍷|𝑒|𝓮|𝕖|𝖊|𝘦|𝗲|𝚎|𝙚|𝒆|𝔢|𝖾|𝐞|Ҿ|ҿ|ⓔ|e|⒠|è|ᧉ|é|ᶒ|ê|ɘ|ἔ|ề|ế|ễ|૯|ǝ|є|ε|ē|ҽ|ɛ|ể|ẽ|ḕ|ḗ|ĕ|ė|ë|ẻ|ě|ȅ|ȇ|ẹ|ệ|ȩ|ɇ|ₑ|ę|ḝ|ḙ|ḛ|℮|е|ԑ|ѐ|ӗ|ᥱ|ё|ἐ|ἑ|ἒ|ἓ|ἕ|ℯ|e)+',
+ '(?:𝐰|ꝡ|𝑤|𝒘|𝓌|𝔀|𝔴|𝕨|𝖜|𝗐|𝘄|𝘸|𝙬|𝚠|ա|ẁ|ꮃ|ẃ|ⓦ|⍵|ŵ|ẇ|ẅ|ẘ|ẉ|ⱳ|ὼ|ὠ|ὡ|ὢ|ὣ|ω|ὤ|ὥ|ὦ|ὧ|ῲ|ῳ|ῴ|ῶ|ῷ|Ⱳ|ѡ|ԝ|ᴡ|ώ|ᾠ|ᾡ|ᾢ|ᾣ|ᾤ|ᾥ|ᾦ|ɯ|𝝕|𝟉|𝞏|w)',
+ '(?:ə|ә|ⅇ|ꬲ|ꞓ|⋴|𝛆|𝛜|𝜀|𝜖|𝜺|𝝐|𝝴|𝞊|𝞮|𝟄|ⲉ|ꮛ|𐐩|Ꞓ|Ⲉ|⍷|𝑒|𝓮|𝕖|𝖊|𝘦|𝗲|𝚎|𝙚|𝒆|𝔢|𝖾|𝐞|Ҿ|ҿ|ⓔ|e|⒠|è|ᧉ|é|ᶒ|ê|ɘ|ἔ|ề|ế|ễ|૯|ǝ|є|ε|ē|ҽ|ɛ|ể|ẽ|ḕ|ḗ|ĕ|ė|ë|ẻ|ě|ȅ|ȇ|ẹ|ệ|ȩ|ɇ|ₑ|ę|ḝ|ḙ|ḛ|℮|е|ԑ|ѐ|ӗ|ᥱ|ё|ἐ|ἑ|ἒ|ἓ|ἕ|ℯ|e)',
+ '(?:ꮟ|Ꮟ|𝐛|𝘣|𝒷|𝔟|𝓫|𝖇|𝖻|𝑏|𝙗|𝕓|𝒃|𝗯|𝚋|♭|ᑳ|ᒈ|b|ᖚ|ᕹ|ᕺ|ⓑ|ḃ|ḅ|ҍ|ъ|ḇ|ƃ|ɓ|ƅ|ᖯ|Ƅ|Ь|ᑲ|þ|Ƃ|⒝|Ъ|ᶀ|ᑿ|ᒀ|ᒂ|ᒁ|ᑾ|ь|ƀ|Ҍ|Ѣ|ѣ|ᔎ|b)',
+ '(?:ո|ռ|ח|𝒏|𝓷|𝙣|𝑛|𝖓|𝔫|𝗇|𝗻|ᥒ|ⓝ|ή|n|ǹ|ᴒ|ń|ñ|ᾗ|η|ṅ|ň|ṇ|ɲ|ņ|ṋ|ṉ|ղ|ຖ|Ռ|ƞ|ŋ|⒩|ภ|ก|ɳ|п|ʼn|л|ԉ|Ƞ|ἠ|ἡ|ῃ|դ|ᾐ|ᾑ|ᾒ|ᾓ|ᾔ|ᾕ|ᾖ|ῄ|ῆ|ῇ|ῂ|ἢ|ἣ|ἤ|ἥ|ἦ|ἧ|ὴ|ή|በ|ቡ|ቢ|ባ|ቤ|ብ|ቦ|ȵ|𝛈|𝜂|𝜼|𝝶|𝞰|𝕟|延|𝐧|𝔫|ᶇ|ᵰ|ᥥ|∩|n|𝘯|𝓃)',
+ '(?:ం|ం|ം|ං|૦|௦|۵|ℴ|𝑜|𝒐|𝒐|ꬽ|𝝄|𝛔|𝜎|𝝈|𝞂|ჿ|𝚘|০|୦|ዐ|𝛐|𝗈|𝞼|ဝ|ⲟ|ⲟ|𝙤|၀|𐐬|𝔬|𐓪|𝓸|🇴|⍤|○|ϙ|🅾|𝒪|𝖮|𝟢|𝟶|𝙾|o|𝗼|𝕠|𝜊|𝐨|𝝾|𝞸|ᐤ|オ|ѳ|᧐|ᥲ|ð|o|ఠ|ᦞ|Փ|ò|ө|ӧ|ó|º|ō|ô|ǒ|ȏ|ŏ|ồ|ȭ|ṏ|ὄ|ṑ|ṓ|ȯ|ȫ|๏|ᴏ|ő|ö|ѻ|о|ዐ|ǭ|ȱ|০|୦|٥|౦|告知|๐|໐|ο|օ|ᴑ|०|੦|ỏ|ơ|ờ|ớ|ỡ|ở|ợ|ọ|ộ|ǫ|ø|ǿ|ɵ|ծ|ὀ|ὁ|ό|ὸ|ό|ὂ|ὃ|ὅ|o|ం|𝖔|𝘰|ⓞ|೦|൦)',
+ '(?:∨|⌄|\\||ⅴ|𝐯|𝑣|𝒗|𝓋|𝔳|𝕧|𝖛|ꮩ|ሀ|ⓥ|v|𝜐|𝝊|ṽ|ṿ|౮|ง|ѵ|ע|ᴠ|ν|ט|ᵥ|ѷ|៴|ᘁ|𝙫|𝙫|𝛎|𝜈|𝝂|𝝼|𝞶|𝘷|𝘃|𝓿|v|⋁|𝗏|𝚟)',
+ '(?:ə|ә|ⅇ|ꬲ|ꞓ|⋴|𝛆|𝛜|𝜀|𝜖|𝜺|𝝐|𝝴|𝞊|𝞮|𝟄|ⲉ|ꮛ|𐐩|Ꞓ|Ⲉ|⍷|𝑒|𝓮|𝕖|𝖊|𝘦|𝗲|𝚎|𝙚|𝒆|𝔢|𝖾|𝐞|Ҿ|ҿ|ⓔ|e|⒠|è|ᧉ|é|ᶒ|ê|ɘ|ἔ|ề|ế|ễ|૯|ǝ|є|ε|ē|ҽ|ɛ|ể|ẽ|ḕ|ḗ|ĕ|ė|ë|ẻ|ě|ȅ|ȇ|ẹ|ệ|ȩ|ɇ|ₑ|ę|ḝ|ḙ|ḛ|℮|е|ԑ|ѐ|ӗ|ᥱ|ё|ἐ|ἑ|ἒ|ἓ|ἕ|ℯ|e|ₑ)',
+ '(?:ⓛ|l|ŀ|ĺ|ľ|ḷ|ḹ|ḷ|ļ|Ӏ|ℓ|ḽ|ḽ|ł|レ|ɭ|ƚ|ɫ|ⱡ|\\||\\\\|Ɩ|⒧|ʅ|ǀ|ו|ן|Ι|І|||ᶩ|ӏ|𝓘|𝕀|𝖨|𝗜|𝘐|𝐥|𝑙|𝒍|𝓁|𝔩|𝕝|𝖑|ލ|𝗅|𝗹|ލ|𝗅|𝗹|ލ|𝗅|𝗹|𝘭|𝚕|𝜤|𝝞|ı|𝚤|ɩ|ι|𝛊|𝜄|𝜾|𝞲|I|l|𝙡|𝓵)',
+ '(?:.?(?:𝝌|c|ⅽ|𝐜|𝑐|𝒄|𝒸|𝓬|𝔠|𝕔|𝖈|𝖈|𝗰|𝘤|𝙘|𝚌|ᴄ|ϲ|ⲥ|с|ꮯ|𐐽|ⲥ|𐐽|ꮯ|ĉ|c|ⓒ|ć|č|ċ|ç|ҁ|ƈ|ḉ|ȼ|ↄ|с|ር|ᴄ|ϲ|ҫ|꒝|ς|ɽ|ϛ|𝙲|ᑦ|᧚|𝐜|𝑐|𝒄|𝒸|𝓬|𝔠|𝕔|𝖈|𝖈|𝗰|𝘤|𝙘|𝚌|₵|🇨|ᥴ|ᒼ|ⅽ|c)(?:ం|ం|ം|ං|૦|௦|۵|ℴ|𝑜|𝒐|𝒐|ꬽ|𝝄|𝛔|𝜎|𝝈|𝞂|ჿ|𝚘|০|୦|ዐ|𝛐|𝗈|𝞼|ဝ|ⲟ|𝙤|၀|𐐬|𝔬|𐓪|𝓸|🇴|⍤|○|ϙ|🅾|𝒪|𝖮|𝟢|𝟶|𝙾|o|𝗼|𝕠|𝜊|𝐨|𝝾|𝞸|ᐤ|オ|ѳ|᧐|ᥲ|ð|o|ఠ|ᦞ|Փ|ò|ө|ӧ|ó|º|ō|ô|ǒ|ȏ|ŏ|ồ|ȭ|ṏ|ὄ|ṑ|ṓ|ȯ|ȫ|๏|ᴏ|ő|ö|ѻ|о|ዐ|ǭ|ȱ|০|୦|٥|౦|告知|๐|໐|ο|օ|ᴑ|०|੦|ỏ|ơ|ờ|ớ|ỡ|ở|ợ|ọ|ộ|ǫ|ø|ǿ|ɵ|ծ|ὀ|ὁ|ό|ὸ|ό|ὂ|ὃ|ὅ|o|ం|𝖔|𝘰|ⓞ|೦|൦)(?:₥|ᵯ|𝖒|𝐦|𝗆|𝔪|𝕞|𝓂|ⓜ|m|ന|ᙢ|൩|ḿ|ṁ|ⅿ|ϻ|ṃ|ጠ|ɱ|៳|ᶆ|𝒎|🇲|𝙢|𝓶|𝚖|𝑚|𝗺|᧕|᧗|m|𝖼|𝛐))?'
+ ].join(''),
+ 'g',
+);
class NovelBuddy implements Plugin.PluginBase {
id = 'novelbuddy';
name = 'NovelBuddy';
site = 'https://novelbuddy.com/';
api = 'https://api.novelbuddy.com/';
- version = '2.1.2';
+ version = '2.1.4';
icon = 'src/en/novelbuddy/icon.png';
parseNovels(body: Response): Plugin.NovelItem[] {
From fabacc31b962ecd0fc3f7b2f9111eb9093018542 Mon Sep 17 00:00:00 2001
From: 7ui77 <99854073+7ui77@users.noreply.github.com>
Date: Tue, 19 May 2026 21:36:11 +0700
Subject: [PATCH 02/23] NovelBuddy: refactor fwnRegex and bump version to 2.1.3
---
plugins/english/novelbuddy.ts | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/plugins/english/novelbuddy.ts b/plugins/english/novelbuddy.ts
index ad3517b38..7e6f64d29 100644
--- a/plugins/english/novelbuddy.ts
+++ b/plugins/english/novelbuddy.ts
@@ -13,11 +13,11 @@ const fwnRegex = new RegExp(
'(?:ə|ә|ⅇ|ꬲ|ꞓ|⋴|𝛆|𝛜|𝜀|𝜖|𝜺|𝝐|𝝴|𝞊|𝞮|𝟄|ⲉ|ꮛ|𐐩|Ꞓ|Ⲉ|⍷|𝑒|𝓮|𝕖|𝖊|𝘦|𝗲|𝚎|𝙚|𝒆|𝔢|𝖾|𝐞|Ҿ|ҿ|ⓔ|e|⒠|è|ᧉ|é|ᶒ|ê|ɘ|ἔ|ề|ế|ễ|૯|ǝ|є|ε|ē|ҽ|ɛ|ể|ẽ|ḕ|ḗ|ĕ|ė|ë|ẻ|ě|ȅ|ȇ|ẹ|ệ|ȩ|ɇ|ₑ|ę|ḝ|ḙ|ḛ|℮|е|ԑ|ѐ|ӗ|ᥱ|ё|ἐ|ἑ|ἒ|ἓ|ἕ|ℯ|e)',
'(?:ꮟ|Ꮟ|𝐛|𝘣|𝒷|𝔟|𝓫|𝖇|𝖻|𝑏|𝙗|𝕓|𝒃|𝗯|𝚋|♭|ᑳ|ᒈ|b|ᖚ|ᕹ|ᕺ|ⓑ|ḃ|ḅ|ҍ|ъ|ḇ|ƃ|ɓ|ƅ|ᖯ|Ƅ|Ь|ᑲ|þ|Ƃ|⒝|Ъ|ᶀ|ᑿ|ᒀ|ᒂ|ᒁ|ᑾ|ь|ƀ|Ҍ|Ѣ|ѣ|ᔎ|b)',
'(?:ո|ռ|ח|𝒏|𝓷|𝙣|𝑛|𝖓|𝔫|𝗇|𝗻|ᥒ|ⓝ|ή|n|ǹ|ᴒ|ń|ñ|ᾗ|η|ṅ|ň|ṇ|ɲ|ņ|ṋ|ṉ|ղ|ຖ|Ռ|ƞ|ŋ|⒩|ภ|ก|ɳ|п|ʼn|л|ԉ|Ƞ|ἠ|ἡ|ῃ|դ|ᾐ|ᾑ|ᾒ|ᾓ|ᾔ|ᾕ|ᾖ|ῄ|ῆ|ῇ|ῂ|ἢ|ἣ|ἤ|ἥ|ἦ|ἧ|ὴ|ή|በ|ቡ|ቢ|ባ|ቤ|ብ|ቦ|ȵ|𝛈|𝜂|𝜼|𝝶|𝞰|𝕟|延|𝐧|𝔫|ᶇ|ᵰ|ᥥ|∩|n|𝘯|𝓃)',
- '(?:ం|ం|ം|ං|૦|௦|۵|ℴ|𝑜|𝒐|𝒐|ꬽ|𝝄|𝛔|𝜎|𝝈|𝞂|ჿ|𝚘|০|୦|ዐ|𝛐|𝗈|𝞼|ဝ|ⲟ|ⲟ|𝙤|၀|𐐬|𝔬|𐓪|𝓸|🇴|⍤|○|ϙ|🅾|𝒪|𝖮|𝟢|𝟶|𝙾|o|𝗼|𝕠|𝜊|𝐨|𝝾|𝞸|ᐤ|オ|ѳ|᧐|ᥲ|ð|o|ఠ|ᦞ|Փ|ò|ө|ӧ|ó|º|ō|ô|ǒ|ȏ|ŏ|ồ|ȭ|ṏ|ὄ|ṑ|ṓ|ȯ|ȫ|๏|ᴏ|ő|ö|ѻ|о|ዐ|ǭ|ȱ|০|୦|٥|౦|告知|๐|໐|ο|օ|ᴑ|०|੦|ỏ|ơ|ờ|ớ|ỡ|ở|ợ|ọ|ộ|ǫ|ø|ǿ|ɵ|ծ|ὀ|ὁ|ό|ὸ|ό|ὂ|ὃ|ὅ|o|ం|𝖔|𝘰|ⓞ|೦|൦)',
+ '(?:ం|ం|ം|ං|૦|௦|۵|ℴ|𝑜|𝒐|𝒐|ꬽ|𝝄|𝛔|𝜎|𝝈|𝞂|ჿ|𝚘|০|୦|ዐ|𝛐|𝗈|𝞼|ဝ|ⲟ|ⲟ|𝙤|၀|𐐬|𝔬|𐓪|𝓸|🇴|⍤|○|ϙ|🅾|𝒪|𝖮|𝟢|𝟶|𝙾|o|𝗼|𝕠|𝜊|𝐨|𝝾|𝞸|ᐤ|オ|ѳ|᧐|ᥲ|ð|o|ఠ|ᦞ|Փ|ò|ө|ӧ|ó|º|ō|ô|ǒ|ȏ|ŏ|ồ|ȭ|ṏ|ὄ|ṑ|ṓ|ȯ|ȫ|๏|ᴏ|ő|ö|ѻ|о|ዐ|ǭ|ȱ|০|୦|٥|౦|告知|๐|໐|ο|օ|ᴑ|०|੦|ỏ|ơ|ờ|ớ|ỡ|ở|ợ|ọ|ộ|ǫ|ø|ǿ|ɵ|ծ|ὀ|ὁ|ό|ὸ|ό|ὂ|ὃ|ὅ|o|ం|𝖔|オ|o|ⓞ|೦|൦)',
'(?:∨|⌄|\\||ⅴ|𝐯|𝑣|𝒗|𝓋|𝔳|𝕧|𝖛|ꮩ|ሀ|ⓥ|v|𝜐|𝝊|ṽ|ṿ|౮|ง|ѵ|ע|ᴠ|ν|ט|ᵥ|ѷ|៴|ᘁ|𝙫|𝙫|𝛎|𝜈|𝝂|𝝼|𝞶|𝘷|𝘃|𝓿|v|⋁|𝗏|𝚟)',
'(?:ə|ә|ⅇ|ꬲ|ꞓ|⋴|𝛆|𝛜|𝜀|𝜖|𝜺|𝝐|𝝴|𝞊|𝞮|𝟄|ⲉ|ꮛ|𐐩|Ꞓ|Ⲉ|⍷|𝑒|𝓮|𝕖|𝖊|𝘦|𝗲|𝚎|𝙚|𝒆|𝔢|𝖾|𝐞|Ҿ|ҿ|ⓔ|e|⒠|è|ᧉ|é|ᶒ|ê|ɘ|ἔ|ề|ế|ễ|૯|ǝ|є|ε|ē|ҽ|ɛ|ể|ẽ|ḕ|ḗ|ĕ|ė|ë|ẻ|ě|ȅ|ȇ|ẹ|ệ|ȩ|ɇ|ₑ|ę|ḝ|ḙ|ḛ|℮|е|ԑ|ѐ|ӗ|ᥱ|ё|ἐ|ἑ|ἒ|ἓ|ἕ|ℯ|e|ₑ)',
- '(?:ⓛ|l|ŀ|ĺ|ľ|ḷ|ḹ|ḷ|ļ|Ӏ|ℓ|ḽ|ḽ|ł|レ|ɭ|ƚ|ɫ|ⱡ|\\||\\\\|Ɩ|⒧|ʅ|ǀ|ו|ן|Ι|І|||ᶩ|ӏ|𝓘|𝕀|𝖨|𝗜|𝘐|𝐥|𝑙|𝒍|𝓁|𝔩|𝕝|𝖑|ލ|𝗅|𝗹|ލ|𝗅|𝗹|ލ|𝗅|𝗹|𝘭|𝚕|𝜤|𝝞|ı|𝚤|ɩ|ι|𝛊|𝜄|𝜾|𝞲|I|l|𝙡|𝓵)',
- '(?:.?(?:𝝌|c|ⅽ|𝐜|𝑐|𝒄|𝒸|𝓬|𝔠|𝕔|𝖈|𝖈|𝗰|𝘤|𝙘|𝚌|ᴄ|ϲ|ⲥ|с|ꮯ|𐐽|ⲥ|𐐽|ꮯ|ĉ|c|ⓒ|ć|č|ċ|ç|ҁ|ƈ|ḉ|ȼ|ↄ|с|ር|ᴄ|ϲ|ҫ|꒝|ς|ɽ|ϛ|𝙲|ᑦ|᧚|𝐜|𝑐|𝒄|𝒸|𝓬|𝔠|𝕔|𝖈|𝖈|𝗰|𝘤|𝙘|𝚌|₵|🇨|ᥴ|ᒼ|ⅽ|c)(?:ం|ం|ം|ං|૦|௦|۵|ℴ|𝑜|𝒐|𝒐|ꬽ|𝝄|𝛔|𝜎|𝝈|𝞂|ჿ|𝚘|০|୦|ዐ|𝛐|𝗈|𝞼|ဝ|ⲟ|𝙤|၀|𐐬|𝔬|𐓪|𝓸|🇴|⍤|○|ϙ|🅾|𝒪|𝖮|𝟢|𝟶|𝙾|o|𝗼|𝕠|𝜊|𝐨|𝝾|𝞸|ᐤ|オ|ѳ|᧐|ᥲ|ð|o|ఠ|ᦞ|Փ|ò|ө|ӧ|ó|º|ō|ô|ǒ|ȏ|ŏ|ồ|ȭ|ṏ|ὄ|ṑ|ṓ|ȯ|ȫ|๏|ᴏ|ő|ö|ѻ|о|ዐ|ǭ|ȱ|০|୦|٥|౦|告知|๐|໐|ο|օ|ᴑ|०|੦|ỏ|ơ|ờ|ớ|ỡ|ở|ợ|ọ|ộ|ǫ|ø|ǿ|ɵ|ծ|ὀ|ὁ|ό|ὸ|ό|ὂ|ὃ|ὅ|o|ం|𝖔|𝘰|ⓞ|೦|൦)(?:₥|ᵯ|𝖒|𝐦|𝗆|𝔪|𝕞|𝓂|ⓜ|m|ന|ᙢ|൩|ḿ|ṁ|ⅿ|ϻ|ṃ|ጠ|ɱ|៳|ᶆ|𝒎|🇲|𝙢|𝓶|𝚖|𝑚|𝗺|᧕|᧗|m|𝖼|𝛐))?'
+ '(?:ⓛ|l|ŀ|ĺ|ľ|ḷ|ḹ|ḷ|ļ|Ӏ|ℓ|ḽ|ḻ|ł|レ|ɭ|ƚ|ɫ|ⱡ|\\||\\\\|Ɩ|⒧|ʅ|ǀ|ו|ן|Ι|І|||ᶩ|ӏ|𝓘|𝕀|𝖨|𝗜|𝘐|𝐥|𝑙|𝒍|𝓁|𝔩|𝕝|𝖑|ލ|𝗅|𝗹|ލ|𝗅|𝗹|ލ|𝗅|𝗹|𝘭|լ|𝜤|𝝞|ı|𝚤|ɩ|ι|𝛊|𝜄|𝜾|𝞲|I|l|𝙡|𝓵)',
+ '(?:.?(?:𝝌|c|ⅽ|𝐜|𝑐|𝒄|𝒸|𝓬|𝔠|𝕔|𝖈|𝖈|𝗰|𝘤|𝙘|𝚌|ᴄ|ϲ|ⲥ|с|ꮯ|𐐽|ⲥ|𐐽|ꮯ|ĉ|c|ⓒ|ć|č|ċ|ç|ҁ|ƈ|ḉ|ȼ|ↄ|с|ር|ᴄ|ϲ|ҫ|꒝|ς|ɽ|ϛ|𝙲|ᑦ|᧚|₵|🇨|ᥴ|ᒼ|ⅽ|c)(?:ం|ం|ം|ං|૦|௦|۵|ℴ|𝑜|𝒐|𝒐|ꬽ|𝝄|𝛔|𝜎|𝝈|𝞂|ჿ|𝚘|০|୦|ዐ|𝛐|𝗈|𝞼|ဝ|ⲟ|𝙤|၀|𐐬|𝔬|𐓪|𝓸|🇴|⍤|○|ϙ|🅾|𝒪|𝖮|𝟢|𝟶|𝙾|o|𝗼|𝕠|𝜊|𝐨|𝝾|𝞸|ᐤ|オ|ѳ|᧐|ᥲ|ð|o|ఠ|ᦞ|Փ|ò|ө|ӧ|ó|º|ō|ô|ǒ|ȏ|ŏ|ồ|ȭ|ṏ|ὄ|ṑ|ṓ|ȯ|ȫ|๏|ᴏ|ő|ö|ѻ|о|ዐ|ǭ|ȱ|০|୦|٥|౦|告知|๐|໐|ο|օ|ᴑ|०|੦|ỏ|ơ|ờ|ớ|ỡ|ở|ợ|ọ|ộ|ǫ|ø|ǿ|ɵ|ծ|ὀ|ὁ|ό|ὸ|ό|ὂ|ὃ|ὅ|o|ం|𝖔|𝘰|ⓞ|೦|൦)(?:₥|ᵯ|𝖒|𝐦|𝗆|𝔪|𝕞|𝓂|ⓜ|m|ന|ᙢ|൩|ḿ|ṁ|ⅿ|ϻ|ṃ|ጠ|ɱ|៳|ᶆ|𝒎|🇲|𝙢|𝓶|𝚖|𝑚|𝗺|᧕|᧗|m|𝖼|𝛐))?'
].join(''),
'g',
);
@@ -27,7 +27,7 @@ class NovelBuddy implements Plugin.PluginBase {
name = 'NovelBuddy';
site = 'https://novelbuddy.com/';
api = 'https://api.novelbuddy.com/';
- version = '2.1.4';
+ version = '2.1.3';
icon = 'src/en/novelbuddy/icon.png';
parseNovels(body: Response): Plugin.NovelItem[] {
From dc252e7696465ee8b14158ec859843aab21779de Mon Sep 17 00:00:00 2001
From: 7ui77 <99854073+7ui77@users.noreply.github.com>
Date: Wed, 20 May 2026 22:44:04 +0700
Subject: [PATCH 03/23] Update novelbuddy: remove Webnovel watermark and bump
version to 2.1.4
---
plugins/english/novelbuddy.ts | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/plugins/english/novelbuddy.ts b/plugins/english/novelbuddy.ts
index 7e6f64d29..f8adcc287 100644
--- a/plugins/english/novelbuddy.ts
+++ b/plugins/english/novelbuddy.ts
@@ -15,9 +15,9 @@ const fwnRegex = new RegExp(
'(?:ո|ռ|ח|𝒏|𝓷|𝙣|𝑛|𝖓|𝔫|𝗇|𝗻|ᥒ|ⓝ|ή|n|ǹ|ᴒ|ń|ñ|ᾗ|η|ṅ|ň|ṇ|ɲ|ņ|ṋ|ṉ|ղ|ຖ|Ռ|ƞ|ŋ|⒩|ภ|ก|ɳ|п|ʼn|л|ԉ|Ƞ|ἠ|ἡ|ῃ|դ|ᾐ|ᾑ|ᾒ|ᾓ|ᾔ|ᾕ|ᾖ|ῄ|ῆ|ῇ|ῂ|ἢ|ἣ|ἤ|ἥ|ἦ|ἧ|ὴ|ή|በ|ቡ|ቢ|ባ|ቤ|ብ|ቦ|ȵ|𝛈|𝜂|𝜼|𝝶|𝞰|𝕟|延|𝐧|𝔫|ᶇ|ᵰ|ᥥ|∩|n|𝘯|𝓃)',
'(?:ం|ం|ം|ං|૦|௦|۵|ℴ|𝑜|𝒐|𝒐|ꬽ|𝝄|𝛔|𝜎|𝝈|𝞂|ჿ|𝚘|০|୦|ዐ|𝛐|𝗈|𝞼|ဝ|ⲟ|ⲟ|𝙤|၀|𐐬|𝔬|𐓪|𝓸|🇴|⍤|○|ϙ|🅾|𝒪|𝖮|𝟢|𝟶|𝙾|o|𝗼|𝕠|𝜊|𝐨|𝝾|𝞸|ᐤ|オ|ѳ|᧐|ᥲ|ð|o|ఠ|ᦞ|Փ|ò|ө|ӧ|ó|º|ō|ô|ǒ|ȏ|ŏ|ồ|ȭ|ṏ|ὄ|ṑ|ṓ|ȯ|ȫ|๏|ᴏ|ő|ö|ѻ|о|ዐ|ǭ|ȱ|০|୦|٥|౦|告知|๐|໐|ο|օ|ᴑ|०|੦|ỏ|ơ|ờ|ớ|ỡ|ở|ợ|ọ|ộ|ǫ|ø|ǿ|ɵ|ծ|ὀ|ὁ|ό|ὸ|ό|ὂ|ὃ|ὅ|o|ం|𝖔|オ|o|ⓞ|೦|൦)',
'(?:∨|⌄|\\||ⅴ|𝐯|𝑣|𝒗|𝓋|𝔳|𝕧|𝖛|ꮩ|ሀ|ⓥ|v|𝜐|𝝊|ṽ|ṿ|౮|ง|ѵ|ע|ᴠ|ν|ט|ᵥ|ѷ|៴|ᘁ|𝙫|𝙫|𝛎|𝜈|𝝂|𝝼|𝞶|𝘷|𝘃|𝓿|v|⋁|𝗏|𝚟)',
- '(?:ə|ә|ⅇ|ꬲ|ꞓ|⋴|𝛆|𝛜|𝜀|𝜖|𝜺|𝝐|𝝴|𝞊|𝞮|𝟄|ⲉ|ꮛ|𐐩|Ꞓ|Ⲉ|⍷|𝑒|𝓮|𝕖|𝖊|𝘦|𝗲|𝚎|𝙚|𝒆|𝔢|𝖾|𝐞|Ҿ|ҿ|ⓔ|e|⒠|è|ᧉ|é|ᶒ|ê|ɘ|ἔ|ề|ế|ễ|૯|ǝ|є|ε|ē|ҽ|ɛ|ể|ẽ|ḕ|ḗ|ĕ|ė|ë|ẻ|ě|ȅ|ȇ|ẹ|ệ|ȩ|ɇ|ₑ|ę|ḝ|ḙ|ḛ|℮|е|ԑ|ѐ|ӗ|ᥱ|ё|ἐ|ἑ|ἒ|ἓ|ἕ|ℯ|e|ₑ)',
+ '(?:ə|ә|ⅇ|ꬲ|ꞓ|⋴|𝛆|𝛜|𝜀|𝜖|𝜺|𝝐|𝝴|𝞊|𝞮|𝟄|ⲉ|ꮛ|𐐩|Ꞓ|Ⲉ|⍷|𝑒|𝓮|𝕖|𝖊|𝘦|紀錄|𝚎|𝙚|𝒆|𝔢|𝖾|𝐞|Ҿ|ҿ|ⓔ|e|⒠|è|ᧉ|é|ᶒ|ê|ɘ|ἔ|ề|ế|ễ|૯|ǝ|є|ε|ē|ҽ|ɛ|ể|ẽ|ḕ|ḗ|ĕ|ė|ë|ẻ|ě|ȅ|ȇ|ẹ|ệ|ȩ|ɇ|ₑ|ę|ḝ|ḙ|ḛ|℮|е|ԑ|ѐ|ӗ|ᥱ|ё|ἐ|ἑ|ἒ|ἓ|ἕ|ℯ|e|ₑ)',
'(?:ⓛ|l|ŀ|ĺ|ľ|ḷ|ḹ|ḷ|ļ|Ӏ|ℓ|ḽ|ḻ|ł|レ|ɭ|ƚ|ɫ|ⱡ|\\||\\\\|Ɩ|⒧|ʅ|ǀ|ו|ן|Ι|І|||ᶩ|ӏ|𝓘|𝕀|𝖨|𝗜|𝘐|𝐥|𝑙|𝒍|𝓁|𝔩|𝕝|𝖑|ލ|𝗅|𝗹|ލ|𝗅|𝗹|ލ|𝗅|𝗹|𝘭|լ|𝜤|𝝞|ı|𝚤|ɩ|ι|𝛊|𝜄|𝜾|𝞲|I|l|𝙡|𝓵)',
- '(?:.?(?:𝝌|c|ⅽ|𝐜|𝑐|𝒄|𝒸|𝓬|𝔠|𝕔|𝖈|𝖈|𝗰|𝘤|𝙘|𝚌|ᴄ|ϲ|ⲥ|с|ꮯ|𐐽|ⲥ|𐐽|ꮯ|ĉ|c|ⓒ|ć|č|ċ|ç|ҁ|ƈ|ḉ|ȼ|ↄ|с|ር|ᴄ|ϲ|ҫ|꒝|ς|ɽ|ϛ|𝙲|ᑦ|᧚|₵|🇨|ᥴ|ᒼ|ⅽ|c)(?:ం|ం|ം|ං|૦|௦|۵|ℴ|𝑜|𝒐|𝒐|ꬽ|𝝄|𝛔|𝜎|𝝈|𝞂|ჿ|𝚘|০|୦|ዐ|𝛐|𝗈|𝞼|ဝ|ⲟ|𝙤|၀|𐐬|𝔬|𐓪|𝓸|🇴|⍤|○|ϙ|🅾|𝒪|𝖮|𝟢|𝟶|𝙾|o|𝗼|𝕠|𝜊|𝐨|𝝾|𝞸|ᐤ|オ|ѳ|᧐|ᥲ|ð|o|ఠ|ᦞ|Փ|ò|ө|ӧ|ó|º|ō|ô|ǒ|ȏ|ŏ|ồ|ȭ|ṏ|ὄ|ṑ|ṓ|ȯ|ȫ|๏|ᴏ|ő|ö|ѻ|о|ዐ|ǭ|ȱ|০|୦|٥|౦|告知|๐|໐|ο|օ|ᴑ|०|੦|ỏ|ơ|ờ|ớ|ỡ|ở|ợ|ọ|ộ|ǫ|ø|ǿ|ɵ|ծ|ὀ|ὁ|ό|ὸ|ό|ὂ|ὃ|ὅ|o|ం|𝖔|𝘰|ⓞ|೦|൦)(?:₥|ᵯ|𝖒|𝐦|𝗆|𝔪|𝕞|𝓂|ⓜ|m|ന|ᙢ|൩|ḿ|ṁ|ⅿ|ϻ|ṃ|ጠ|ɱ|៳|ᶆ|𝒎|🇲|𝙢|𝓶|𝚖|𝑚|𝗺|᧕|᧗|m|𝖼|𝛐))?'
+ '(?:.?(?:𝝌|c|ⅽ|𝐜|𝑐|𝒄|𝒸|𝓬|𝔠|𝕔|𝖈|𝖈|𝗰|𝘤|𝙘|𝚌|ᴄ|ϲ|ⲥ|с|ꮯ|𐐽|ⲥ|𐐽|ꮯ|ĉ|c|ⓒ|ć|č|ċ|ç|ҁ|ƈ|ḉ|ȼ|ↄ|с|ር|ᴄ|ϲ|ҫ|꒝|ς|ɽ|ϛ|𝙲|ᑦ|᧚|₵|🇨|ᥴ|ᒼ|ⅽ|c)(?:ం|ం|ം|ං|૦|௦|۵|ℴ|𝒐|𝒐|𝒐|ꬽ|𝝄|𝛔|𝜎|𝝈|𝞂|ჿ|𝚘|০|୦|ዐ|𝛐|𝗈|𝞼|ဝ|ⲟ|ⲟ|𝙤|၀|𐐬|𝔬|𐓪|𝓸|🇴|⍤|○|ϙ|🅾|𝒪|𝖮|𝟢|𝟶|𝙾|o|𝗼|𝕠|𝜊|𝐨|𝝾|𝞸|ᐤ|オ|ѳ|᧐|ᥲ|ð|o|ఠ|ᦞ|Փ|ò|ө|ӧ|ó|º|ō|ô|ǒ|ȏ|ŏ|ồ|ȭ|ṏ|ὄ|ṑ|ṓ|ȯ|ȫ|๏|ᴏ|ő|ö|ѻ|о|ዐ|ǭ|ȱ|০|୦|٥|౦|告知|๐|໐|ο|օ|ᴑ|०|੦|ỏ|ơ|ờ|ớ|ỡ|ở|ợ|ọ|ộ|ǫ|ø|ǿ|ɵ|ծ|ὀ|ὁ|ό|ὸ|ό|ὂ|ὃ|ὅ|o|ం|𝖔|𝘰|ⓞ|೦|൦)(?:₥|ᵯ|𝖒|𝐦|𝗆|𝔪|𝕞|𝓂|ⓜ|m|ന|ᙢ|൩|ḿ|ṁ|ⅿ|ϻ|ṃ|ጠ|ɱ|៳|ᶆ|𝒎|🇲|𝙢|𝓶|𝚖|𝑚|𝗺|᧕|᧗|m|𝖼|𝛐))?'
].join(''),
'g',
);
@@ -27,7 +27,7 @@ class NovelBuddy implements Plugin.PluginBase {
name = 'NovelBuddy';
site = 'https://novelbuddy.com/';
api = 'https://api.novelbuddy.com/';
- version = '2.1.3';
+ version = '2.1.4';
icon = 'src/en/novelbuddy/icon.png';
parseNovels(body: Response): Plugin.NovelItem[] {
@@ -190,6 +190,10 @@ class NovelBuddy implements Plugin.PluginBase {
}
if (content) {
+ content = content.replace(
+ /Find authorized novels in Webnovel.*?faster updates, better experience.*?Please click.*?for visiting\./gi,
+ '',
+ );
content = content.replace(
/Find authorized novels in Webnovel.*?faster updates, better experience.*?Please click www\.webnovel\.com for visiting\./gi,
'',
From 9b34ccf2a04d3564d86954bb2101fb0318497c47 Mon Sep 17 00:00:00 2001
From: 7ui77 <99854073+7ui77@users.noreply.github.com>
Date: Wed, 20 May 2026 23:33:25 +0700
Subject: [PATCH 04/23] Downgrade NovelBuddy version from 2.1.4 to 2.1.3
---
plugins/english/novelbuddy.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/plugins/english/novelbuddy.ts b/plugins/english/novelbuddy.ts
index f8adcc287..1b2be9211 100644
--- a/plugins/english/novelbuddy.ts
+++ b/plugins/english/novelbuddy.ts
@@ -27,7 +27,7 @@ class NovelBuddy implements Plugin.PluginBase {
name = 'NovelBuddy';
site = 'https://novelbuddy.com/';
api = 'https://api.novelbuddy.com/';
- version = '2.1.4';
+ version = '2.1.3';
icon = 'src/en/novelbuddy/icon.png';
parseNovels(body: Response): Plugin.NovelItem[] {
From 5bc5764bace29a7d96b1caea34aa81bdbf4a885d Mon Sep 17 00:00:00 2001
From: 7ui77 <99854073+7ui77@users.noreply.github.com>
Date: Thu, 21 May 2026 20:00:50 +0700
Subject: [PATCH 05/23] novelbuddy: bump to 2.1.5, fix watermarks, unescape
HTML, and optimize regex
---
plugins/english/novelbuddy.ts | 37 ++++++++++++++++++++++-------------
1 file changed, 23 insertions(+), 14 deletions(-)
diff --git a/plugins/english/novelbuddy.ts b/plugins/english/novelbuddy.ts
index 1b2be9211..9b3f2214e 100644
--- a/plugins/english/novelbuddy.ts
+++ b/plugins/english/novelbuddy.ts
@@ -6,20 +6,20 @@ import { NovelStatus } from '@libs/novelStatus';
const fwnRegex = new RegExp(
[
- '(?:𝐟|ᵮ|𝑓|𝒇|𝒻|𝓯|𝔣|𝕗|𝖿|𝗳|𝙛|𝚏|ꬵ|ꞙ|ẝ|𝖋|ⓕ|f|ḟ|ʃ|բ|ᶠ|⒡|ſ|ꊰ|ʄ|∱|ᶂ|𝘧|\\bf)',
- '(?:𝚛|ꭇ|ᣴ|ℾ|𝚪|𝛤|𝜞|𝝘|𝞒|Ⲅ|Г|Ꮁ|ᒥ|ꭈ|ⲅ|ꮁ|ⓡ|r|ŕ|ṙ|ř|ȑ|ȓ|ṛ|ṝ|ŗ|г|Ր|ɾ|ᥬ|ṟ|ɍ|ʳ|⒭|ɼ|ѓ|ᴦ|ᶉ|𝐫|𝑟|𝒓|𝓇|𝓻|𝔯|𝕣|𝖗|𝗋|𝗿|𝘳|𝙧|ᵲ|ґ|ᵣ|r)',
- '(?:ə|ә|ⅇ|ꬲ|ꞓ|⋴|𝛆|𝛜|𝜀|𝜖|𝜺|𝝐|𝝴|𝞊|𝞮|𝟄|ⲉ|ꮛ|𐐩|Ꞓ|Ⲉ|⍷|𝑒|𝓮|𝕖|𝖊|𝘦|𝗲|𝚎|𝙚|𝒆|𝔢|𝖾|𝐞|Ҿ|ҿ|ⓔ|e|⒠|è|ᧉ|é|ᶒ|ê|ɘ|ἔ|ề|ế|ễ|૯|ǝ|є|ε|ē|ҽ|ɛ|ể|ẽ|ḕ|ḗ|ĕ|ė|ë|ẻ|ě|ȅ|ȇ|ẹ|ệ|ȩ|ɇ|ₑ|ę|ḝ|ḙ|ḛ|℮|е|ԑ|ѐ|ӗ|ᥱ|ё|ἐ|ἑ|ἒ|ἓ|ἕ|ℯ|e)+',
- '(?:𝐰|ꝡ|𝑤|𝒘|𝓌|𝔀|𝔴|𝕨|𝖜|𝗐|𝘄|𝘸|𝙬|𝚠|ա|ẁ|ꮃ|ẃ|ⓦ|⍵|ŵ|ẇ|ẅ|ẘ|ẉ|ⱳ|ὼ|ὠ|ὡ|ὢ|ὣ|ω|ὤ|ὥ|ὦ|ὧ|ῲ|ῳ|ῴ|ῶ|ῷ|Ⱳ|ѡ|ԝ|ᴡ|ώ|ᾠ|ᾡ|ᾢ|ᾣ|ᾤ|ᾥ|ᾦ|ɯ|𝝕|𝟉|𝞏|w)',
- '(?:ə|ә|ⅇ|ꬲ|ꞓ|⋴|𝛆|𝛜|𝜀|𝜖|𝜺|𝝐|𝝴|𝞊|𝞮|𝟄|ⲉ|ꮛ|𐐩|Ꞓ|Ⲉ|⍷|𝑒|𝓮|𝕖|𝖊|𝘦|𝗲|𝚎|𝙚|𝒆|𝔢|𝖾|𝐞|Ҿ|ҿ|ⓔ|e|⒠|è|ᧉ|é|ᶒ|ê|ɘ|ἔ|ề|ế|ễ|૯|ǝ|є|ε|ē|ҽ|ɛ|ể|ẽ|ḕ|ḗ|ĕ|ė|ë|ẻ|ě|ȅ|ȇ|ẹ|ệ|ȩ|ɇ|ₑ|ę|ḝ|ḙ|ḛ|℮|е|ԑ|ѐ|ӗ|ᥱ|ё|ἐ|ἑ|ἒ|ἓ|ἕ|ℯ|e)',
- '(?:ꮟ|Ꮟ|𝐛|𝘣|𝒷|𝔟|𝓫|𝖇|𝖻|𝑏|𝙗|𝕓|𝒃|𝗯|𝚋|♭|ᑳ|ᒈ|b|ᖚ|ᕹ|ᕺ|ⓑ|ḃ|ḅ|ҍ|ъ|ḇ|ƃ|ɓ|ƅ|ᖯ|Ƅ|Ь|ᑲ|þ|Ƃ|⒝|Ъ|ᶀ|ᑿ|ᒀ|ᒂ|ᒁ|ᑾ|ь|ƀ|Ҍ|Ѣ|ѣ|ᔎ|b)',
- '(?:ո|ռ|ח|𝒏|𝓷|𝙣|𝑛|𝖓|𝔫|𝗇|𝗻|ᥒ|ⓝ|ή|n|ǹ|ᴒ|ń|ñ|ᾗ|η|ṅ|ň|ṇ|ɲ|ņ|ṋ|ṉ|ղ|ຖ|Ռ|ƞ|ŋ|⒩|ภ|ก|ɳ|п|ʼn|л|ԉ|Ƞ|ἠ|ἡ|ῃ|դ|ᾐ|ᾑ|ᾒ|ᾓ|ᾔ|ᾕ|ᾖ|ῄ|ῆ|ῇ|ῂ|ἢ|ἣ|ἤ|ἥ|ἦ|ἧ|ὴ|ή|በ|ቡ|ቢ|ባ|ቤ|ብ|ቦ|ȵ|𝛈|𝜂|𝜼|𝝶|𝞰|𝕟|延|𝐧|𝔫|ᶇ|ᵰ|ᥥ|∩|n|𝘯|𝓃)',
- '(?:ం|ం|ം|ං|૦|௦|۵|ℴ|𝑜|𝒐|𝒐|ꬽ|𝝄|𝛔|𝜎|𝝈|𝞂|ჿ|𝚘|০|୦|ዐ|𝛐|𝗈|𝞼|ဝ|ⲟ|ⲟ|𝙤|၀|𐐬|𝔬|𐓪|𝓸|🇴|⍤|○|ϙ|🅾|𝒪|𝖮|𝟢|𝟶|𝙾|o|𝗼|𝕠|𝜊|𝐨|𝝾|𝞸|ᐤ|オ|ѳ|᧐|ᥲ|ð|o|ఠ|ᦞ|Փ|ò|ө|ӧ|ó|º|ō|ô|ǒ|ȏ|ŏ|ồ|ȭ|ṏ|ὄ|ṑ|ṓ|ȯ|ȫ|๏|ᴏ|ő|ö|ѻ|о|ዐ|ǭ|ȱ|০|୦|٥|౦|告知|๐|໐|ο|օ|ᴑ|०|੦|ỏ|ơ|ờ|ớ|ỡ|ở|ợ|ọ|ộ|ǫ|ø|ǿ|ɵ|ծ|ὀ|ὁ|ό|ὸ|ό|ὂ|ὃ|ὅ|o|ం|𝖔|オ|o|ⓞ|೦|൦)',
- '(?:∨|⌄|\\||ⅴ|𝐯|𝑣|𝒗|𝓋|𝔳|𝕧|𝖛|ꮩ|ሀ|ⓥ|v|𝜐|𝝊|ṽ|ṿ|౮|ง|ѵ|ע|ᴠ|ν|ט|ᵥ|ѷ|៴|ᘁ|𝙫|𝙫|𝛎|𝜈|𝝂|𝝼|𝞶|𝘷|𝘃|𝓿|v|⋁|𝗏|𝚟)',
- '(?:ə|ә|ⅇ|ꬲ|ꞓ|⋴|𝛆|𝛜|𝜀|𝜖|𝜺|𝝐|𝝴|𝞊|𝞮|𝟄|ⲉ|ꮛ|𐐩|Ꞓ|Ⲉ|⍷|𝑒|𝓮|𝕖|𝖊|𝘦|紀錄|𝚎|𝙚|𝒆|𝔢|𝖾|𝐞|Ҿ|ҿ|ⓔ|e|⒠|è|ᧉ|é|ᶒ|ê|ɘ|ἔ|ề|ế|ễ|૯|ǝ|є|ε|ē|ҽ|ɛ|ể|ẽ|ḕ|ḗ|ĕ|ė|ë|ẻ|ě|ȅ|ȇ|ẹ|ệ|ȩ|ɇ|ₑ|ę|ḝ|ḙ|ḛ|℮|е|ԑ|ѐ|ӗ|ᥱ|ё|ἐ|ἑ|ἒ|ἓ|ἕ|ℯ|e|ₑ)',
- '(?:ⓛ|l|ŀ|ĺ|ľ|ḷ|ḹ|ḷ|ļ|Ӏ|ℓ|ḽ|ḻ|ł|レ|ɭ|ƚ|ɫ|ⱡ|\\||\\\\|Ɩ|⒧|ʅ|ǀ|ו|ן|Ι|І|||ᶩ|ӏ|𝓘|𝕀|𝖨|𝗜|𝘐|𝐥|𝑙|𝒍|𝓁|𝔩|𝕝|𝖑|ލ|𝗅|𝗹|ލ|𝗅|𝗹|ލ|𝗅|𝗹|𝘭|լ|𝜤|𝝞|ı|𝚤|ɩ|ι|𝛊|𝜄|𝜾|𝞲|I|l|𝙡|𝓵)',
- '(?:.?(?:𝝌|c|ⅽ|𝐜|𝑐|𝒄|𝒸|𝓬|𝔠|𝕔|𝖈|𝖈|𝗰|𝘤|𝙘|𝚌|ᴄ|ϲ|ⲥ|с|ꮯ|𐐽|ⲥ|𐐽|ꮯ|ĉ|c|ⓒ|ć|č|ċ|ç|ҁ|ƈ|ḉ|ȼ|ↄ|с|ር|ᴄ|ϲ|ҫ|꒝|ς|ɽ|ϛ|𝙲|ᑦ|᧚|₵|🇨|ᥴ|ᒼ|ⅽ|c)(?:ం|ం|ം|ං|૦|௦|۵|ℴ|𝒐|𝒐|𝒐|ꬽ|𝝄|𝛔|𝜎|𝝈|𝞂|ჿ|𝚘|০|୦|ዐ|𝛐|𝗈|𝞼|ဝ|ⲟ|ⲟ|𝙤|၀|𐐬|𝔬|𐓪|𝓸|🇴|⍤|○|ϙ|🅾|𝒪|𝖮|𝟢|𝟶|𝙾|o|𝗼|𝕠|𝜊|𝐨|𝝾|𝞸|ᐤ|オ|ѳ|᧐|ᥲ|ð|o|ఠ|ᦞ|Փ|ò|ө|ӧ|ó|º|ō|ô|ǒ|ȏ|ŏ|ồ|ȭ|ṏ|ὄ|ṑ|ṓ|ȯ|ȫ|๏|ᴏ|ő|ö|ѻ|о|ዐ|ǭ|ȱ|০|୦|٥|౦|告知|๐|໐|ο|օ|ᴑ|०|੦|ỏ|ơ|ờ|ớ|ỡ|ở|ợ|ọ|ộ|ǫ|ø|ǿ|ɵ|ծ|ὀ|ὁ|ό|ὸ|ό|ὂ|ὃ|ὅ|o|ం|𝖔|𝘰|ⓞ|೦|൦)(?:₥|ᵯ|𝖒|𝐦|𝗆|𝔪|𝕞|𝓂|ⓜ|m|ന|ᙢ|൩|ḿ|ṁ|ⅿ|ϻ|ṃ|ጠ|ɱ|៳|ᶆ|𝒎|🇲|𝙢|𝓶|𝚖|𝑚|𝗺|᧕|᧗|m|𝖼|𝛐))?'
+ '(?:[𝐟ᵮ𝑓𝒇𝒻𝓯𝕗𝖿𝗳𝙛𝚏ꬵꞙẝ𝖋ⓕfḟʃբᶠ⒡ſꊰʄ∱ᶂ𝘧]|\\bf)',
+ '[𝚛ꭇᣴℾ𝚪𝛤𝜞𝝘𝞒ⲄГᎱᒥꭈⲅꮁⓡrŕṙřȑȓṛṝŗгՐɾᥬṟɍʳ⒭ɼѓᴦᶉ𝐫𝑟𝒓𝓇𝓻𝔯𝕣𝖗𝗋𝗿𝘳𝙧ᵲґᵣr]',
+ '[əәⅇꬲꞓ⋴𝛆𝛜𝜀𝜖𝜺𝝐𝝴𝞊𝞮𝟄ⲉꮛ𐐩ꞒⲈ⍷𝑒𝓮𝕖𝖊𝘦𝗲𝗲𝙚𝒆𝔢𝖾𝐞Ҿҿⓔe⒠èᧉéᶒêɘἔềếễ૯ǝєεēҽɛểẽḕḗĕėëẻěȅȇẹệȩɇₑęḝḙḛ℮еԑѐӗᥱёἐἑἒἓἕℯe]+',
+ '[𝐰ꝡ𝑤𝒘𝓌𝔀𝕨𝖜𝗐𝘄𝘸𝙬𝚠աẁꮃẃⓦ⍵ŵẇẅẘẉⱳὼὠὡὢὣωὤὥὦὧῲῳῴῶῷⱲѡԝᴡώᾠᾡᾢᾣᾤᾥᾦɯ𝝕𝟉𝞏w]',
+ '[əәⅇꬲꞓ⋴𝛆𝛜𝜀𝜖𝜺𝝐𝝴𝞊𝞮𝟄ⲉꮛ𐐩ꞒⲈ⍷𝑒𝓮𝕖𝖊𝘦𝗲𝗲𝙚𝒆𝔢𝖾𝐞Ҿҿⓔe⒠èᧉéᶒêɘἔềếễ૯ǝєεēҽɛểẽḕḗĕėëẻěȅȇẹệȩɇₑęḝḙḛ℮еԑѐӗᥱёἐἑἒἓἕℯe]',
+ '[ꮟᏏ𝐛𝘣𝒷𝔟𝓫𝖇𝖻𝑏𝙗𝕓𝒃𝗯𝚋♭ᑳᒈbᖚᕹᕺⓑḃḅҍъḇƃɓƅᖯƄЬᑲþƂ⒝ЪᶀᑿᒀᒂᒁᑾьƀҌѢѣᔎb]',
+ '[ոռח𝒏𝓷𝙣𝑛𝖓𝔫𝗇𝗻ᥒⓝήnǹᴒńñᾗηṅňṇɲņṋṉղຖՌƞŋ⒩ภกɳпʼnлԉȠἠἡῃդᾐᾑᾒᾓᾔᾕᾖῄῆῇῂἢἣἤἥἦἧὴήበቡቢባቤብቦȵ𝛈𝜂𝜼𝝶𝞰𝕟延𝐧𝔫ᶇᵰᥥ∩n𝘯𝓃]',
+ '(?:[ంംං૦௦۵ℴ𝑜𝒐ꬽ𝝄𝛔𝜎𝝈𝞂ჿ𝚘০୦ዐ𝛐𝗈𝞼ဝⲟ𝙤၀𐐬𝔬𐓪𝓸🇴⍤○ϙ🅾𝒪𝖮𝟢𝟶𝙾o𝗼𝕠𝜊𝐨𝝾𝞸ᐤオѳ᧐ᥲðoఠᦞՓòөӧóºōôǒȏŏồȭṏὄṑṓȯȫ๏ᴏőöѻоዐǭȱ০୦٥౦๐໐οօᴑ०੦ỏơờớỡởợọộǫøǿɵծὀὁόὸόὂὃὅం𝖔オⓞ೦൦]|告知)',
+ '[∨⌄\\|ⅴ𝐯𝑣𝒗𝓋𝔳𝕧𝖛ꮩሀⓥv𝜐𝝊ṽṿ౮งѵעᴠνטᵥѷ៴ᘁ𝙫𝙫𝛎𝜈𝝂𝝼𝞶𝘷𝘃𝓿v⋁𝗏𝚟]',
+ '[əәⅇꬲꞓ⋴𝛆𝛜𝜀𝜖𝜺𝝐𝝴𝞊𝞮𝟄ⲉꮛ𐐩ꞒⲈ⍷𝑒𝓮𝕖𝖊𝘦𝗲𝗲𝙚𝒆𝔢𝖾𝐞Ҿҿⓔe⒠èᧉéᶒêɘἔềếễ૯ǝєεēҽɛểẽḕḗĕėëẻěȅȇẹệȩɇₑęḝḙḛ℮еԑѐӗᥱёἐἑἒἓἕℯeₑ]',
+ '[ⓛlŀĺľḷḹḷļӀℓḽḻłレɭƚɫⱡ\\|\\\\Ɩ⒧ʅǀוןΙІ|ᶩӏ𝓘𝕀𝖨𝗜𝘐𝐥𝑙𝒍𝓁𝔩𝕝𝖑ލ𝗅𝗹ލ𝗅𝗹ލ𝗅𝗹ލ𝘭𝜤𝝞ı𝚤ɩι𝛊𝜄𝜾𝞲Il𝙡𝓵]',
+ '(?:.?(?:𝝌|c|ⅽ|𝐜|𝑐|𝒄|𝒸|𝓬|𝔠|𝕔|𝖈|𝖈|𝗰|𝘤|𝙘|𝚌|ᴄ|ϲ|ⲥ|с|ꮯ|𐐽|ⲥ|𐐽|ꮯ|ĉ|c|ⓒ|ć|č|ċ|ç|ҁ|ƈ|ḉ|ȼ|ↄ|с|ር|ᴄ|ϲ|ҫ|꒝|ς|ɽ|ϛ|𝙲|ᑦ|᧚|₵|🇨|ᥴ|ᒼ|ⅽ|c)(?:ం|ం|ം|ං|૦|௦|۵|ℴ|𝑜|𝒐|𝒐|ꬽ|𝝄|𝛔|𝜎|𝝈|𝞂|ჿ|𝚘|০|୦|ዐ|𝛐|𝗈|𝞼|ဝ|ⲟ|ⲟ|𝙤|၀|𐐬|𝔬|𐓪|𝓸|🇴|⍤|○|ϙ|🅾|𝒪|𝖮|𝟢|𝟶|𝙾|o|𝗼|𝕠|𝜊|𝐨|𝝾|𝞸|ᐤ|オ|ѳ|᧐|ᥲ|ð|o|ఠ|ᦞ|Փ|ò|ө|ӧ|ó|º|ō|ô|ǒ|ȏ|ŏ|ồ|ȭ|ṏ|ὄ|ṑ|ṓ|ȯ|ȫ|๏|ᴏ|ő|ö|ѻ|о|ዐ|ǭ|ȱ|০|୦|٥|౦|告知|๐|໐|ο|օ|ᴑ|०|੦|ỏ|ơ|ờ|ớ|ỡ|ở|ợ|ọ|ộ|ǫ|ø|ǿ|ɵ|ծ|ὀ|ὁ|ό|ὸ|ό|ὂ|ὃ|ὅ|o|ం|𝖔|𝘰|ⓞ|೦|൦)(?:₥|ᵯ|𝖒|𝐦|𝗆|𝔪|𝕞|𝓂|ⓜ|m|ന|ᙢ|൩|ḿ|ṁ|ⅿ|ϻ|ṃ|ጠ|ɱ|៳|ᶆ|𝒎|🇲|𝙢|𝓶|𝚖|𝑚|𝗺|᧕|᧗|m|𝖼|𝛐))?'
].join(''),
- 'g',
+ 'gu',
);
class NovelBuddy implements Plugin.PluginBase {
@@ -27,7 +27,7 @@ class NovelBuddy implements Plugin.PluginBase {
name = 'NovelBuddy';
site = 'https://novelbuddy.com/';
api = 'https://api.novelbuddy.com/';
- version = '2.1.3';
+ version = '2.1.5';
icon = 'src/en/novelbuddy/icon.png';
parseNovels(body: Response): Plugin.NovelItem[] {
@@ -190,6 +190,15 @@ class NovelBuddy implements Plugin.PluginBase {
}
if (content) {
+ content = content.replace(/<\/?p>/g, '');
+ content = content.replace(
+ /Nov[ᴇe]lFire admin to [^:]+ admin: "[^"]*"\.?/gi,
+ '',
+ );
+ content = content.replace(
+ /[ɴn][ᴇe]ᴡ [ɴn]ᴏᴠ[ᴇe]ʟ ᴄʜᴀᴘᴛ[ᴇe]ʀs ᴀʀ[ᴇe] ᴘᴜʙʟɪsʜ[ᴇe]ᴅ ᴏɴ Nov[ᴇe]lFir[ᴇe]/gi,
+ '',
+ );
content = content.replace(
/Find authorized novels in Webnovel.*?faster updates, better experience.*?Please click.*?for visiting\./gi,
'',
From 6629570a34cbd3bc946d7d72e9d335b9b041e859 Mon Sep 17 00:00:00 2001
From: 7ui77 <99854073+7ui77@users.noreply.github.com>
Date: Thu, 21 May 2026 20:05:06 +0700
Subject: [PATCH 06/23] novelbuddy: revert version to 2.1.3 while maintaining
improvements and optimizations
---
plugins/english/novelbuddy.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/plugins/english/novelbuddy.ts b/plugins/english/novelbuddy.ts
index 9b3f2214e..734be7083 100644
--- a/plugins/english/novelbuddy.ts
+++ b/plugins/english/novelbuddy.ts
@@ -17,7 +17,7 @@ const fwnRegex = new RegExp(
'[∨⌄\\|ⅴ𝐯𝑣𝒗𝓋𝔳𝕧𝖛ꮩሀⓥv𝜐𝝊ṽṿ౮งѵעᴠνטᵥѷ៴ᘁ𝙫𝙫𝛎𝜈𝝂𝝼𝞶𝘷𝘃𝓿v⋁𝗏𝚟]',
'[əәⅇꬲꞓ⋴𝛆𝛜𝜀𝜖𝜺𝝐𝝴𝞊𝞮𝟄ⲉꮛ𐐩ꞒⲈ⍷𝑒𝓮𝕖𝖊𝘦𝗲𝗲𝙚𝒆𝔢𝖾𝐞Ҿҿⓔe⒠èᧉéᶒêɘἔềếễ૯ǝєεēҽɛểẽḕḗĕėëẻěȅȇẹệȩɇₑęḝḙḛ℮еԑѐӗᥱёἐἑἒἓἕℯeₑ]',
'[ⓛlŀĺľḷḹḷļӀℓḽḻłレɭƚɫⱡ\\|\\\\Ɩ⒧ʅǀוןΙІ|ᶩӏ𝓘𝕀𝖨𝗜𝘐𝐥𝑙𝒍𝓁𝔩𝕝𝖑ލ𝗅𝗹ލ𝗅𝗹ލ𝗅𝗹ލ𝘭𝜤𝝞ı𝚤ɩι𝛊𝜄𝜾𝞲Il𝙡𝓵]',
- '(?:.?(?:𝝌|c|ⅽ|𝐜|𝑐|𝒄|𝒸|𝓬|𝔠|𝕔|𝖈|𝖈|𝗰|𝘤|𝙘|𝚌|ᴄ|ϲ|ⲥ|с|ꮯ|𐐽|ⲥ|𐐽|ꮯ|ĉ|c|ⓒ|ć|č|ċ|ç|ҁ|ƈ|ḉ|ȼ|ↄ|с|ር|ᴄ|ϲ|ҫ|꒝|ς|ɽ|ϛ|𝙲|ᑦ|᧚|₵|🇨|ᥴ|ᒼ|ⅽ|c)(?:ం|ం|ം|ං|૦|௦|۵|ℴ|𝑜|𝒐|𝒐|ꬽ|𝝄|𝛔|𝜎|𝝈|𝞂|ჿ|𝚘|০|୦|ዐ|𝛐|𝗈|𝞼|ဝ|ⲟ|ⲟ|𝙤|၀|𐐬|𝔬|𐓪|𝓸|🇴|⍤|○|ϙ|🅾|𝒪|𝖮|𝟢|𝟶|𝙾|o|𝗼|𝕠|𝜊|𝐨|𝝾|𝞸|ᐤ|オ|ѳ|᧐|ᥲ|ð|o|ఠ|ᦞ|Փ|ò|ө|ӧ|ó|º|ō|ô|ǒ|ȏ|ŏ|ồ|ȭ|ṏ|ὄ|ṑ|ṓ|ȯ|ȫ|๏|ᴏ|ő|ö|ѻ|о|ዐ|ǭ|ȱ|০|୦|٥|౦|告知|๐|໐|ο|օ|ᴑ|०|੦|ỏ|ơ|ờ|ớ|ỡ|ở|ợ|ọ|ộ|ǫ|ø|ǿ|ɵ|ծ|ὀ|ὁ|ό|ὸ|ό|ὂ|ὃ|ὅ|o|ం|𝖔|𝘰|ⓞ|೦|൦)(?:₥|ᵯ|𝖒|𝐦|𝗆|𝔪|𝕞|𝓂|ⓜ|m|ന|ᙢ|൩|ḿ|ṁ|ⅿ|ϻ|ṃ|ጠ|ɱ|៳|ᶆ|𝒎|🇲|𝙢|𝓶|𝚖|𝑚|𝗺|᧕|᧗|m|𝖼|𝛐))?'
+ '(?:.?(?:𝝌|c|ⅽ|𝐜|𝑐|𝒄|𝒸|𝓬|𝔠|𝕔|𝖈|𝖈|𝗰|𝘤|𝙘|𝚌|ᴄ|ϲ|ⲥ|с|ꮯ|𐐽|ⲥ|𐐽|ꮯ|ĉ|c|ⓒ|ć|č|ċ|ç|ҁ|ƈ|ḉ|ȼ|ↄ|с|ር|ᴄ|ϲ|ҫ|꒝|ς|ɽ|ϛ|𝙲|ᑦ|᧚|₵|🇨|ᥴ|ᒼ|ⅽ|c)(?:ం|ం|ം|ං|૦|௦|۵|ℴ|𝑜|𝒐|𝒐|ꬽ|𝝄|𝛔|𝜎|𝝈|𝞂|ჿ|𝚘|০|୦|ዐ|𝛐|𝗈|𝞼|ဝ|ⲟ|𝙤|၀|𐐬|𝔬|𐓪|𝓸|🇴|⍤|○|ϙ|🅾|𝒪|𝖮|𝟢|𝟶|𝙾|o|𝗼|𝕠|𝜊|𝐨|𝝾|𝞸|ᐤ|オ|ѳ|᧐|ᥲ|ð|o|ఠ|ᦞ|Փ|ò|ө|ӧ|ó|º|ō|ô|ǒ|ȏ|ŏ|ồ|ȭ|ṏ|ὄ|ṑ|ṓ|ȯ|ȫ|๏|ᴏ|ő|ö|ѻ|о|ዐ|ǭ|ȱ|০|୦|٥|౦|告知|๐|໐|ο|օ|ᴑ|०|੦|ỏ|ơ|ờ|ớ|ỡ|ở|ợ|ọ|ộ|ǫ|ø|ǿ|ɵ|ծ|ὀ|ὁ|ό|ὸ|ό|ὂ|ὃ|ὅ|o|ం|𝖔|𝘰|ⓞ|೦|൦)(?:₥|ᵯ|𝖒|𝐦|𝗆|𝔪|𝕞|𝓂|ⓜ|m|ന|ᙢ|൩|ḿ|ṁ|ⅿ|ϻ|ṃ|ጠ|ɱ|៳|ᶆ|𝒎|🇲|𝙢|𝓶|𝚖|𝑚|𝗺|᧕|᧗|m|𝖼|𝛐))?'
].join(''),
'gu',
);
@@ -27,7 +27,7 @@ class NovelBuddy implements Plugin.PluginBase {
name = 'NovelBuddy';
site = 'https://novelbuddy.com/';
api = 'https://api.novelbuddy.com/';
- version = '2.1.5';
+ version = '2.1.3';
icon = 'src/en/novelbuddy/icon.png';
parseNovels(body: Response): Plugin.NovelItem[] {
From e36eabe8c23baa4d868b2c25372124b8e0bfeffa Mon Sep 17 00:00:00 2001
From: 7ui77 <99854073+7ui77@users.noreply.github.com>
Date: Mon, 25 May 2026 11:53:28 +0700
Subject: [PATCH 07/23] docs: add documentation for Plugin.PagePlugin
---
docs/docs.md | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 96 insertions(+)
diff --git a/docs/docs.md b/docs/docs.md
index e21efff0b..8fc0b317a 100644
--- a/docs/docs.md
+++ b/docs/docs.md
@@ -6,6 +6,7 @@
- [ChapterItem](#chapteritem)
- [Filters](#filters)
- [PluginSettings](#pluginsettings)
+- [PagePlugin](#pageplugin)
- [Using Cheerio](#using-cheerio)
- [Custom fetching functions](#custom-fetching-functions)
@@ -637,6 +638,101 @@ class ExamplePlugin implements Plugin.PluginBase {
---
+### PagePlugin
+
+`PagePlugin` is a specialized plugin type used for sites that split their chapter list across multiple pages (pagination). It extends `PluginBase` and adds methods to parse pages individually.
+
+To implement a `PagePlugin`, your class must implement `Plugin.PagePlugin`:
+
+```ts
+class ExamplePlugin implements Plugin.PagePlugin {
+ ...
+}
+```
+
+#### Differences from PluginBase
+
+| Method / Field | Return Type / Type | Description |
+| --- | --- | --- |
+| [parseNovel(novelPath)](#pagepluginparsenovel) | `Promise` | Parses novel metadata. The returned object MUST include `totalPages` (the total number of chapter pages) and typically initiates the `chapters` array as empty `[]`. |
+| [parsePage(novelPath, page)](#pagepluginparsepage) | `Promise` | Parses a specific page's chapter list. `page` is passed as a string (representing the page number or identifier). Returns a `SourcePage` object. |
+
+#### PagePlugin::parseNovel
+
+```ts
+async parseNovel(
+ novelPath: string
+): Promise
+```
+
+Unlike `PluginBase::parseNovel`, this method does not fetch the entire chapter list at once. Instead, it parses the metadata of the novel and identifies the total number of pages.
+
+##### Returns
+`SourceNovel & { totalPages: number }`
+- `totalPages`: The total number of pages of chapters available for this novel.
+
+##### Example:
+
+```ts
+async parseNovel(
+ novelPath: string
+): Promise {
+ const novel: Plugin.SourceNovel & { totalPages: number } = {
+ path: novelPath,
+ name: "Novel Name",
+ cover: defaultCover,
+ totalPages: 5, // total pages of chapters
+ chapters: [],
+ };
+ return novel;
+}
+```
+
+#### PagePlugin::parsePage
+
+```ts
+async parsePage(
+ novelPath: string,
+ page: string
+): Promise
+```
+
+This method fetches and parses the chapters belonging to a specific page.
+
+##### Parameters
+- `novelPath`: The relative path to the novel.
+- `page`: The page number or page identifier as a string.
+
+##### Returns
+`SourcePage` An object containing a `chapters` array for that page:
+```ts
+type SourcePage = {
+ chapters: ChapterItem[];
+};
+```
+
+##### Example:
+
+```ts
+async parsePage(
+ novelPath: string,
+ page: string
+): Promise {
+ const chapters: Plugin.ChapterItem[] = [];
+
+ // Parse chapters for the given page...
+ chapters.push({
+ name: "Chapter 1",
+ path: novelPath + "/chapter-1",
+ page: page,
+ });
+
+ return { chapters };
+}
+```
+
+---
+
### Using Cheerio
### Custom fetching functions
From 9a3d50bc0dfcce927f2a83446086049b86db2fdf Mon Sep 17 00:00:00 2001
From: 7ui77 <99854073+7ui77@users.noreply.github.com>
Date: Wed, 3 Jun 2026 10:05:42 +0700
Subject: [PATCH 08/23] Add NovelArrow plugin (english)
---
plugins/english/novelarrow.ts | 127 ++++++++++++++++++++++++++++++++++
1 file changed, 127 insertions(+)
create mode 100644 plugins/english/novelarrow.ts
diff --git a/plugins/english/novelarrow.ts b/plugins/english/novelarrow.ts
new file mode 100644
index 000000000..beb8fb6b3
--- /dev/null
+++ b/plugins/english/novelarrow.ts
@@ -0,0 +1,127 @@
+import { load as parseHTML } from 'cheerio';
+import { fetchApi } from '@libs/fetch';
+import { Plugin } from '@/types/plugin';
+import { NovelStatus } from '@libs/novelStatus';
+
+class NovelArrow implements Plugin {
+ id = 'novelarrow';
+ name = 'Novel Arrow';
+ icon = 'https://novelarrow.com/favicon-32.png';
+ site = 'https://novelarrow.com/';
+ version = '1.0.0';
+
+ // Headers cần thiết để vượt qua Cloudflare và giả lập trình duyệt di động như bạn đã cung cấp
+ headers = {
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
+ 'User-Agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Mobile Safari/537.36',
+ 'Referer': 'https://novelarrow.com/',
+ };
+
+ async popularNovels(page: number) {
+ const url = `${this.site}novels/latest?page=${page}`;
+ const result = await fetchApi(url, { headers: this.headers }).then(res => res.text());
+ const $ = parseHTML(result);
+ const novels: any[] = [];
+
+ $('article').each((i, el) => {
+ const title = $(el).find('h2').text().trim();
+ const cover = $(el).find('img').attr('src');
+ const href = $(el).find('a').attr('href');
+
+ if (title && href) {
+ novels.push({
+ name: title,
+ cover,
+ path: href.replace('/novel/', ''),
+ });
+ }
+ });
+
+ return novels;
+ }
+
+ async parseNovel(novelPath: string) {
+ const url = `${this.site}novel/${novelPath}`;
+ const result = await fetchApi(url, { headers: this.headers }).then(res => res.text());
+ const $ = parseHTML(result);
+
+ const novel: any = {
+ path: novelPath,
+ name: $('meta[property="og:novel:novel_name"]').attr('content') || $('h1').first().text().trim(),
+ cover: $('meta[property="og:image"]').attr('content'),
+ author: $('meta[property="og:novel:author"]').attr('content'),
+ status: $('meta[property="og:novel:status"]').attr('content') === 'Ongoing' ? NovelStatus.Ongoing : NovelStatus.Completed,
+ summary: $('meta[name="description"]').attr('content'),
+ chapters: [],
+ };
+
+ // Trích xuất danh sách chương từ script Next.js (thường nằm trong self.__next_f.push)
+ // Lưu ý: Next.js App Router đôi khi render danh sách chương qua API riêng hoặc nhúng sâu trong script
+ // Đây là logic fallback lấy từ các link có sẵn trong HTML
+ $('a[href^="/chapter/"]').each((i, el) => {
+ const name = $(el).text().trim();
+ const href = $(el).attr('href');
+ if (name && href) {
+ novel.chapters.push({
+ name,
+ path: href.replace('/chapter/', ''),
+ releaseTime: null,
+ });
+ }
+ });
+
+ return novel;
+ }
+
+ async parseChapter(novelPath: string, chapterPath: string) {
+ const url = `${this.site}chapter/${novelPath}/${chapterPath}`;
+ const result = await fetchApi(url, { headers: this.headers }).then(res => res.text());
+
+ // Vì nội dung chương nằm trong self.__next_f.push, chúng ta dùng Regex để trích xuất HTML
+ const contentMatch = result.match(/\\u003ch4\u003e(.*?)\\u003c\/p\u003e/);
+
+ if (!contentMatch) {
+ // Fallback: Thử tìm trong thẻ HTML thông thường nếu server-side render
+ const $ = parseHTML(result);
+ return $('.site-reading-copy').html() || "Content not found or premium.";
+ }
+
+ let chapterHtml = contentMatch[0];
+
+ // Giải mã các ký tự Unicode/Escaped của JSON Next.js
+ chapterHtml = chapterHtml
+ .replace(/\\u003c/g, '<')
+ .replace(/\\u003e/g, '>')
+ .replace(/\\"/g, '"')
+ .replace(/\\n/g, '')
+ .replace(/\\t/g, '');
+
+ return chapterHtml;
+ }
+
+ async searchNovels(searchTerm: string, page: number) {
+ // NovelArrow thường dùng query param cho search
+ const url = `${this.site}novels/search?q=${encodeURIComponent(searchTerm)}&page=${page}`;
+ const result = await fetchApi(url, { headers: this.headers }).then(res => res.text());
+ const $ = parseHTML(result);
+ const novels: any[] = [];
+
+ $('article').each((i, el) => {
+ const title = $(el).find('h2').text().trim();
+ const cover = $(el).find('img').attr('src');
+ const href = $(el).find('a').attr('href');
+
+ if (title && href) {
+ novels.push({
+ name: title,
+ cover,
+ path: href.replace('/novel/', ''),
+ });
+ }
+ });
+
+ return novels;
+ }
+}
+
+export default new NovelArrow();
From f3b78e6a27d342b4e4edcbd65641736fba54ab67 Mon Sep 17 00:00:00 2001
From: 7ui77 <99854073+7ui77@users.noreply.github.com>
Date: Wed, 3 Jun 2026 10:22:26 +0700
Subject: [PATCH 09/23] Fix chapter list and content extraction for NovelArrow
(version 1.0.1)
---
plugins/english/novelarrow.ts | 67 +++++++++++++++++++++++------------
1 file changed, 44 insertions(+), 23 deletions(-)
diff --git a/plugins/english/novelarrow.ts b/plugins/english/novelarrow.ts
index beb8fb6b3..ec0d17860 100644
--- a/plugins/english/novelarrow.ts
+++ b/plugins/english/novelarrow.ts
@@ -8,9 +8,9 @@ class NovelArrow implements Plugin {
name = 'Novel Arrow';
icon = 'https://novelarrow.com/favicon-32.png';
site = 'https://novelarrow.com/';
- version = '1.0.0';
+ version = '1.0.1';
- // Headers cần thiết để vượt qua Cloudflare và giả lập trình duyệt di động như bạn đã cung cấp
+ // Headers cần thiết để vượt qua Cloudflare và giả lập trình duyệt di động
headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
'User-Agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Mobile Safari/537.36',
@@ -55,20 +55,39 @@ class NovelArrow implements Plugin {
chapters: [],
};
- // Trích xuất danh sách chương từ script Next.js (thường nằm trong self.__next_f.push)
- // Lưu ý: Next.js App Router đôi khi render danh sách chương qua API riêng hoặc nhúng sâu trong script
- // Đây là logic fallback lấy từ các link có sẵn trong HTML
- $('a[href^="/chapter/"]').each((i, el) => {
- const name = $(el).text().trim();
- const href = $(el).attr('href');
- if (name && href) {
- novel.chapters.push({
+ // Tìm tất cả các chương bằng Regex vì chúng nằm trong stream JSON của Next.js
+ const chapterRegex = /\\?"chapter_id\\?":\\?"([^"]+)\\?",\\?"chapter_name\\?":\\?"([^"]+)\\?"/g;
+ let match;
+ const chaptersMap = new Map();
+
+ while ((match = chapterRegex.exec(result)) !== null) {
+ const path = match[1];
+ const name = match[2].replace(/\\"/g, '"');
+ if (!chaptersMap.has(path)) {
+ chaptersMap.set(path, {
name,
- path: href.replace('/chapter/', ''),
+ path,
releaseTime: null,
});
}
- });
+ }
+
+ novel.chapters = Array.from(chaptersMap.values());
+
+ // Nếu không tìm thấy bằng Regex, thử dùng fallback Cheerio
+ if (novel.chapters.length === 0) {
+ $('a[href^="/chapter/"]').each((i, el) => {
+ const name = $(el).text().trim();
+ const href = $(el).attr('href');
+ if (name && href) {
+ novel.chapters.push({
+ name,
+ path: href.replace('/chapter/', ''),
+ releaseTime: null,
+ });
+ }
+ });
+ }
return novel;
}
@@ -77,30 +96,32 @@ class NovelArrow implements Plugin {
const url = `${this.site}chapter/${novelPath}/${chapterPath}`;
const result = await fetchApi(url, { headers: this.headers }).then(res => res.text());
- // Vì nội dung chương nằm trong self.__next_f.push, chúng ta dùng Regex để trích xuất HTML
- const contentMatch = result.match(/\\u003ch4\u003e(.*?)\\u003c\/p\u003e/);
-
- if (!contentMatch) {
- // Fallback: Thử tìm trong thẻ HTML thông thường nếu server-side render
- const $ = parseHTML(result);
- return $('.site-reading-copy').html() || "Content not found or premium.";
+ // Tìm nội dung chương trong stream Next.js
+ // Nội dung thường bắt đầu bằng và chứa nhiều
+ const contentRegex = /\\u003ch4\\u003e([\s\S]*?)\\u003c\/p\\u003e/;
+ const match = result.match(contentRegex);
+
+ if (!match) {
+ const $ = parseHTML(result);
+ return $('.site-reading-copy').html() || "Content not found or premium.";
}
- let chapterHtml = contentMatch[0];
+ let chapterHtml = match[0];
- // Giải mã các ký tự Unicode/Escaped của JSON Next.js
+ // Giải mã Unicode và các ký tự thoát
chapterHtml = chapterHtml
.replace(/\\u003c/g, '<')
.replace(/\\u003e/g, '>')
.replace(/\\"/g, '"')
.replace(/\\n/g, '')
- .replace(/\\t/g, '');
+ .replace(/\\t/g, '')
+ .replace(/\\r/g, '')
+ .replace(/\\\\/g, '\\');
return chapterHtml;
}
async searchNovels(searchTerm: string, page: number) {
- // NovelArrow thường dùng query param cho search
const url = `${this.site}novels/search?q=${encodeURIComponent(searchTerm)}&page=${page}`;
const result = await fetchApi(url, { headers: this.headers }).then(res => res.text());
const $ = parseHTML(result);
From 24757b580f915f46c68b5244c9cad67aeb7ce0b4 Mon Sep 17 00:00:00 2001
From: 7ui77 <99854073+7ui77@users.noreply.github.com>
Date: Wed, 3 Jun 2026 10:47:25 +0700
Subject: [PATCH 10/23] Refine content and chapter extraction for NovelArrow
(version 1.0.1)
---
plugins/english/novelarrow.ts | 30 ++++++++++++++++++++++++++++--
1 file changed, 28 insertions(+), 2 deletions(-)
diff --git a/plugins/english/novelarrow.ts b/plugins/english/novelarrow.ts
index ec0d17860..ad42fe6b7 100644
--- a/plugins/english/novelarrow.ts
+++ b/plugins/english/novelarrow.ts
@@ -72,6 +72,25 @@ class NovelArrow implements Plugin {
}
}
+ // Lấy chương đầu và chương cuối từ meta tags (đề phòng danh sách bị thiếu)
+ const firstChapterUrl = $('meta[property="og:novel:read_url"]').attr('content');
+ const latestChapterUrl = $('meta[property="og:novel:latest_chapter_url"]').attr('content');
+ const latestChapterName = $('meta[property="og:novel:latest_chapter_name"]').attr('content');
+
+ if (firstChapterUrl) {
+ const path = firstChapterUrl.replace(/.*\/chapter\//, '').split('/').pop() || '';
+ if (path && !chaptersMap.has(path)) {
+ chaptersMap.set(path, { name: 'Chapter 1', path, releaseTime: null });
+ }
+ }
+
+ if (latestChapterUrl && latestChapterName) {
+ const path = latestChapterUrl.replace(/.*\/chapter\//, '').split('/').pop() || '';
+ if (path && !chaptersMap.has(path)) {
+ chaptersMap.set(path, { name: latestChapterName, path, releaseTime: null });
+ }
+ }
+
novel.chapters = Array.from(chaptersMap.values());
// Nếu không tìm thấy bằng Regex, thử dùng fallback Cheerio
@@ -97,8 +116,8 @@ class NovelArrow implements Plugin {
const result = await fetchApi(url, { headers: this.headers }).then(res => res.text());
// Tìm nội dung chương trong stream Next.js
- // Nội dung thường bắt đầu bằng
và chứa nhiều
- const contentRegex = /\\u003ch4\\u003e([\s\S]*?)\\u003c\/p\\u003e/;
+ // Sử dụng Regex tham lam để lấy từ
đầu tiên đến
cuối cùng trong block
+ const contentRegex = /\\u003ch4\\u003e(.*)\\u003c\/p\\u003e/;
const match = result.match(contentRegex);
if (!match) {
@@ -118,6 +137,13 @@ class NovelArrow implements Plugin {
.replace(/\\r/g, '')
.replace(/\\\\/g, '\\');
+ // Làm sạch nội dung (loại bỏ các chuỗi thừa nếu Regex tham lam lấy quá nhiều)
+ // Nội dung thật thường kết thúc bằng và sau đó là các ký tự điều khiển JSON
+ const lastPTagIndex = chapterHtml.lastIndexOf('');
+ if (lastPTagIndex !== -1) {
+ chapterHtml = chapterHtml.substring(0, lastPTagIndex + 4);
+ }
+
return chapterHtml;
}
From b588ed5281be8085f59eb6e212b62f2177bdca81 Mon Sep 17 00:00:00 2001
From: 7ui77 <99854073+7ui77@users.noreply.github.com>
Date: Wed, 3 Jun 2026 10:53:13 +0700
Subject: [PATCH 11/23] Add full chapter support and robust content extraction
for NovelArrow (v1.0.2)
---
plugins/english/novelarrow.ts | 88 ++++++++++++++---------------------
1 file changed, 35 insertions(+), 53 deletions(-)
diff --git a/plugins/english/novelarrow.ts b/plugins/english/novelarrow.ts
index ad42fe6b7..871b8c78a 100644
--- a/plugins/english/novelarrow.ts
+++ b/plugins/english/novelarrow.ts
@@ -8,13 +8,16 @@ class NovelArrow implements Plugin {
name = 'Novel Arrow';
icon = 'https://novelarrow.com/favicon-32.png';
site = 'https://novelarrow.com/';
- version = '1.0.1';
+ version = '1.0.2';
// Headers cần thiết để vượt qua Cloudflare và giả lập trình duyệt di động
headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
'User-Agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Mobile Safari/537.36',
'Referer': 'https://novelarrow.com/',
+ 'x-client-platform': 'web-mobile',
+ 'x-device-type': 'mobile',
+ 'x-version-app': 'web-mobile',
};
async popularNovels(page: number) {
@@ -55,57 +58,37 @@ class NovelArrow implements Plugin {
chapters: [],
};
- // Tìm tất cả các chương bằng Regex vì chúng nằm trong stream JSON của Next.js
- const chapterRegex = /\\?"chapter_id\\?":\\?"([^"]+)\\?",\\?"chapter_name\\?":\\?"([^"]+)\\?"/g;
- let match;
- const chaptersMap = new Map();
-
- while ((match = chapterRegex.exec(result)) !== null) {
- const path = match[1];
- const name = match[2].replace(/\\"/g, '"');
- if (!chaptersMap.has(path)) {
- chaptersMap.set(path, {
- name,
- path,
- releaseTime: null,
- });
- }
- }
-
- // Lấy chương đầu và chương cuối từ meta tags (đề phòng danh sách bị thiếu)
- const firstChapterUrl = $('meta[property="og:novel:read_url"]').attr('content');
- const latestChapterUrl = $('meta[property="og:novel:latest_chapter_url"]').attr('content');
- const latestChapterName = $('meta[property="og:novel:latest_chapter_name"]').attr('content');
-
- if (firstChapterUrl) {
- const path = firstChapterUrl.replace(/.*\/chapter\//, '').split('/').pop() || '';
- if (path && !chaptersMap.has(path)) {
- chaptersMap.set(path, { name: 'Chapter 1', path, releaseTime: null });
+ // Sử dụng API web để lấy đầy đủ danh sách chương (Hỗ trợ truyện 3000+ chương)
+ const chaptersUrl = `${this.site}api-web/novels/${novelPath}/chapters?sort=asc`;
+ try {
+ const chaptersJson = await fetchApi(chaptersUrl, {
+ headers: {
+ ...this.headers,
+ 'Accept': 'application/json',
+ }
+ }).then(res => res.json());
+
+ if (chaptersJson && chaptersJson.items) {
+ novel.chapters = chaptersJson.items.map((item: any) => ({
+ name: item.chapter_name,
+ path: item.chapter_id,
+ releaseTime: null,
+ }));
}
- }
-
- if (latestChapterUrl && latestChapterName) {
- const path = latestChapterUrl.replace(/.*\/chapter\//, '').split('/').pop() || '';
- if (path && !chaptersMap.has(path)) {
- chaptersMap.set(path, { name: latestChapterName, path, releaseTime: null });
- }
- }
-
- novel.chapters = Array.from(chaptersMap.values());
-
- // Nếu không tìm thấy bằng Regex, thử dùng fallback Cheerio
- if (novel.chapters.length === 0) {
- $('a[href^="/chapter/"]').each((i, el) => {
- const name = $(el).text().trim();
- const href = $(el).attr('href');
- if (name && href) {
- novel.chapters.push({
- name,
- path: href.replace('/chapter/', ''),
- releaseTime: null,
- });
+ } catch (e) {
+ // Fallback: Tìm bằng Regex trong stream JSON của Next.js
+ const chapterRegex = /\\?"chapter_id\\?":\\?"([^"]+)\\?",\\?"chapter_name\\?":\\?"([^"]+)\\?"/g;
+ let match;
+ const chaptersMap = new Map();
+
+ while ((match = chapterRegex.exec(result)) !== null) {
+ const path = match[1];
+ const name = match[2].replace(/\\"/g, '"');
+ if (!chaptersMap.has(path)) {
+ chaptersMap.set(path, { name, path, releaseTime: null });
+ }
}
- });
+ novel.chapters = Array.from(chaptersMap.values());
}
return novel;
@@ -127,7 +110,7 @@ class NovelArrow implements Plugin {
let chapterHtml = match[0];
- // Giải mã Unicode và các ký tự thoát
+ // Giải mã Unicode và các ký tự thoát đặc thù của Next.js
chapterHtml = chapterHtml
.replace(/\\u003c/g, '<')
.replace(/\\u003e/g, '>')
@@ -137,8 +120,7 @@ class NovelArrow implements Plugin {
.replace(/\\r/g, '')
.replace(/\\\\/g, '\\');
- // Làm sạch nội dung (loại bỏ các chuỗi thừa nếu Regex tham lam lấy quá nhiều)
- // Nội dung thật thường kết thúc bằng và sau đó là các ký tự điều khiển JSON
+ // Làm sạch nội dung: Cắt bỏ các chuỗi thừa sau thẻ cuối cùng
const lastPTagIndex = chapterHtml.lastIndexOf('');
if (lastPTagIndex !== -1) {
chapterHtml = chapterHtml.substring(0, lastPTagIndex + 4);
From 20afd70409614fe5e40158506ac29b05254fcec5 Mon Sep 17 00:00:00 2001
From: 7ui77 <99854073+7ui77@users.noreply.github.com>
Date: Wed, 3 Jun 2026 10:56:54 +0700
Subject: [PATCH 12/23] Use stable api-web endpoints for chapter list and
content (v1.0.3)
---
plugins/english/novelarrow.ts | 68 ++++++++++++++++++++---------------
1 file changed, 39 insertions(+), 29 deletions(-)
diff --git a/plugins/english/novelarrow.ts b/plugins/english/novelarrow.ts
index 871b8c78a..60c00b8be 100644
--- a/plugins/english/novelarrow.ts
+++ b/plugins/english/novelarrow.ts
@@ -8,7 +8,7 @@ class NovelArrow implements Plugin {
name = 'Novel Arrow';
icon = 'https://novelarrow.com/favicon-32.png';
site = 'https://novelarrow.com/';
- version = '1.0.2';
+ version = '1.0.3';
// Headers cần thiết để vượt qua Cloudflare và giả lập trình duyệt di động
headers = {
@@ -95,38 +95,48 @@ class NovelArrow implements Plugin {
}
async parseChapter(novelPath: string, chapterPath: string) {
- const url = `${this.site}chapter/${novelPath}/${chapterPath}`;
- const result = await fetchApi(url, { headers: this.headers }).then(res => res.text());
-
- // Tìm nội dung chương trong stream Next.js
- // Sử dụng Regex tham lam để lấy từ đầu tiên đến cuối cùng trong block
- const contentRegex = /\\u003ch4\\u003e(.*)\\u003c\/p\\u003e/;
- const match = result.match(contentRegex);
+ // Sử dụng API web để lấy nội dung chương (Rất tin cậy)
+ const url = `${this.site}api-web/novels/${novelPath}/chapters/${chapterPath}`;
+ try {
+ const json = await fetchApi(url, {
+ headers: {
+ ...this.headers,
+ 'Accept': 'application/json',
+ }
+ }).then(res => res.json());
- if (!match) {
- const $ = parseHTML(result);
- return $('.site-reading-copy').html() || "Content not found or premium.";
- }
+ if (json && json.item && json.item.chapterInfo && json.item.chapterInfo.chapter_content) {
+ return json.item.chapterInfo.chapter_content;
+ }
+ } catch (e) {
+ // Fallback: Tìm trong stream JSON của Next.js nếu API web lỗi
+ const result = await fetchApi(`${this.site}chapter/${novelPath}/${chapterPath}`, { headers: this.headers }).then(res => res.text());
+ const contentRegex = /\\u003ch4\\u003e(.*)\\u003c\/p\\u003e/;
+ const match = result.match(contentRegex);
+
+ if (match) {
+ let chapterHtml = match[0];
+ chapterHtml = chapterHtml
+ .replace(/\\u003c/g, '<')
+ .replace(/\\u003e/g, '>')
+ .replace(/\\"/g, '"')
+ .replace(/\\n/g, '')
+ .replace(/\\t/g, '')
+ .replace(/\\r/g, '')
+ .replace(/\\\\/g, '\\');
+
+ const lastPTagIndex = chapterHtml.lastIndexOf('');
+ if (lastPTagIndex !== -1) {
+ chapterHtml = chapterHtml.substring(0, lastPTagIndex + 4);
+ }
+ return chapterHtml;
+ }
- let chapterHtml = match[0];
-
- // Giải mã Unicode và các ký tự thoát đặc thù của Next.js
- chapterHtml = chapterHtml
- .replace(/\\u003c/g, '<')
- .replace(/\\u003e/g, '>')
- .replace(/\\"/g, '"')
- .replace(/\\n/g, '')
- .replace(/\\t/g, '')
- .replace(/\\r/g, '')
- .replace(/\\\\/g, '\\');
-
- // Làm sạch nội dung: Cắt bỏ các chuỗi thừa sau thẻ cuối cùng
- const lastPTagIndex = chapterHtml.lastIndexOf('');
- if (lastPTagIndex !== -1) {
- chapterHtml = chapterHtml.substring(0, lastPTagIndex + 4);
+ const $ = parseHTML(result);
+ return $('.site-reading-copy').html() || "Content not found or premium.";
}
- return chapterHtml;
+ return "Content not found or premium.";
}
async searchNovels(searchTerm: string, page: number) {
From 0910f104b9fa9d967706bc9f0582c7037c0fe383 Mon Sep 17 00:00:00 2001
From: 7ui77 <99854073+7ui77@users.noreply.github.com>
Date: Wed, 3 Jun 2026 11:03:28 +0700
Subject: [PATCH 13/23] Fix parseChapter signature and path encoding for
NovelArrow (v1.0.4)
---
plugins/english/novelarrow.ts | 26 +++++++++++++-------------
1 file changed, 13 insertions(+), 13 deletions(-)
diff --git a/plugins/english/novelarrow.ts b/plugins/english/novelarrow.ts
index 60c00b8be..a9718753f 100644
--- a/plugins/english/novelarrow.ts
+++ b/plugins/english/novelarrow.ts
@@ -8,7 +8,7 @@ class NovelArrow implements Plugin {
name = 'Novel Arrow';
icon = 'https://novelarrow.com/favicon-32.png';
site = 'https://novelarrow.com/';
- version = '1.0.3';
+ version = '1.0.4';
// Headers cần thiết để vượt qua Cloudflare và giả lập trình duyệt di động
headers = {
@@ -71,7 +71,7 @@ class NovelArrow implements Plugin {
if (chaptersJson && chaptersJson.items) {
novel.chapters = chaptersJson.items.map((item: any) => ({
name: item.chapter_name,
- path: item.chapter_id,
+ path: `${novelPath}/${item.chapter_id}`, // Lưu cả novelId và chapterId
releaseTime: null,
}));
}
@@ -79,24 +79,23 @@ class NovelArrow implements Plugin {
// Fallback: Tìm bằng Regex trong stream JSON của Next.js
const chapterRegex = /\\?"chapter_id\\?":\\?"([^"]+)\\?",\\?"chapter_name\\?":\\?"([^"]+)\\?"/g;
let match;
- const chaptersMap = new Map();
-
while ((match = chapterRegex.exec(result)) !== null) {
const path = match[1];
const name = match[2].replace(/\\"/g, '"');
- if (!chaptersMap.has(path)) {
- chaptersMap.set(path, { name, path, releaseTime: null });
- }
+ novel.chapters.push({
+ name,
+ path: `${novelPath}/${path}`,
+ releaseTime: null,
+ });
}
- novel.chapters = Array.from(chaptersMap.values());
}
return novel;
}
- async parseChapter(novelPath: string, chapterPath: string) {
- // Sử dụng API web để lấy nội dung chương (Rất tin cậy)
- const url = `${this.site}api-web/novels/${novelPath}/chapters/${chapterPath}`;
+ async parseChapter(chapterPath: string) {
+ // chapterPath bây giờ có dạng "novel-slug/chapter-slug"
+ const url = `${this.site}api-web/novels/${chapterPath}`;
try {
const json = await fetchApi(url, {
headers: {
@@ -105,12 +104,13 @@ class NovelArrow implements Plugin {
}
}).then(res => res.json());
+ // Kiểm tra đúng cấu trúc JSON trả về: item.chapterInfo.chapter_content
if (json && json.item && json.item.chapterInfo && json.item.chapterInfo.chapter_content) {
return json.item.chapterInfo.chapter_content;
}
} catch (e) {
- // Fallback: Tìm trong stream JSON của Next.js nếu API web lỗi
- const result = await fetchApi(`${this.site}chapter/${novelPath}/${chapterPath}`, { headers: this.headers }).then(res => res.text());
+ // Fallback: Thử tải trang HTML và quét Regex
+ const result = await fetchApi(`${this.site}chapter/${chapterPath}`, { headers: this.headers }).then(res => res.text());
const contentRegex = /\\u003ch4\\u003e(.*)\\u003c\/p\\u003e/;
const match = result.match(contentRegex);
From f467b5aa1c85a8d26f2ef5790229957dce723259 Mon Sep 17 00:00:00 2001
From: 7ui77 <99854073+7ui77@users.noreply.github.com>
Date: Wed, 3 Jun 2026 11:16:03 +0700
Subject: [PATCH 14/23] Fix chapter content API URL and pathing (v1.0.5)
---
plugins/english/novelarrow.ts | 24 ++++++++++++++++--------
1 file changed, 16 insertions(+), 8 deletions(-)
diff --git a/plugins/english/novelarrow.ts b/plugins/english/novelarrow.ts
index a9718753f..c6c1ec862 100644
--- a/plugins/english/novelarrow.ts
+++ b/plugins/english/novelarrow.ts
@@ -8,7 +8,7 @@ class NovelArrow implements Plugin {
name = 'Novel Arrow';
icon = 'https://novelarrow.com/favicon-32.png';
site = 'https://novelarrow.com/';
- version = '1.0.4';
+ version = '1.0.5';
// Headers cần thiết để vượt qua Cloudflare và giả lập trình duyệt di động
headers = {
@@ -79,28 +79,36 @@ class NovelArrow implements Plugin {
// Fallback: Tìm bằng Regex trong stream JSON của Next.js
const chapterRegex = /\\?"chapter_id\\?":\\?"([^"]+)\\?",\\?"chapter_name\\?":\\?"([^"]+)\\?"/g;
let match;
+ const chaptersMap = new Map();
+
while ((match = chapterRegex.exec(result)) !== null) {
const path = match[1];
const name = match[2].replace(/\\"/g, '"');
- novel.chapters.push({
- name,
- path: `${novelPath}/${path}`,
- releaseTime: null,
- });
+ if (!chaptersMap.has(path)) {
+ chaptersMap.set(path, { name, path: `${novelPath}/${path}`, releaseTime: null });
+ }
}
+ novel.chapters = Array.from(chaptersMap.values());
}
return novel;
}
async parseChapter(chapterPath: string) {
- // chapterPath bây giờ có dạng "novel-slug/chapter-slug"
- const url = `${this.site}api-web/novels/${chapterPath}`;
+ // chapterPath có dạng "novel-slug/chapter-slug"
+ const pathParts = chapterPath.split('/');
+ const novelId = pathParts[0];
+ const chapterId = pathParts[1];
+
+ // API URL đúng phải có /chapters/ ở giữa
+ const url = `${this.site}api-web/novels/${novelId}/chapters/${chapterId}`;
+
try {
const json = await fetchApi(url, {
headers: {
...this.headers,
'Accept': 'application/json',
+ 'x-track-reading-progress': 'false',
}
}).then(res => res.json());
From 8238178d5bf54a60770caba40f703d7ac0c2d741 Mon Sep 17 00:00:00 2001
From: 7ui77 <99854073+7ui77@users.noreply.github.com>
Date: Wed, 3 Jun 2026 11:21:36 +0700
Subject: [PATCH 15/23] Fix WebView 404 by including URL prefixes in paths
(v1.0.6)
---
plugins/english/novelarrow.ts | 32 +++++++++++++++++---------------
1 file changed, 17 insertions(+), 15 deletions(-)
diff --git a/plugins/english/novelarrow.ts b/plugins/english/novelarrow.ts
index c6c1ec862..cb65d425d 100644
--- a/plugins/english/novelarrow.ts
+++ b/plugins/english/novelarrow.ts
@@ -8,7 +8,7 @@ class NovelArrow implements Plugin {
name = 'Novel Arrow';
icon = 'https://novelarrow.com/favicon-32.png';
site = 'https://novelarrow.com/';
- version = '1.0.5';
+ version = '1.0.6';
// Headers cần thiết để vượt qua Cloudflare và giả lập trình duyệt di động
headers = {
@@ -35,7 +35,7 @@ class NovelArrow implements Plugin {
novels.push({
name: title,
cover,
- path: href.replace('/novel/', ''),
+ path: href.substring(1), // Kết quả: "novel/slug" thay vì chỉ "slug"
});
}
});
@@ -44,10 +44,12 @@ class NovelArrow implements Plugin {
}
async parseNovel(novelPath: string) {
- const url = `${this.site}novel/${novelPath}`;
+ // novelPath bây giờ có dạng "novel/slug"
+ const url = `${this.site}${novelPath}`;
const result = await fetchApi(url, { headers: this.headers }).then(res => res.text());
const $ = parseHTML(result);
+ const novelId = novelPath.replace('novel/', '');
const novel: any = {
path: novelPath,
name: $('meta[property="og:novel:novel_name"]').attr('content') || $('h1').first().text().trim(),
@@ -58,8 +60,8 @@ class NovelArrow implements Plugin {
chapters: [],
};
- // Sử dụng API web để lấy đầy đủ danh sách chương (Hỗ trợ truyện 3000+ chương)
- const chaptersUrl = `${this.site}api-web/novels/${novelPath}/chapters?sort=asc`;
+ // Sử dụng API web để lấy đầy đủ danh sách chương
+ const chaptersUrl = `${this.site}api-web/novels/${novelId}/chapters?sort=asc`;
try {
const chaptersJson = await fetchApi(chaptersUrl, {
headers: {
@@ -71,7 +73,7 @@ class NovelArrow implements Plugin {
if (chaptersJson && chaptersJson.items) {
novel.chapters = chaptersJson.items.map((item: any) => ({
name: item.chapter_name,
- path: `${novelPath}/${item.chapter_id}`, // Lưu cả novelId và chapterId
+ path: `chapter/${novelId}/${item.chapter_id}`, // Kết quả: "chapter/novel-slug/chapter-slug"
releaseTime: null,
}));
}
@@ -84,8 +86,9 @@ class NovelArrow implements Plugin {
while ((match = chapterRegex.exec(result)) !== null) {
const path = match[1];
const name = match[2].replace(/\\"/g, '"');
- if (!chaptersMap.has(path)) {
- chaptersMap.set(path, { name, path: `${novelPath}/${path}`, releaseTime: null });
+ const fullPath = `chapter/${novelId}/${path}`;
+ if (!chaptersMap.has(fullPath)) {
+ chaptersMap.set(fullPath, { name, path: fullPath, releaseTime: null });
}
}
novel.chapters = Array.from(chaptersMap.values());
@@ -95,12 +98,12 @@ class NovelArrow implements Plugin {
}
async parseChapter(chapterPath: string) {
- // chapterPath có dạng "novel-slug/chapter-slug"
- const pathParts = chapterPath.split('/');
+ // chapterPath bây giờ có dạng "chapter/novel-slug/chapter-slug"
+ // Để gọi API, chúng ta cần "novel-slug/chapters/chapter-slug"
+ const pathParts = chapterPath.replace('chapter/', '').split('/');
const novelId = pathParts[0];
const chapterId = pathParts[1];
- // API URL đúng phải có /chapters/ ở giữa
const url = `${this.site}api-web/novels/${novelId}/chapters/${chapterId}`;
try {
@@ -112,13 +115,12 @@ class NovelArrow implements Plugin {
}
}).then(res => res.json());
- // Kiểm tra đúng cấu trúc JSON trả về: item.chapterInfo.chapter_content
if (json && json.item && json.item.chapterInfo && json.item.chapterInfo.chapter_content) {
return json.item.chapterInfo.chapter_content;
}
} catch (e) {
- // Fallback: Thử tải trang HTML và quét Regex
- const result = await fetchApi(`${this.site}chapter/${chapterPath}`, { headers: this.headers }).then(res => res.text());
+ // Fallback: Thử tải trang HTML (Sử dụng chính chapterPath làm slug)
+ const result = await fetchApi(`${this.site}${chapterPath}`, { headers: this.headers }).then(res => res.text());
const contentRegex = /\\u003ch4\\u003e(.*)\\u003c\/p\\u003e/;
const match = result.match(contentRegex);
@@ -162,7 +164,7 @@ class NovelArrow implements Plugin {
novels.push({
name: title,
cover,
- path: href.replace('/novel/', ''),
+ path: href.substring(1), // Kết quả: "novel/slug"
});
}
});
From 522d0ff92c0fde6af2c2ce95df1fbf547a1fd3c8 Mon Sep 17 00:00:00 2001
From: 7ui77 <99854073+7ui77@users.noreply.github.com>
Date: Wed, 3 Jun 2026 11:26:29 +0700
Subject: [PATCH 16/23] Restore missing title and summary (v1.0.7)
---
plugins/english/novelarrow.ts | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/plugins/english/novelarrow.ts b/plugins/english/novelarrow.ts
index cb65d425d..11fd2b210 100644
--- a/plugins/english/novelarrow.ts
+++ b/plugins/english/novelarrow.ts
@@ -8,7 +8,7 @@ class NovelArrow implements Plugin {
name = 'Novel Arrow';
icon = 'https://novelarrow.com/favicon-32.png';
site = 'https://novelarrow.com/';
- version = '1.0.6';
+ version = '1.0.7';
// Headers cần thiết để vượt qua Cloudflare và giả lập trình duyệt di động
headers = {
@@ -52,11 +52,13 @@ class NovelArrow implements Plugin {
const novelId = novelPath.replace('novel/', '');
const novel: any = {
path: novelPath,
- name: $('meta[property="og:novel:novel_name"]').attr('content') || $('h1').first().text().trim(),
+ name: $('meta[name="og:novel:novel_name"]').attr('content') ||
+ $('meta[property="og:title"]').attr('content')?.split(' Novel')[0] ||
+ $('h1').text().trim(),
cover: $('meta[property="og:image"]').attr('content'),
- author: $('meta[property="og:novel:author"]').attr('content'),
- status: $('meta[property="og:novel:status"]').attr('content') === 'Ongoing' ? NovelStatus.Ongoing : NovelStatus.Completed,
- summary: $('meta[name="description"]').attr('content'),
+ author: $('meta[name="og:novel:author"]').attr('content') || $('meta[name="author"]').attr('content'),
+ status: $('meta[name="og:novel:status"]').attr('content') === 'Ongoing' ? NovelStatus.Ongoing : NovelStatus.Completed,
+ summary: $('meta[name="description"]').attr('content') || $('meta[property="og:description"]').attr('content'),
chapters: [],
};
@@ -99,7 +101,6 @@ class NovelArrow implements Plugin {
async parseChapter(chapterPath: string) {
// chapterPath bây giờ có dạng "chapter/novel-slug/chapter-slug"
- // Để gọi API, chúng ta cần "novel-slug/chapters/chapter-slug"
const pathParts = chapterPath.replace('chapter/', '').split('/');
const novelId = pathParts[0];
const chapterId = pathParts[1];
From 58230333c875e6479b92bfb6bdd51c5e0515bfb9 Mon Sep 17 00:00:00 2001
From: 7ui77 <99854073+7ui77@users.noreply.github.com>
Date: Wed, 3 Jun 2026 11:34:57 +0700
Subject: [PATCH 17/23] Add Category and Genre filters for NovelArrow (v1.0.8)
---
plugins/english/novelarrow.ts | 96 ++++++++++++++++++++++++++++++++---
1 file changed, 89 insertions(+), 7 deletions(-)
diff --git a/plugins/english/novelarrow.ts b/plugins/english/novelarrow.ts
index 11fd2b210..3d00fbfa9 100644
--- a/plugins/english/novelarrow.ts
+++ b/plugins/english/novelarrow.ts
@@ -2,13 +2,14 @@ import { load as parseHTML } from 'cheerio';
import { fetchApi } from '@libs/fetch';
import { Plugin } from '@/types/plugin';
import { NovelStatus } from '@libs/novelStatus';
+import { Filters, FilterTypes } from '@libs/filterInputs';
class NovelArrow implements Plugin {
id = 'novelarrow';
name = 'Novel Arrow';
icon = 'https://novelarrow.com/favicon-32.png';
site = 'https://novelarrow.com/';
- version = '1.0.7';
+ version = '1.0.8';
// Headers cần thiết để vượt qua Cloudflare và giả lập trình duyệt di động
headers = {
@@ -20,8 +21,15 @@ class NovelArrow implements Plugin {
'x-version-app': 'web-mobile',
};
- async popularNovels(page: number) {
- const url = `${this.site}novels/latest?page=${page}`;
+ async popularNovels(page: number, { filters }: Plugin.PopularNovelsOptions) {
+ let url = this.site;
+ if (filters?.genre && filters.genre !== '') {
+ url += `genre/${filters.genre}?page=${page}`;
+ } else {
+ const category = filters?.category || 'latest';
+ url += `novels/${category}?page=${page}`;
+ }
+
const result = await fetchApi(url, { headers: this.headers }).then(res => res.text());
const $ = parseHTML(result);
const novels: any[] = [];
@@ -35,7 +43,7 @@ class NovelArrow implements Plugin {
novels.push({
name: title,
cover,
- path: href.substring(1), // Kết quả: "novel/slug" thay vì chỉ "slug"
+ path: href.substring(1), // Kết quả: "novel/slug"
});
}
});
@@ -44,7 +52,7 @@ class NovelArrow implements Plugin {
}
async parseNovel(novelPath: string) {
- // novelPath bây giờ có dạng "novel/slug"
+ // novelPath có dạng "novel/slug"
const url = `${this.site}${novelPath}`;
const result = await fetchApi(url, { headers: this.headers }).then(res => res.text());
const $ = parseHTML(result);
@@ -75,7 +83,7 @@ class NovelArrow implements Plugin {
if (chaptersJson && chaptersJson.items) {
novel.chapters = chaptersJson.items.map((item: any) => ({
name: item.chapter_name,
- path: `chapter/${novelId}/${item.chapter_id}`, // Kết quả: "chapter/novel-slug/chapter-slug"
+ path: `chapter/${novelId}/${item.chapter_id}`,
releaseTime: null,
}));
}
@@ -100,7 +108,7 @@ class NovelArrow implements Plugin {
}
async parseChapter(chapterPath: string) {
- // chapterPath bây giờ có dạng "chapter/novel-slug/chapter-slug"
+ // chapterPath có dạng "chapter/novel-slug/chapter-slug"
const pathParts = chapterPath.replace('chapter/', '').split('/');
const novelId = pathParts[0];
const chapterId = pathParts[1];
@@ -172,6 +180,80 @@ class NovelArrow implements Plugin {
return novels;
}
+
+ readonly filters = {
+ category: {
+ label: 'Category',
+ type: FilterTypes.Picker,
+ options: [
+ { label: 'Latest Updates', value: 'latest' },
+ { label: 'Hot Novels', value: 'hot' },
+ { label: 'Completed', value: 'complete' },
+ { label: 'Ongoing', value: 'ongoing' },
+ ],
+ },
+ genre: {
+ label: 'Genre',
+ type: FilterTypes.Picker,
+ options: [
+ { label: 'None', value: '' },
+ { label: 'Action', value: 'action' },
+ { label: 'Adult', value: 'adult' },
+ { label: 'Adventure', value: 'adventure' },
+ { label: 'Anime & Comics', value: 'anime-&-comics' },
+ { label: 'Comedy', value: 'comedy' },
+ { label: 'Drama', value: 'drama' },
+ { label: 'Eastern', value: 'eastern' },
+ { label: 'Ecchi', value: 'ecchi' },
+ { label: 'Fan-fiction', value: 'fan-fiction' },
+ { label: 'Fantasy', value: 'fantasy' },
+ { label: 'Game', value: 'game' },
+ { label: 'Gender bender', value: 'gender-bender' },
+ { label: 'Harem', value: 'harem' },
+ { label: 'Historical', value: 'historical' },
+ { label: 'Horror', value: 'horror' },
+ { label: 'Isekai', value: 'isekai' },
+ { label: 'Josei', value: 'josei' },
+ { label: 'Lgbt+', value: 'lgbt+' },
+ { label: 'Litrpg', value: 'litrpg' },
+ { 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: 'Mystery', value: 'mystery' },
+ { label: 'Other', value: 'other' },
+ { label: 'Psychological', value: 'psychological' },
+ { label: 'Realistic', value: 'realistic' },
+ { label: 'Reincarnation', value: 'reincarnation' },
+ { 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: '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: 'System', value: 'system' },
+ { label: 'Thriller', value: 'thriller' },
+ { label: 'Tragedy', value: 'tragedy' },
+ { label: 'Urban', value: 'urban' },
+ { label: 'Video games', value: 'video-games' },
+ { label: 'War', value: 'war' },
+ { label: 'Wuxia', value: 'wuxia' },
+ { label: 'Xianxia', value: 'xianxia' },
+ { label: 'Xuanhuan', value: 'xuanhuan' },
+ { label: 'Yaoi', value: 'yaoi' },
+ { label: 'Yuri', value: 'yuri' },
+ ],
+ },
+ } satisfies Filters;
}
export default new NovelArrow();
From 44fb77f02b544dbcc297185a0431c6a71e4676ca Mon Sep 17 00:00:00 2001
From: 7ui77 <99854073+7ui77@users.noreply.github.com>
Date: Wed, 3 Jun 2026 11:42:35 +0700
Subject: [PATCH 18/23] Refactor filters and fix search for NovelArrow (v1.0.9)
---
plugins/english/novelarrow.ts | 55 +++++++++++++++++++++--------------
1 file changed, 33 insertions(+), 22 deletions(-)
diff --git a/plugins/english/novelarrow.ts b/plugins/english/novelarrow.ts
index 3d00fbfa9..947e70550 100644
--- a/plugins/english/novelarrow.ts
+++ b/plugins/english/novelarrow.ts
@@ -9,7 +9,7 @@ class NovelArrow implements Plugin {
name = 'Novel Arrow';
icon = 'https://novelarrow.com/favicon-32.png';
site = 'https://novelarrow.com/';
- version = '1.0.8';
+ version = '1.0.9';
// Headers cần thiết để vượt qua Cloudflare và giả lập trình duyệt di động
headers = {
@@ -23,11 +23,15 @@ class NovelArrow implements Plugin {
async popularNovels(page: number, { filters }: Plugin.PopularNovelsOptions) {
let url = this.site;
+
+ // Nếu có chọn thể loại (Filter bắt buộc theo yêu cầu user)
if (filters?.genre && filters.genre !== '') {
url += `genre/${filters.genre}?page=${page}`;
+ if (filters.language) url += `&language=${filters.language}`;
+ if (filters.sort) url += `&sort=${filters.sort}`;
} else {
- const category = filters?.category || 'latest';
- url += `novels/${category}?page=${page}`;
+ // Mặc định hiển thị danh sách mới nhất (Popular)
+ url += `novels/latest?page=${page}`;
}
const result = await fetchApi(url, { headers: this.headers }).then(res => res.text());
@@ -52,7 +56,6 @@ class NovelArrow implements Plugin {
}
async parseNovel(novelPath: string) {
- // novelPath có dạng "novel/slug"
const url = `${this.site}${novelPath}`;
const result = await fetchApi(url, { headers: this.headers }).then(res => res.text());
const $ = parseHTML(result);
@@ -70,7 +73,6 @@ class NovelArrow implements Plugin {
chapters: [],
};
- // Sử dụng API web để lấy đầy đủ danh sách chương
const chaptersUrl = `${this.site}api-web/novels/${novelId}/chapters?sort=asc`;
try {
const chaptersJson = await fetchApi(chaptersUrl, {
@@ -88,7 +90,6 @@ class NovelArrow implements Plugin {
}));
}
} catch (e) {
- // Fallback: Tìm bằng Regex trong stream JSON của Next.js
const chapterRegex = /\\?"chapter_id\\?":\\?"([^"]+)\\?",\\?"chapter_name\\?":\\?"([^"]+)\\?"/g;
let match;
const chaptersMap = new Map();
@@ -108,7 +109,6 @@ class NovelArrow implements Plugin {
}
async parseChapter(chapterPath: string) {
- // chapterPath có dạng "chapter/novel-slug/chapter-slug"
const pathParts = chapterPath.replace('chapter/', '').split('/');
const novelId = pathParts[0];
const chapterId = pathParts[1];
@@ -128,7 +128,6 @@ class NovelArrow implements Plugin {
return json.item.chapterInfo.chapter_content;
}
} catch (e) {
- // Fallback: Thử tải trang HTML (Sử dụng chính chapterPath làm slug)
const result = await fetchApi(`${this.site}${chapterPath}`, { headers: this.headers }).then(res => res.text());
const contentRegex = /\\u003ch4\\u003e(.*)\\u003c\/p\\u003e/;
const match = result.match(contentRegex);
@@ -159,7 +158,7 @@ class NovelArrow implements Plugin {
}
async searchNovels(searchTerm: string, page: number) {
- const url = `${this.site}novels/search?q=${encodeURIComponent(searchTerm)}&page=${page}`;
+ const url = `${this.site}novels/search?keyword=${encodeURIComponent(searchTerm)}&page=${page}`;
const result = await fetchApi(url, { headers: this.headers }).then(res => res.text());
const $ = parseHTML(result);
const novels: any[] = [];
@@ -173,7 +172,7 @@ class NovelArrow implements Plugin {
novels.push({
name: title,
cover,
- path: href.substring(1), // Kết quả: "novel/slug"
+ path: href.substring(1),
});
}
});
@@ -182,21 +181,10 @@ class NovelArrow implements Plugin {
}
readonly filters = {
- category: {
- label: 'Category',
- type: FilterTypes.Picker,
- options: [
- { label: 'Latest Updates', value: 'latest' },
- { label: 'Hot Novels', value: 'hot' },
- { label: 'Completed', value: 'complete' },
- { label: 'Ongoing', value: 'ongoing' },
- ],
- },
genre: {
- label: 'Genre',
+ label: 'Genre (Mandatory for filtering)',
type: FilterTypes.Picker,
options: [
- { label: 'None', value: '' },
{ label: 'Action', value: 'action' },
{ label: 'Adult', value: 'adult' },
{ label: 'Adventure', value: 'adventure' },
@@ -253,6 +241,29 @@ class NovelArrow implements Plugin {
{ label: 'Yuri', value: 'yuri' },
],
},
+ sort: {
+ label: 'Sort By',
+ type: FilterTypes.Picker,
+ options: [
+ { label: 'Latest', value: 'LASTEST' },
+ { label: 'New', value: 'NEW' },
+ { label: 'All Time', value: 'ALL_TIME' },
+ { label: 'Popular', value: 'POPULAR' },
+ { label: 'Rating', value: 'RATING' },
+ { label: 'Chapters', value: 'CHAPTERS' },
+ ],
+ },
+ language: {
+ label: 'Filter by Language',
+ type: FilterTypes.Picker,
+ options: [
+ { label: 'All', value: 'ALL' },
+ { label: 'English', value: 'EN' },
+ { label: 'Chinese', value: 'CN' },
+ { label: 'Japanese', value: 'JP' },
+ { label: 'Korean', value: 'KR' },
+ ],
+ },
} satisfies Filters;
}
From f25775802c3ab9f773aa53a4fffb6f32dfbe82a6 Mon Sep 17 00:00:00 2001
From: 7ui77 <99854073+7ui77@users.noreply.github.com>
Date: Wed, 3 Jun 2026 11:46:27 +0700
Subject: [PATCH 19/23] Separate popular from filters and restore v1.0.7
behavior (v1.1.0)
---
plugins/english/novelarrow.ts | 18 +++++++++++-------
1 file changed, 11 insertions(+), 7 deletions(-)
diff --git a/plugins/english/novelarrow.ts b/plugins/english/novelarrow.ts
index 947e70550..ffb94f0a0 100644
--- a/plugins/english/novelarrow.ts
+++ b/plugins/english/novelarrow.ts
@@ -9,7 +9,7 @@ class NovelArrow implements Plugin {
name = 'Novel Arrow';
icon = 'https://novelarrow.com/favicon-32.png';
site = 'https://novelarrow.com/';
- version = '1.0.9';
+ version = '1.1.0';
// Headers cần thiết để vượt qua Cloudflare và giả lập trình duyệt di động
headers = {
@@ -21,17 +21,20 @@ class NovelArrow implements Plugin {
'x-version-app': 'web-mobile',
};
- async popularNovels(page: number, { filters }: Plugin.PopularNovelsOptions) {
+ async popularNovels(page: number, { filters, showLatestNovels }: Plugin.PopularNovelsOptions) {
let url = this.site;
- // Nếu có chọn thể loại (Filter bắt buộc theo yêu cầu user)
- if (filters?.genre && filters.genre !== '') {
+ // Ưu tiên hiển thị danh sách Latest Updates (Giống v1.0.7) hoặc Hot/Popular nếu được chọn
+ if (showLatestNovels) {
+ url += `novels/latest?page=${page}`;
+ } else if (filters?.genre && filters.genre !== '') {
+ // Chế độ lọc nâng cao: Bắt buộc chọn Genre
url += `genre/${filters.genre}?page=${page}`;
if (filters.language) url += `&language=${filters.language}`;
if (filters.sort) url += `&sort=${filters.sort}`;
} else {
- // Mặc định hiển thị danh sách mới nhất (Popular)
- url += `novels/latest?page=${page}`;
+ // Mặc định cho Popular tab (v1.0.7 dùng latest, ở đây ta dùng hot/popular cho đúng nghĩa Popular)
+ url += `novels/popular?page=${page}`;
}
const result = await fetchApi(url, { headers: this.headers }).then(res => res.text());
@@ -47,7 +50,7 @@ class NovelArrow implements Plugin {
novels.push({
name: title,
cover,
- path: href.substring(1), // Kết quả: "novel/slug"
+ path: href.substring(1),
});
}
});
@@ -185,6 +188,7 @@ class NovelArrow implements Plugin {
label: 'Genre (Mandatory for filtering)',
type: FilterTypes.Picker,
options: [
+ { label: 'None', value: '' },
{ label: 'Action', value: 'action' },
{ label: 'Adult', value: 'adult' },
{ label: 'Adventure', value: 'adventure' },
From ff3f1d3ce01d1eeb6211706115f5204807fae195 Mon Sep 17 00:00:00 2001
From: 7ui77 <99854073+7ui77@users.noreply.github.com>
Date: Wed, 3 Jun 2026 11:52:16 +0700
Subject: [PATCH 20/23] Revert to stable version 1.0.7 behavior and remove
complex filters (v1.1.1)
---
plugins/english/novelarrow.ts | 110 ++--------------------------------
1 file changed, 4 insertions(+), 106 deletions(-)
diff --git a/plugins/english/novelarrow.ts b/plugins/english/novelarrow.ts
index ffb94f0a0..51a05b5f6 100644
--- a/plugins/english/novelarrow.ts
+++ b/plugins/english/novelarrow.ts
@@ -2,14 +2,13 @@ import { load as parseHTML } from 'cheerio';
import { fetchApi } from '@libs/fetch';
import { Plugin } from '@/types/plugin';
import { NovelStatus } from '@libs/novelStatus';
-import { Filters, FilterTypes } from '@libs/filterInputs';
class NovelArrow implements Plugin {
id = 'novelarrow';
name = 'Novel Arrow';
icon = 'https://novelarrow.com/favicon-32.png';
site = 'https://novelarrow.com/';
- version = '1.1.0';
+ version = '1.1.1';
// Headers cần thiết để vượt qua Cloudflare và giả lập trình duyệt di động
headers = {
@@ -21,22 +20,8 @@ class NovelArrow implements Plugin {
'x-version-app': 'web-mobile',
};
- async popularNovels(page: number, { filters, showLatestNovels }: Plugin.PopularNovelsOptions) {
- let url = this.site;
-
- // Ưu tiên hiển thị danh sách Latest Updates (Giống v1.0.7) hoặc Hot/Popular nếu được chọn
- if (showLatestNovels) {
- url += `novels/latest?page=${page}`;
- } else if (filters?.genre && filters.genre !== '') {
- // Chế độ lọc nâng cao: Bắt buộc chọn Genre
- url += `genre/${filters.genre}?page=${page}`;
- if (filters.language) url += `&language=${filters.language}`;
- if (filters.sort) url += `&sort=${filters.sort}`;
- } else {
- // Mặc định cho Popular tab (v1.0.7 dùng latest, ở đây ta dùng hot/popular cho đúng nghĩa Popular)
- url += `novels/popular?page=${page}`;
- }
-
+ async popularNovels(page: number) {
+ const url = `${this.site}novels/latest?page=${page}`;
const result = await fetchApi(url, { headers: this.headers }).then(res => res.text());
const $ = parseHTML(result);
const novels: any[] = [];
@@ -50,7 +35,7 @@ class NovelArrow implements Plugin {
novels.push({
name: title,
cover,
- path: href.substring(1),
+ path: href.substring(1), // Kết quả: "novel/slug"
});
}
});
@@ -182,93 +167,6 @@ class NovelArrow implements Plugin {
return novels;
}
-
- readonly filters = {
- genre: {
- label: 'Genre (Mandatory for filtering)',
- type: FilterTypes.Picker,
- options: [
- { label: 'None', value: '' },
- { label: 'Action', value: 'action' },
- { label: 'Adult', value: 'adult' },
- { label: 'Adventure', value: 'adventure' },
- { label: 'Anime & Comics', value: 'anime-&-comics' },
- { label: 'Comedy', value: 'comedy' },
- { label: 'Drama', value: 'drama' },
- { label: 'Eastern', value: 'eastern' },
- { label: 'Ecchi', value: 'ecchi' },
- { label: 'Fan-fiction', value: 'fan-fiction' },
- { label: 'Fantasy', value: 'fantasy' },
- { label: 'Game', value: 'game' },
- { label: 'Gender bender', value: 'gender-bender' },
- { label: 'Harem', value: 'harem' },
- { label: 'Historical', value: 'historical' },
- { label: 'Horror', value: 'horror' },
- { label: 'Isekai', value: 'isekai' },
- { label: 'Josei', value: 'josei' },
- { label: 'Lgbt+', value: 'lgbt+' },
- { label: 'Litrpg', value: 'litrpg' },
- { 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: 'Mystery', value: 'mystery' },
- { label: 'Other', value: 'other' },
- { label: 'Psychological', value: 'psychological' },
- { label: 'Realistic', value: 'realistic' },
- { label: 'Reincarnation', value: 'reincarnation' },
- { 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: '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: 'System', value: 'system' },
- { label: 'Thriller', value: 'thriller' },
- { label: 'Tragedy', value: 'tragedy' },
- { label: 'Urban', value: 'urban' },
- { label: 'Video games', value: 'video-games' },
- { label: 'War', value: 'war' },
- { label: 'Wuxia', value: 'wuxia' },
- { label: 'Xianxia', value: 'xianxia' },
- { label: 'Xuanhuan', value: 'xuanhuan' },
- { label: 'Yaoi', value: 'yaoi' },
- { label: 'Yuri', value: 'yuri' },
- ],
- },
- sort: {
- label: 'Sort By',
- type: FilterTypes.Picker,
- options: [
- { label: 'Latest', value: 'LASTEST' },
- { label: 'New', value: 'NEW' },
- { label: 'All Time', value: 'ALL_TIME' },
- { label: 'Popular', value: 'POPULAR' },
- { label: 'Rating', value: 'RATING' },
- { label: 'Chapters', value: 'CHAPTERS' },
- ],
- },
- language: {
- label: 'Filter by Language',
- type: FilterTypes.Picker,
- options: [
- { label: 'All', value: 'ALL' },
- { label: 'English', value: 'EN' },
- { label: 'Chinese', value: 'CN' },
- { label: 'Japanese', value: 'JP' },
- { label: 'Korean', value: 'KR' },
- ],
- },
- } satisfies Filters;
}
export default new NovelArrow();
From bf43ac2f330617d29b3e2aad4f76445dc1440a21 Mon Sep 17 00:00:00 2001
From: 7ui77 <99854073+7ui77@users.noreply.github.com>
Date: Wed, 3 Jun 2026 14:23:41 +0700
Subject: [PATCH 21/23] Improve parsing robustness and summary extraction for
all novels (v1.1.3)
---
plugins/english/novelarrow.ts | 45 +++++++++++++++++++++++++----------
1 file changed, 32 insertions(+), 13 deletions(-)
diff --git a/plugins/english/novelarrow.ts b/plugins/english/novelarrow.ts
index 51a05b5f6..17469e868 100644
--- a/plugins/english/novelarrow.ts
+++ b/plugins/english/novelarrow.ts
@@ -8,7 +8,7 @@ class NovelArrow implements Plugin {
name = 'Novel Arrow';
icon = 'https://novelarrow.com/favicon-32.png';
site = 'https://novelarrow.com/';
- version = '1.1.1';
+ version = '1.1.3';
// Headers cần thiết để vượt qua Cloudflare và giả lập trình duyệt di động
headers = {
@@ -35,7 +35,7 @@ class NovelArrow implements Plugin {
novels.push({
name: title,
cover,
- path: href.substring(1), // Kết quả: "novel/slug"
+ path: href.substring(1),
});
}
});
@@ -44,20 +44,39 @@ class NovelArrow implements Plugin {
}
async parseNovel(novelPath: string) {
- const url = `${this.site}${novelPath}`;
+ const url = this.site + novelPath.replace(/^\//, '');
const result = await fetchApi(url, { headers: this.headers }).then(res => res.text());
const $ = parseHTML(result);
- const novelId = novelPath.replace('novel/', '');
+ const novelId = novelPath.replace('novel/', '').replace(/^\//, '');
+
+ const genres: string[] = [];
+ $('meta[property="article:tag"]').each((i, el) => {
+ const tag = $(el).attr('content');
+ if (tag) genres.push(tag);
+ });
+
+ // Thử lấy tóm tắt đầy đủ từ stream JSON nếu meta bị cắt ngắn
+ let fullSummary = $('meta[name="description"]').attr('content') || $('meta[property="og:description"]').attr('content');
+ const summaryMatch = result.match(/\\?"description\\?":\\?"(.*?)\\?"/);
+ if (summaryMatch && summaryMatch[1].length > (fullSummary?.length || 0)) {
+ fullSummary = summaryMatch[1].replace(/\\n/g, '\n').replace(/\\"/g, '"');
+ }
+
const novel: any = {
path: novelPath,
name: $('meta[name="og:novel:novel_name"]').attr('content') ||
+ $('meta[property="og:novel:novel_name"]').attr('content') ||
$('meta[property="og:title"]').attr('content')?.split(' Novel')[0] ||
- $('h1').text().trim(),
- cover: $('meta[property="og:image"]').attr('content'),
- author: $('meta[name="og:novel:author"]').attr('content') || $('meta[name="author"]').attr('content'),
- status: $('meta[name="og:novel:status"]').attr('content') === 'Ongoing' ? NovelStatus.Ongoing : NovelStatus.Completed,
- summary: $('meta[name="description"]').attr('content') || $('meta[property="og:description"]').attr('content'),
+ $('h1').first().text().trim(),
+ cover: $('meta[property="og:image"]').attr('content') || $('meta[name="og:image"]').attr('content'),
+ author: $('meta[name="og:novel:author"]').attr('content') ||
+ $('meta[property="og:novel:author"]').attr('content') ||
+ $('meta[name="author"]').attr('content') ||
+ $('meta[property="article:author"]').attr('content'),
+ status: ($('meta[name="og:novel:status"]').attr('content') || $('meta[property="og:novel:status"]').attr('content')) === 'Ongoing' ? NovelStatus.Ongoing : NovelStatus.Completed,
+ summary: fullSummary,
+ genres: genres.join(', '),
chapters: [],
};
@@ -78,11 +97,11 @@ class NovelArrow implements Plugin {
}));
}
} catch (e) {
- const chapterRegex = /\\?"chapter_id\\?":\\?"([^"]+)\\?",\\?"chapter_name\\?":\\?"([^"]+)\\?"/g;
- let match;
const chaptersMap = new Map();
-
- while ((match = chapterRegex.exec(result)) !== null) {
+ // Sử dụng Regex linh hoạt hơn
+ const combinedRegex = /\\?"chapter_id\\?":\\?"([^"]+)\\?",\\?"chapter_name\\?":\\?"([^"]+)\\?"/g;
+ let match;
+ while ((match = combinedRegex.exec(result)) !== null) {
const path = match[1];
const name = match[2].replace(/\\"/g, '"');
const fullPath = `chapter/${novelId}/${path}`;
From 506d78e9f4987f5082bdcf29098a5855fbf0af89 Mon Sep 17 00:00:00 2001
From: 7ui77 <99854073+7ui77@users.noreply.github.com>
Date: Wed, 3 Jun 2026 14:28:03 +0700
Subject: [PATCH 22/23] Add robust genre extraction to novel information
(v1.1.4)
---
plugins/english/novelarrow.ts | 21 ++++++++++++++-------
1 file changed, 14 insertions(+), 7 deletions(-)
diff --git a/plugins/english/novelarrow.ts b/plugins/english/novelarrow.ts
index 17469e868..9a20afc29 100644
--- a/plugins/english/novelarrow.ts
+++ b/plugins/english/novelarrow.ts
@@ -8,7 +8,7 @@ class NovelArrow implements Plugin {
name = 'Novel Arrow';
icon = 'https://novelarrow.com/favicon-32.png';
site = 'https://novelarrow.com/';
- version = '1.1.3';
+ version = '1.1.4';
// Headers cần thiết để vượt qua Cloudflare và giả lập trình duyệt di động
headers = {
@@ -50,11 +50,18 @@ class NovelArrow implements Plugin {
const novelId = novelPath.replace('novel/', '').replace(/^\//, '');
- const genres: string[] = [];
- $('meta[property="article:tag"]').each((i, el) => {
- const tag = $(el).attr('content');
- if (tag) genres.push(tag);
- });
+ // Thu thập thể loại (Genre) - Hỗ trợ nhiều loại tag meta
+ let genres = $('meta[name="og:novel:genre"]').attr('content') ||
+ $('meta[property="og:novel:genre"]').attr('content');
+
+ if (!genres) {
+ const genreList: string[] = [];
+ $('meta[property="article:tag"]').each((i, el) => {
+ const tag = $(el).attr('content');
+ if (tag) genreList.push(tag);
+ });
+ genres = genreList.join(', ');
+ }
// Thử lấy tóm tắt đầy đủ từ stream JSON nếu meta bị cắt ngắn
let fullSummary = $('meta[name="description"]').attr('content') || $('meta[property="og:description"]').attr('content');
@@ -76,7 +83,7 @@ class NovelArrow implements Plugin {
$('meta[property="article:author"]').attr('content'),
status: ($('meta[name="og:novel:status"]').attr('content') || $('meta[property="og:novel:status"]').attr('content')) === 'Ongoing' ? NovelStatus.Ongoing : NovelStatus.Completed,
summary: fullSummary,
- genres: genres.join(', '),
+ genres: genres,
chapters: [],
};
From 639ca5e539d04ab17acd4996057dcaf11fc67921 Mon Sep 17 00:00:00 2001
From: 7ui77 <99854073+7ui77@users.noreply.github.com>
Date: Wed, 3 Jun 2026 15:13:07 +0700
Subject: [PATCH 23/23] Clean restore of accidentally modified files and
include NovelArrow plugin only (Final attempt)
---
docs/docs.md | 96 -----------------------------------
plugins/english/novelbuddy.ts | 34 ++-----------
2 files changed, 3 insertions(+), 127 deletions(-)
diff --git a/docs/docs.md b/docs/docs.md
index 8fc0b317a..e21efff0b 100644
--- a/docs/docs.md
+++ b/docs/docs.md
@@ -6,7 +6,6 @@
- [ChapterItem](#chapteritem)
- [Filters](#filters)
- [PluginSettings](#pluginsettings)
-- [PagePlugin](#pageplugin)
- [Using Cheerio](#using-cheerio)
- [Custom fetching functions](#custom-fetching-functions)
@@ -638,101 +637,6 @@ class ExamplePlugin implements Plugin.PluginBase {
---
-### PagePlugin
-
-`PagePlugin` is a specialized plugin type used for sites that split their chapter list across multiple pages (pagination). It extends `PluginBase` and adds methods to parse pages individually.
-
-To implement a `PagePlugin`, your class must implement `Plugin.PagePlugin`:
-
-```ts
-class ExamplePlugin implements Plugin.PagePlugin {
- ...
-}
-```
-
-#### Differences from PluginBase
-
-| Method / Field | Return Type / Type | Description |
-| --- | --- | --- |
-| [parseNovel(novelPath)](#pagepluginparsenovel) | `Promise` | Parses novel metadata. The returned object MUST include `totalPages` (the total number of chapter pages) and typically initiates the `chapters` array as empty `[]`. |
-| [parsePage(novelPath, page)](#pagepluginparsepage) | `Promise` | Parses a specific page's chapter list. `page` is passed as a string (representing the page number or identifier). Returns a `SourcePage` object. |
-
-#### PagePlugin::parseNovel
-
-```ts
-async parseNovel(
- novelPath: string
-): Promise
-```
-
-Unlike `PluginBase::parseNovel`, this method does not fetch the entire chapter list at once. Instead, it parses the metadata of the novel and identifies the total number of pages.
-
-##### Returns
-`SourceNovel & { totalPages: number }`
-- `totalPages`: The total number of pages of chapters available for this novel.
-
-##### Example:
-
-```ts
-async parseNovel(
- novelPath: string
-): Promise {
- const novel: Plugin.SourceNovel & { totalPages: number } = {
- path: novelPath,
- name: "Novel Name",
- cover: defaultCover,
- totalPages: 5, // total pages of chapters
- chapters: [],
- };
- return novel;
-}
-```
-
-#### PagePlugin::parsePage
-
-```ts
-async parsePage(
- novelPath: string,
- page: string
-): Promise
-```
-
-This method fetches and parses the chapters belonging to a specific page.
-
-##### Parameters
-- `novelPath`: The relative path to the novel.
-- `page`: The page number or page identifier as a string.
-
-##### Returns
-`SourcePage` An object containing a `chapters` array for that page:
-```ts
-type SourcePage = {
- chapters: ChapterItem[];
-};
-```
-
-##### Example:
-
-```ts
-async parsePage(
- novelPath: string,
- page: string
-): Promise {
- const chapters: Plugin.ChapterItem[] = [];
-
- // Parse chapters for the given page...
- chapters.push({
- name: "Chapter 1",
- path: novelPath + "/chapter-1",
- page: page,
- });
-
- return { chapters };
-}
-```
-
----
-
### Using Cheerio
### Custom fetching functions
diff --git a/plugins/english/novelbuddy.ts b/plugins/english/novelbuddy.ts
index 734be7083..898fcc9bf 100644
--- a/plugins/english/novelbuddy.ts
+++ b/plugins/english/novelbuddy.ts
@@ -4,30 +4,15 @@ import { Plugin } from '@/types/plugin';
import { Filters, FilterTypes } from '@libs/filterInputs';
import { NovelStatus } from '@libs/novelStatus';
-const fwnRegex = new RegExp(
- [
- '(?:[𝐟ᵮ𝑓𝒇𝒻𝓯𝕗𝖿𝗳𝙛𝚏ꬵꞙẝ𝖋ⓕfḟʃբᶠ⒡ſꊰʄ∱ᶂ𝘧]|\\bf)',
- '[𝚛ꭇᣴℾ𝚪𝛤𝜞𝝘𝞒ⲄГᎱᒥꭈⲅꮁⓡrŕṙřȑȓṛṝŗгՐɾᥬṟɍʳ⒭ɼѓᴦᶉ𝐫𝑟𝒓𝓇𝓻𝔯𝕣𝖗𝗋𝗿𝘳𝙧ᵲґᵣr]',
- '[əәⅇꬲꞓ⋴𝛆𝛜𝜀𝜖𝜺𝝐𝝴𝞊𝞮𝟄ⲉꮛ𐐩ꞒⲈ⍷𝑒𝓮𝕖𝖊𝘦𝗲𝗲𝙚𝒆𝔢𝖾𝐞Ҿҿⓔe⒠èᧉéᶒêɘἔềếễ૯ǝєεēҽɛểẽḕḗĕėëẻěȅȇẹệȩɇₑęḝḙḛ℮еԑѐӗᥱёἐἑἒἓἕℯe]+',
- '[𝐰ꝡ𝑤𝒘𝓌𝔀𝕨𝖜𝗐𝘄𝘸𝙬𝚠աẁꮃẃⓦ⍵ŵẇẅẘẉⱳὼὠὡὢὣωὤὥὦὧῲῳῴῶῷⱲѡԝᴡώᾠᾡᾢᾣᾤᾥᾦɯ𝝕𝟉𝞏w]',
- '[əәⅇꬲꞓ⋴𝛆𝛜𝜀𝜖𝜺𝝐𝝴𝞊𝞮𝟄ⲉꮛ𐐩ꞒⲈ⍷𝑒𝓮𝕖𝖊𝘦𝗲𝗲𝙚𝒆𝔢𝖾𝐞Ҿҿⓔe⒠èᧉéᶒêɘἔềếễ૯ǝєεēҽɛểẽḕḗĕėëẻěȅȇẹệȩɇₑęḝḙḛ℮еԑѐӗᥱёἐἑἒἓἕℯe]',
- '[ꮟᏏ𝐛𝘣𝒷𝔟𝓫𝖇𝖻𝑏𝙗𝕓𝒃𝗯𝚋♭ᑳᒈbᖚᕹᕺⓑḃḅҍъḇƃɓƅᖯƄЬᑲþƂ⒝ЪᶀᑿᒀᒂᒁᑾьƀҌѢѣᔎb]',
- '[ոռח𝒏𝓷𝙣𝑛𝖓𝔫𝗇𝗻ᥒⓝήnǹᴒńñᾗηṅňṇɲņṋṉղຖՌƞŋ⒩ภกɳпʼnлԉȠἠἡῃդᾐᾑᾒᾓᾔᾕᾖῄῆῇῂἢἣἤἥἦἧὴήበቡቢባቤብቦȵ𝛈𝜂𝜼𝝶𝞰𝕟延𝐧𝔫ᶇᵰᥥ∩n𝘯𝓃]',
- '(?:[ంംං૦௦۵ℴ𝑜𝒐ꬽ𝝄𝛔𝜎𝝈𝞂ჿ𝚘০୦ዐ𝛐𝗈𝞼ဝⲟ𝙤၀𐐬𝔬𐓪𝓸🇴⍤○ϙ🅾𝒪𝖮𝟢𝟶𝙾o𝗼𝕠𝜊𝐨𝝾𝞸ᐤオѳ᧐ᥲðoఠᦞՓòөӧóºōôǒȏŏồȭṏὄṑṓȯȫ๏ᴏőöѻоዐǭȱ০୦٥౦๐໐οօᴑ०੦ỏơờớỡởợọộǫøǿɵծὀὁόὸόὂὃὅం𝖔オⓞ೦൦]|告知)',
- '[∨⌄\\|ⅴ𝐯𝑣𝒗𝓋𝔳𝕧𝖛ꮩሀⓥv𝜐𝝊ṽṿ౮งѵעᴠνטᵥѷ៴ᘁ𝙫𝙫𝛎𝜈𝝂𝝼𝞶𝘷𝘃𝓿v⋁𝗏𝚟]',
- '[əәⅇꬲꞓ⋴𝛆𝛜𝜀𝜖𝜺𝝐𝝴𝞊𝞮𝟄ⲉꮛ𐐩ꞒⲈ⍷𝑒𝓮𝕖𝖊𝘦𝗲𝗲𝙚𝒆𝔢𝖾𝐞Ҿҿⓔe⒠èᧉéᶒêɘἔềếễ૯ǝєεēҽɛểẽḕḗĕėëẻěȅȇẹệȩɇₑęḝḙḛ℮еԑѐӗᥱёἐἑἒἓἕℯeₑ]',
- '[ⓛlŀĺľḷḹḷļӀℓḽḻłレɭƚɫⱡ\\|\\\\Ɩ⒧ʅǀוןΙІ|ᶩӏ𝓘𝕀𝖨𝗜𝘐𝐥𝑙𝒍𝓁𝔩𝕝𝖑ލ𝗅𝗹ލ𝗅𝗹ލ𝗅𝗹ލ𝘭𝜤𝝞ı𝚤ɩι𝛊𝜄𝜾𝞲Il𝙡𝓵]',
- '(?:.?(?:𝝌|c|ⅽ|𝐜|𝑐|𝒄|𝒸|𝓬|𝔠|𝕔|𝖈|𝖈|𝗰|𝘤|𝙘|𝚌|ᴄ|ϲ|ⲥ|с|ꮯ|𐐽|ⲥ|𐐽|ꮯ|ĉ|c|ⓒ|ć|č|ċ|ç|ҁ|ƈ|ḉ|ȼ|ↄ|с|ር|ᴄ|ϲ|ҫ|꒝|ς|ɽ|ϛ|𝙲|ᑦ|᧚|₵|🇨|ᥴ|ᒼ|ⅽ|c)(?:ం|ం|ം|ං|૦|௦|۵|ℴ|𝑜|𝒐|𝒐|ꬽ|𝝄|𝛔|𝜎|𝝈|𝞂|ჿ|𝚘|০|୦|ዐ|𝛐|𝗈|𝞼|ဝ|ⲟ|𝙤|၀|𐐬|𝔬|𐓪|𝓸|🇴|⍤|○|ϙ|🅾|𝒪|𝖮|𝟢|𝟶|𝙾|o|𝗼|𝕠|𝜊|𝐨|𝝾|𝞸|ᐤ|オ|ѳ|᧐|ᥲ|ð|o|ఠ|ᦞ|Փ|ò|ө|ӧ|ó|º|ō|ô|ǒ|ȏ|ŏ|ồ|ȭ|ṏ|ὄ|ṑ|ṓ|ȯ|ȫ|๏|ᴏ|ő|ö|ѻ|о|ዐ|ǭ|ȱ|০|୦|٥|౦|告知|๐|໐|ο|օ|ᴑ|०|੦|ỏ|ơ|ờ|ớ|ỡ|ở|ợ|ọ|ộ|ǫ|ø|ǿ|ɵ|ծ|ὀ|ὁ|ό|ὸ|ό|ὂ|ὃ|ὅ|o|ం|𝖔|𝘰|ⓞ|೦|൦)(?:₥|ᵯ|𝖒|𝐦|𝗆|𝔪|𝕞|𝓂|ⓜ|m|ന|ᙢ|൩|ḿ|ṁ|ⅿ|ϻ|ṃ|ጠ|ɱ|៳|ᶆ|𝒎|🇲|𝙢|𝓶|𝚖|𝑚|𝗺|᧕|᧗|m|𝖼|𝛐))?'
- ].join(''),
- 'gu',
-);
+const fwnRegex =
+ /(?:𝐟|ᵮ|𝑓|𝒇|𝒻|𝓯|𝔣|𝕗|𝖿|𝗳|𝙛|𝚏|ꬵ|ꞙ|ẝ|𝖋|ⓕ|f|ḟ|ʃ|բ|ᶠ|⒡|ſ|ꊰ|ʄ|∱|ᶂ|𝘧|\bf)(?:𝚛|ꭇ|ᣴ|ℾ|𝚪|𝛤|𝜞|𝝘|𝞒|Ⲅ|Г|Ꮁ|ᒥ|ꭈ|ⲅ|ꮁ|ⓡ|r|ŕ|ṙ|ř|ȑ|ȓ|ṛ|ṝ|ŗ|г|Ր|ɾ|ᥬ|ṟ|ɍ|ʳ|⒭|ɼ|ѓ|ᴦ|ᶉ|𝐫|𝑟|𝒓|𝓇|𝓻|𝔯|𝕣|𝖗|𝗋|𝗿|𝘳|𝙧|ᵲ|ґ|ᵣ|r)(?:ə|ә|ⅇ|ꬲ|ꞓ|⋴|𝛆|𝛜|𝜀|𝜖|𝜺|𝝐|𝝴|𝞊|𝞮|𝟄|ⲉ|ꮛ|𐐩|Ꞓ|Ⲉ|⍷|𝑒|𝓮|𝕖|𝖊|𝘦|𝗲|𝚎|𝙚|𝒆|𝔢|𝖾|𝐞|Ҿ|ҿ|ⓔ|e|⒠|è|ᧉ|é|ᶒ|ê|ɘ|ἔ|ề|ế|ễ|૯|ǝ|є|ε|ē|ҽ|ɛ|ể|ẽ|ḕ|ḗ|ĕ|ė|ë|ẻ|ě|ȅ|ȇ|ẹ|ệ|ȩ|ɇ|ₑ|ę|ḝ|ḙ|ḛ|℮|е|ԑ|ѐ|ӗ|ᥱ|ё|ἐ|ἑ|ἒ|ἓ|ἕ|ℯ|e)+(?:𝐰|ꝡ|𝑤|𝒘|𝓌|𝔀|𝔴|𝕨|𝖜|𝗐|𝘄|𝘸|𝙬|𝚠|ա|ẁ|ꮃ|ẃ|ⓦ|⍵|ŵ|ẇ|ẅ|ẘ|ẉ|ⱳ|ὼ|ὠ|ὡ|ὢ|ὣ|ω|ὤ|ὥ|ὦ|ὧ|ῲ|ῳ|ῴ|ῶ|ῷ|Ⱳ|ѡ|ԝ|ԝ|ᴡ|ώ|ᾠ|ᾡ|ᾡ|ᾢ|ᾣ|ᾤ|ᾥ|ᾦ|ɯ|𝝕|𝟉|𝞏|w)(?:ə|ә|ⅇ|ꬲ|ꞓ|⋴|𝛆|𝛜|𝜀|𝜖|𝜺|𝝐|𝝴|𝞊|𝞮|𝟄|ⲉ|ꮛ|𐐩|Ꞓ|Ⲉ|⍷|𝑒|𝓮|𝕖|𝖊|𝘦|𝗲|𝚎|𝙚|𝒆|𝔢|𝖾|𝐞|Ҿ|ҿ|ⓔ|e|⒠|è|ᧉ|é|ᶒ|ê|ɘ|ἔ|ề|ế|ễ|૯|ǝ|є|ε|ē|ҽ|ɛ|ể|ẽ|ḕ|ḗ|ĕ|ė|ë|ẻ|ě|ȅ|ȇ|ȇ|ẹ|ệ|ȩ|ɇ|ₑ|ę|ḝ|ḙ|ḛ|℮|е|ԑ|ѐ|ӗ|ᥱ|ё|ἐ|ἑ|ἒ|ἓ|ἕ|ℯ|e)(?:ꮟ|Ꮟ|𝐛|𝘣|𝒷|𝔟|𝓫|𝖇|𝖻|𝑏|𝙗|𝕓|𝒃|𝗯|𝚋|♭|ᑳ|ᒈ|b|ᖚ|ᕹ|ᕺ|ⓑ|ḃ|ḅ|ҍ|ъ|ḇ|ƃ|ɓ|ƅ|ᖯ|Ƅ|Ь|ᑲ|þ|Ƃ|⒝|Ъ|ᶀ|ᑿ|ᒀ|ᒂ|ᒁ|ᑾ|ь|ƀ|Ҍ|Ѣ|ѣ|ᔎ |b)(?:ո|ռ|ח|𝒏|𝓷|𝙣|𝑛|𝖓|𝔫|𝗇|𝚗|𝗻|ᥒ|ⓝ|ή|n|ǹ|ᴒ|ń|ñ|ᾗ|η|ṅ|ň|ṇ|ɲ|ņ|ṋ|ṉ|ղ|ຖ|Ռ|ƞ|ŋ|⒩|ภ|ก|ɳ|п|ʼn|л|ԉ|Ƞ|ἠ|ἡ|ῃ|դ|ᾐ|ᾑ|ᾒ|ᾓ|ᾔ|ᾕ|ᾖ|ῄ|ῆ|ῇ|ῂ|ἢ|ἣ|ἤ|ἥ|ἦ|ἧ|ὴ|ή|በ|ቡ|ቢ|ባ|ቤ|ብ|ቦ|ȵ|𝛈|𝜂|𝜼|𝝶|𝞰|𝕟|延|𝐧|𝔫|ᶇ|ᵰ|ᥥ|∩|n)(?:ం|ం|ം|ං|૦|௦|۵|ℴ|𝑜|𝒐|𝒐|ꬽ|𝝄|𝛔|𝜎|𝝈|𝞂|ჿ|𝚘|০|୦|ዐ|𝛐|𝗈|𝞼|ဝ|ⲟ|𝙤|၀|𐐬|𝔬|𐓪|𝓸|🇴|⍤|○|ϙ|🅾|𝒪|𝖮|𝟢|𝟶|𝙾|o|𝗼|𝕠|𝜊|𝐨|𝝾|𝞸|ᐤ|オ|ѳ|᧐|ᥲ|ð|o|ఠ|ᦞ|Փ|ò|ө|ӧ|ó|º|ō|ô|ǒ|ȏ|ŏ|ồ|ȭ|ṏ|ὄ|ṑ|ṓ|ȯ|ȫ|๏|ᴏ|ő|ö|ѻ|о|ዐ|ǭ|ȱ|০|୦|٥|౦|告知|๐|໐|ο|օ|ᴑ|०|੦|ỏ|ơ|ờ|ớ|ỡ|ở|ợ|ọ|ộ|ộ|ǫ|ø|ǿ|ɵ|ծ|ὀ|ὁ|ό|ὸ|ό|ὂ|ὃ|ὅ|o)(?:∨|⌄|\\|ⅴ|𝐯|𝑣|𝒗|𝓋|𝔳|𝕧|𝖛|ꮩ|ሀ|ⓥ|v|𝜐|𝝊|ṽ|ṿ|౮|ง|ѵ|ע|ᴠ|ν|ט|ᵥ|ѷ|៴|ᘁ|𝙫|𝙫|𝛎|𝜈|𝝂|𝝼|𝞶|𝘷|𝘃|𝓿|v)(?:ə|ә|ⅇ|ꬲ|ꞓ|⋴|𝛆|𝛜|𝜀|𝜖|𝜺|𝝐|𝝴|𝞊|𝞮|𝟄|ⲉ|ꮛ|𐐩|Ꞓ|Ⲉ|⍷|𝑒|𝓮|𝕖|𝖊|𝘦|𝗲|𝚎|𝙚|𝒆|𝔢|𝖾|𝐞|Ҿ|ҿ|ⓔ|e|⒠|è|ᧉ|é|ᶒ|ê|ɘ|ἔ|ề|ế|ễ|૯|ǝ|є|ε|ē|ҽ|ɛ|ể|ẽ|ḕ|ḗ|ĕ|ė|ë|ẻ|ě|ȅ|ȇ|ẹ|ệ|ȩ|ɇ|ₑ|ę|ḝ|ḙ|ḛ|℮|е|ԑ|ѐ|ӗ|ᥱ|ё|ἐ|ἑ|ἒ|ἓ|ἕ|ℯ|e)(?:ⓛ|l|ŀ|ĺ|ľ|ḷ|ḹ|ḷ|ļ|Ӏ|ℓ|ḽ|ḻ|ł|レ|ɭ|ƚ|ɫ|ⱡ|\\||\\\\|Ɩ|⒧|ʅ|ǀ|ו|ן|Ι|І|||ᶩ|ӏ|𝓘|𝕀|𝖨|𝗜|𝘐|𝐥|𝑙|𝒍|𝓁|𝔩|𝕝|𝖑|ލ|𝗅|𝗹|ލ|𝗅|𝗹|𝘭|𝚕|𝜤|𝝞|ı|𝚤|ɩ|ι|𝛊|𝜄|𝜾|𝞲|I|l)(?:.?(?:🝌|c|ⅽ|𝐜|𝑐|𝒄|𝒸|𝓬|𝔠|𝕔|𝖈|𝖈|𝗰|𝘤|𝙘|𝚌|ᴄ|ϲ|ⲥ|с|ꮯ|𐐽|ⲥ|𐐽|ꮯ|ĉ|c|ⓒ|ć|č|ċ|ç|ҁ|ƈ|ḉ|ȼ|ↄ|с|ር|ᴄ|ϲ|ҫ|꒝|ς|ɽ|ϛ|𝙲|ᑦ|᧚|𝐜|𝑐|𝒄|𝒸|𝓬|𝔠|𝕔|𝖈|𝖈|𝗰|𝘤|𝙘|𝚌|₵|🇨|ᥴ|ᒼ|ⅽ|c)(?:ం|ం|ം|ං|૦|௦|۵|ℴ|𝑜|𝒐|𝒐|ꬽ|𝝄|𝛔|𝜎|𝝈|𝞂|ჿ|𝚘|০|୦|ዐ|𝛐|𝗈|𝞼|ဝ|ⲟ|𝙤|၀|𐐬|𝔬|𐓪|𝓸|🇴|⍤|○|ϙ|🅾|𝒪|𝖮|𝟢|𝟶|𝙾|o|𝗼|𝕠|𝜊|𝐨|𝝾|𝞸|ᐤ|ⓞ|ѳ|᧐|ᥲ|ð|o|ఠ|ᦞ|Փ|ò|ө|ӧ|ó|º|ō|ô|ǒ|ȏ|ŏ|ồ|ȭ|ṏ|ὄ|ṑ|ṓ|ȯ|ȫ|๏|ᴏ|ő|ö|ѻ|о|ዐ|ǭ|ȱ|০|୦|٥|౦|告知|๐|໐|ο|օ|ᴑ|०|੦|ỏ|ơ|ờ|ớ|ỡ|ở|ợ|ọ|ộ|ộ|ǫ|ø|ǿ|ɵ|ծ|ὀ|ὁ|ό|ὸ|ό|ὂ|ὃ|ὅ|o)(?:₥|ᵯ|𝖒|𝐦|𝗆|𝔪|𝕞|𝕞|𝕞|ⓜ|m|ന|ᙢ|൩|ḿ|ṁ|ⅿ|ϻ|ṃ|ጠ|ɱ|៳|ᶆ|𝒎|🇲|𝙢|𝓶|𝚖|𝑚|𝗺|᧕|᧗|m))?/g;
class NovelBuddy implements Plugin.PluginBase {
id = 'novelbuddy';
name = 'NovelBuddy';
site = 'https://novelbuddy.com/';
api = 'https://api.novelbuddy.com/';
- version = '2.1.3';
+ version = '2.1.2';
icon = 'src/en/novelbuddy/icon.png';
parseNovels(body: Response): Plugin.NovelItem[] {
@@ -190,19 +175,6 @@ class NovelBuddy implements Plugin.PluginBase {
}
if (content) {
- content = content.replace(/<\/?p>/g, '');
- content = content.replace(
- /Nov[ᴇe]lFire admin to [^:]+ admin: "[^"]*"\.?/gi,
- '',
- );
- content = content.replace(
- /[ɴn][ᴇe]ᴡ [ɴn]ᴏᴠ[ᴇe]ʟ ᴄʜᴀᴘᴛ[ᴇe]ʀs ᴀʀ[ᴇe] ᴘᴜʙʟɪsʜ[ᴇe]ᴅ ᴏɴ Nov[ᴇe]lFir[ᴇe]/gi,
- '',
- );
- content = content.replace(
- /Find authorized novels in Webnovel.*?faster updates, better experience.*?Please click.*?for visiting\./gi,
- '',
- );
content = content.replace(
/Find authorized novels in Webnovel.*?faster updates, better experience.*?Please click www\.webnovel\.com for visiting\./gi,
'',