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, '',