From f2d9bb30dec2ace299695ff385f0867a5a365b4f Mon Sep 17 00:00:00 2001 From: 7ui77 <99854073+7ui77@users.noreply.github.com> Date: Sun, 17 May 2026 09:04:15 +0700 Subject: [PATCH 1/5] fix(novelbuddy): fix watermark regex for obfuscated watermarks Removed an accidental double pipe '||' in the 'l' group of fwnRegex which was causing partial matches and incomplete watermark removal. --- plugins/english/novelbuddy.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/english/novelbuddy.ts b/plugins/english/novelbuddy.ts index b30cf469c..58e82d00f 100644 --- a/plugins/english/novelbuddy.ts +++ b/plugins/english/novelbuddy.ts @@ -167,7 +167,9 @@ class NovelBuddy implements Plugin.PluginBase { ); // Remove obfuscated freewebnovel watermarks (e.g., free𝑤𝑒𝑏novel.com) - content = content.replace(/free.*?novel\.com/gi, ''); + const fwnRegex = + /(?:𝐟|ᵮ|𝑓|𝒇|𝒻|𝓯|𝔣|𝕗|𝖿|𝗳|𝙛|𝚏|ꬵ|ꞙ|ẝ|𝖋|ⓕ|f|ƒ|ḟ|ʃ|բ|ᶠ|⒡|ſ|ꊰ|ʄ|∱|ᶂ|𝘧|f)(?:𝚛|ꭇ|ᣴ|ℾ|𝚪|𝛤|𝜞|𝝘|𝞒|Ⲅ|Г|Ꮁ|ᒥ|ꭈ|ⲅ|ꮁ|ⓡ|r|ŕ|ṙ|ř|ȑ|ȓ|ṛ|ṝ|ŗ|г|Ր|ɾ|ᥬ|ṟ|ɍ|ʳ|⒭|ɼ|ѓ|ᴦ|ᶉ|𝐫|𝑟|𝒓|𝓇|𝓻|𝔯|𝕣|𝖗|𝗋|𝗿|𝘳|𝙧|ᵲ|ґ|ᵣ|r)(?:ə|ә|ⅇ|ꬲ|ꞓ|⋴|𝛆|𝛜|𝜀|𝜖|𝜺|𝝐|𝝴|𝞊|𝞮|𝟄|ⲉ|ꮛ|𐐩|Ꞓ|Ⲉ|⍷|𝑒|𝓮|𝕖|𝖊|𝘦|𝗲|𝚎|𝙚|𝒆|𝔢|𝖾|𝐞|Ҿ|ҿ|ⓔ|e|⒠|è|ᧉ|é|ᶒ|ê|ɘ|ἔ|ề|ế|ễ|૯|ǝ|є|ε|ē|ҽ|ɛ|ể|ẽ|ḕ|ḗ|ĕ|ė|ë|ẻ|ě|ȅ|ȇ|ẹ|ệ|ȩ|ɇ|ₑ|ę|ḝ|ḙ|ḛ|℮|е|ԑ|ѐ|ӗ|ᥱ|ё|ἐ|ἑ|ἒ|ἓ|ἕ|ℯ|e)+(?:𝐰|ꝡ|𝑤|𝒘|𝓌|𝔀|𝔴|𝕨|𝖜|𝗐|𝘄|𝘸|𝙬|𝚠|ա|ẁ|ꮃ|ẃ|ⓦ|⍵|ŵ|ẇ|ẅ|ẘ|ẉ|ⱳ|ὼ|ὠ|ὡ|ὢ|ὣ|ω|ὤ|ὥ|ὦ|ὧ|ῲ|ῳ|ῴ|ῶ|ῷ|Ⱳ|ѡ|ԝ|ᴡ|ώ|ᾠ|ᾡ|ᾡ|ᾢ|ᾣ|ᾤ|ᾥ|ᾦ|ɯ|𝝕|𝟉|𝞏|w)(?:ə|ә|ⅇ|ꬲ|ꞓ|⋴|𝛆|𝛜|𝜀|𝜖|𝜺|𝝐|𝝴|𝞊|𝞮|𝟄|ⲉ|ꮛ|𐐩|Ꞓ|Ⲉ|⍷|𝑒|𝓮|𝕖|𝖊|𝘦|𝗲|𝚎|𝙚|𝒆|𝔢|𝖾|𝐞|Ҿ|ҿ|ⓔ|e|⒠|è|ᧉ|é|ᶒ|ê|ɘ|ἔ|ề|ế|ễ|૯|ǝ|є|ε|ē|ҽ|ɛ|ể|ẽ|ḕ|ḗ|ĕ|ė|ë|ẻ|ě|ȅ|ȇ|ẹ|ệ|ȩ|ɇ|ₑ|ę|ḝ|ḙ|ḛ|℮|е|ԑ|ѐ|ӗ|ᥱ|ё|ἐ|ἑ|ἒ|ἓ|ἕ|ℯ|e)(?:ꮟ|Ꮟ|𝐛|𝘣|𝒷|𝔟|𝓫|𝖇|𝖻|𝑏|𝙗|𝕓|𝒃|𝗯|𝚋|♭|ᑳ|ᒈ|b|ᖚ|ᕹ|ᕺ|ⓑ|ḃ|ḅ|ҍ|ъ|ḇ|ƃ|ɓ|ƅ|ᖯ|Ƅ|Ь|ᑲ|þ|Ƃ|⒝|Ъ|ᶀ|ᑿ|ᒀ|ᒂ|ᒁ|ᑾ|ь|ƀ|Ҍ|Ѣ|ѣ|ᔎ|b)(?:ո|ռ|ח|𝒏|𝓷|𝙣|𝑛|𝖓|𝔫|𝗇|𝚗|𝗻|ᥒ|ⓝ|ή|n|ǹ|ᴒ|ń|ñ|ᾗ|η|ṅ|ň|ṇ|ɲ|ņ|ṋ|ṉ|ղ|ຖ|Ռ|ƞ|ŋ|⒩|ภ|ก|ɳ|п|ʼn|л|ԉ|Ƞ|ἠ|ἡ|ῃ|դ|ᾐ|ᾑ|ᾒ|ᾓ|ᾔ|ᾕ|ᾖ|ῄ|ῆ|ῇ|ῂ|ἢ|ἣ|ἤ|ἥ|ἦ|ἧ|ὴ|ή|በ|ቡ|ቢ|ባ|ቤ|ብ|ቦ|ȵ|𝛈|𝜂|𝜼|𝝶|𝞰|𝕟|𝘯|𝐧|𝓃|ᶇ|ᵰ|ᥥ|∩|n)(?:ం|ಂ|ം|ං|૦|௦|۵|ℴ|𝑜|𝒐|𝒐|ꬽ|𝝄|𝛔|𝜎|𝝈|𝞂|ჿ|𝚘|০|୦|ዐ|𝛐|𝗈|𝞼|ဝ|ⲟ|𝙤|၀|𐐬|𝔬|𐓪|𝓸|🇴|⍤|○|ϙ|🅾|𝒪|𝖮|𝟢|𝟶|𝙾|𝘰|𝗼|𝕠|𝜊|𝐨|𝝾|𝞸|ᐤ|ⓞ|ѳ|᧐|ᥲ|ð|o|ఠ|ᦞ|Փ|ò|ө|ӧ|ó|º|ō|ô|ǒ|ȏ|ŏ|ồ|ȭ|ṏ|ὄ|ṑ|ṓ|ȯ|ȫ|๏|ᴏ|ő|ö|ѻ|о|ዐ|ǭ|ȱ|০|୦|٥|౦|೦|൦|๐|໐|ο|օ|ᴑ|०|੦|ỏ|ơ|ờ|ớ|ỡ|ở|ợ|ọ|ộ|ǫ|ø|ǿ|ɵ|ծ|ὀ|ὁ|ό|ὸ|ό|ὂ|ὃ|ὅ|o)(?:∨|⌄||ⅴ|𝐯|𝑣|𝒗|𝓋|𝔳|𝕧|𝖛|𝗏|ꮩ|ሀ|ⓥ|v|𝜐|𝝊|ṽ|ṿ|౮|ง|ѵ|ע|ᴠ|ν|ט|ᵥ|ѷ|៴|ᘁ|𝙫|𝚟|𝛎|𝜈|𝝂|𝝼|𝞶|𝘷|𝘃|𝓿|v)(?:ə|ә|ⅇ|ꬲ|ꞓ|⋴|𝛆|𝛜|𝜀|𝜖|𝜺|𝝐|𝝴|𝞊|𝞮|𝟄|ⲉ|ꮛ|𐐩|Ꞓ|Ⲉ|⍷|𝑒|𝓮|𝕖|𝖊|𝘦|𝗲|𝚎|𝙚|𝒆|𝔢|𝖾|𝐞|Ҿ|ҿ|ⓔ|e|⒠|è|ᧉ|é|ᶒ|ê|ɘ|ἔ|ề|ế|ễ|૯|ǝ|є|ε|ē|ҽ|ɛ|ể|ẽ|ḕ|ḗ|ĕ|ė|ë|ẻ|ě|ȅ|ȇ|ẹ|ệ|ȩ|ɇ|ₑ|ę|ḝ|ḙ|ḛ|℮|е|ԑ|ѐ|ӗ|ᥱ|ё|ἐ|ἑ|ἒ|ἓ|ἕ|ℯ|e)(?:ⓛ|l|ŀ|ĺ|ľ|ḷ|ḹ|ḷ|ļ|Ӏ|ℓ|ḽ|ḻ|ł|レ|ɭ|ƚ|ɫ|ⱡ|\\|Ɩ|⒧|ʅ|ǀ|ו|ן|Ι|І|||ᶩ|ӏ|𝓘|𝕀|𝖨|𝗜|𝘐|𝐥|𝑙|𝒍|𝓁|𝔩|𝕝|𝖑|𝗅|𝗹|𝘭|𝚕|𝜤|𝝞|ı|𝚤|ɩ|ι|𝛊|𝜄|𝜾|𝞲|I|l)(?:.?(?:🝌|c|ⅽ|𝐜|𝑐|𝒄|𝒸|𝓬|𝔠|𝕔|𝖈|𝖼|𝗰|𝘤|𝙘|𝚌|ᴄ|ϲ|ⲥ|с|ꮯ|𐐽|ⲥ|𐐽|ꮯ|ĉ|c|ⓒ|ć|č|ċ|ç|ҁ|ƈ|ḉ|ȼ|ↄ|с|ር|ᴄ|ϲ|ҫ|꒝|ς|ɽ|ϛ|𝙲|ᑦ|᧚|𝐜|𝑐|𝒄|𝒸|𝓬|𝔠|𝕔|𝖈|𝖼|𝗰|𝘤|𝙘|𝚌|₵|🇨|ᥴ|ᒼ|ⅽ|c)(?:ం|ಂ|ം|ං|૦|௦|۵|ℴ|𝑜|𝒐|𝒐|ꬽ|𝝄|𝛔|𝜎|𝝈|𝞂|ჿ|𝚘|০|୦|ዐ|𝛐|𝗈|𝞼|ဝ|ⲟ|𝙤|၀|𐐬|𝔬|𐓪|𝓸|🇴|⍤|○|ϙ|🅾|𝒪|𝖮|𝟢|𝟶|𝙾|オ|𝗼|𝕠|𝜊|𝐨|𝝾|𝞸|ᐤ|ⓞ|ѳ|᧐|ᥲ|ð|o|ఠ|ᦞ|Փ|ò|ө|ӧ|ó|º|ō|ô|ǒ|ȏ|ŏ|ồ|ȭ|ṏ|ὄ|ṑ|ṓ|ȯ|ȫ|๏|ᴏ|ő|ö|ѻ|о|ዐ|ǭ|ȱ|০|୦|٥|౦|告知|๐|໐|ο|օ|ᴑ|०|੦|ỏ|ơ|ờ|ớ|ỡ|ở|ợ|ọ|ộ|ǫ|ø|ǿ|ɵ|ծ|ὀ|ὁ|ό|ὸ|ό|ὂ|ὃ|ὅ|o)(?:₥|ᵯ|𝖒|𝐦|𝗆|𝔪|𝕞|𝓂|ⓜ|m|ന|ᙢ|൩|m|ḿ|ṁ|ⅿ|ϻ|ṃ|ጠ|ɱ|៳|ᶆ|𝒎|𝙢|𝓶|𝚖|𝑚|𝗺|᧕|᧗|m))?/g; + content = content.replace(fwnRegex, ''); } return content; From e19204e6e47e62623776504e945328aadb563f65 Mon Sep 17 00:00:00 2001 From: 7ui77 <99854073+7ui77@users.noreply.github.com> Date: Sun, 17 May 2026 09:08:53 +0700 Subject: [PATCH 2/5] chore(novelbuddy): bump version to 2.1.2 --- 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 58e82d00f..fa624fefb 100644 --- a/plugins/english/novelbuddy.ts +++ b/plugins/english/novelbuddy.ts @@ -9,7 +9,7 @@ class NovelBuddy implements Plugin.PluginBase { name = 'NovelBuddy'; site = 'https://novelbuddy.com/'; api = 'https://api.novelbuddy.com/'; - version = '2.1.1'; + version = '2.1.2'; icon = 'src/en/novelbuddy/icon.png'; parseNovels(body: Response): Plugin.NovelItem[] { @@ -168,7 +168,7 @@ class NovelBuddy implements Plugin.PluginBase { // Remove obfuscated freewebnovel watermarks (e.g., free𝑤𝑒𝑏novel.com) const fwnRegex = - /(?:𝐟|ᵮ|𝑓|𝒇|𝒻|𝓯|𝔣|𝕗|𝖿|𝗳|𝙛|𝚏|ꬵ|ꞙ|ẝ|𝖋|ⓕ|f|ƒ|ḟ|ʃ|բ|ᶠ|⒡|ſ|ꊰ|ʄ|∱|ᶂ|𝘧|f)(?:𝚛|ꭇ|ᣴ|ℾ|𝚪|𝛤|𝜞|𝝘|𝞒|Ⲅ|Г|Ꮁ|ᒥ|ꭈ|ⲅ|ꮁ|ⓡ|r|ŕ|ṙ|ř|ȑ|ȓ|ṛ|ṝ|ŗ|г|Ր|ɾ|ᥬ|ṟ|ɍ|ʳ|⒭|ɼ|ѓ|ᴦ|ᶉ|𝐫|𝑟|𝒓|𝓇|𝓻|𝔯|𝕣|𝖗|𝗋|𝗿|𝘳|𝙧|ᵲ|ґ|ᵣ|r)(?:ə|ә|ⅇ|ꬲ|ꞓ|⋴|𝛆|𝛜|𝜀|𝜖|𝜺|𝝐|𝝴|𝞊|𝞮|𝟄|ⲉ|ꮛ|𐐩|Ꞓ|Ⲉ|⍷|𝑒|𝓮|𝕖|𝖊|𝘦|𝗲|𝚎|𝙚|𝒆|𝔢|𝖾|𝐞|Ҿ|ҿ|ⓔ|e|⒠|è|ᧉ|é|ᶒ|ê|ɘ|ἔ|ề|ế|ễ|૯|ǝ|є|ε|ē|ҽ|ɛ|ể|ẽ|ḕ|ḗ|ĕ|ė|ë|ẻ|ě|ȅ|ȇ|ẹ|ệ|ȩ|ɇ|ₑ|ę|ḝ|ḙ|ḛ|℮|е|ԑ|ѐ|ӗ|ᥱ|ё|ἐ|ἑ|ἒ|ἓ|ἕ|ℯ|e)+(?:𝐰|ꝡ|𝑤|𝒘|𝓌|𝔀|𝔴|𝕨|𝖜|𝗐|𝘄|𝘸|𝙬|𝚠|ա|ẁ|ꮃ|ẃ|ⓦ|⍵|ŵ|ẇ|ẅ|ẘ|ẉ|ⱳ|ὼ|ὠ|ὡ|ὢ|ὣ|ω|ὤ|ὥ|ὦ|ὧ|ῲ|ῳ|ῴ|ῶ|ῷ|Ⱳ|ѡ|ԝ|ᴡ|ώ|ᾠ|ᾡ|ᾡ|ᾢ|ᾣ|ᾤ|ᾥ|ᾦ|ɯ|𝝕|𝟉|𝞏|w)(?:ə|ә|ⅇ|ꬲ|ꞓ|⋴|𝛆|𝛜|𝜀|𝜖|𝜺|𝝐|𝝴|𝞊|𝞮|𝟄|ⲉ|ꮛ|𐐩|Ꞓ|Ⲉ|⍷|𝑒|𝓮|𝕖|𝖊|𝘦|𝗲|𝚎|𝙚|𝒆|𝔢|𝖾|𝐞|Ҿ|ҿ|ⓔ|e|⒠|è|ᧉ|é|ᶒ|ê|ɘ|ἔ|ề|ế|ễ|૯|ǝ|є|ε|ē|ҽ|ɛ|ể|ẽ|ḕ|ḗ|ĕ|ė|ë|ẻ|ě|ȅ|ȇ|ẹ|ệ|ȩ|ɇ|ₑ|ę|ḝ|ḙ|ḛ|℮|е|ԑ|ѐ|ӗ|ᥱ|ё|ἐ|ἑ|ἒ|ἓ|ἕ|ℯ|e)(?:ꮟ|Ꮟ|𝐛|𝘣|𝒷|𝔟|𝓫|𝖇|𝖻|𝑏|𝙗|𝕓|𝒃|𝗯|𝚋|♭|ᑳ|ᒈ|b|ᖚ|ᕹ|ᕺ|ⓑ|ḃ|ḅ|ҍ|ъ|ḇ|ƃ|ɓ|ƅ|ᖯ|Ƅ|Ь|ᑲ|þ|Ƃ|⒝|Ъ|ᶀ|ᑿ|ᒀ|ᒂ|ᒁ|ᑾ|ь|ƀ|Ҍ|Ѣ|ѣ|ᔎ|b)(?:ո|ռ|ח|𝒏|𝓷|𝙣|𝑛|𝖓|𝔫|𝗇|𝚗|𝗻|ᥒ|ⓝ|ή|n|ǹ|ᴒ|ń|ñ|ᾗ|η|ṅ|ň|ṇ|ɲ|ņ|ṋ|ṉ|ղ|ຖ|Ռ|ƞ|ŋ|⒩|ภ|ก|ɳ|п|ʼn|л|ԉ|Ƞ|ἠ|ἡ|ῃ|դ|ᾐ|ᾑ|ᾒ|ᾓ|ᾔ|ᾕ|ᾖ|ῄ|ῆ|ῇ|ῂ|ἢ|ἣ|ἤ|ἥ|ἦ|ἧ|ὴ|ή|በ|ቡ|ቢ|ባ|ቤ|ብ|ቦ|ȵ|𝛈|𝜂|𝜼|𝝶|𝞰|𝕟|𝘯|𝐧|𝓃|ᶇ|ᵰ|ᥥ|∩|n)(?:ం|ಂ|ം|ං|૦|௦|۵|ℴ|𝑜|𝒐|𝒐|ꬽ|𝝄|𝛔|𝜎|𝝈|𝞂|ჿ|𝚘|০|୦|ዐ|𝛐|𝗈|𝞼|ဝ|ⲟ|𝙤|၀|𐐬|𝔬|𐓪|𝓸|🇴|⍤|○|ϙ|🅾|𝒪|𝖮|𝟢|𝟶|𝙾|𝘰|𝗼|𝕠|𝜊|𝐨|𝝾|𝞸|ᐤ|ⓞ|ѳ|᧐|ᥲ|ð|o|ఠ|ᦞ|Փ|ò|ө|ӧ|ó|º|ō|ô|ǒ|ȏ|ŏ|ồ|ȭ|ṏ|ὄ|ṑ|ṓ|ȯ|ȫ|๏|ᴏ|ő|ö|ѻ|о|ዐ|ǭ|ȱ|০|୦|٥|౦|೦|൦|๐|໐|ο|օ|ᴑ|०|੦|ỏ|ơ|ờ|ớ|ỡ|ở|ợ|ọ|ộ|ǫ|ø|ǿ|ɵ|ծ|ὀ|ὁ|ό|ὸ|ό|ὂ|ὃ|ὅ|o)(?:∨|⌄||ⅴ|𝐯|𝑣|𝒗|𝓋|𝔳|𝕧|𝖛|𝗏|ꮩ|ሀ|ⓥ|v|𝜐|𝝊|ṽ|ṿ|౮|ง|ѵ|ע|ᴠ|ν|ט|ᵥ|ѷ|៴|ᘁ|𝙫|𝚟|𝛎|𝜈|𝝂|𝝼|𝞶|𝘷|𝘃|𝓿|v)(?:ə|ә|ⅇ|ꬲ|ꞓ|⋴|𝛆|𝛜|𝜀|𝜖|𝜺|𝝐|𝝴|𝞊|𝞮|𝟄|ⲉ|ꮛ|𐐩|Ꞓ|Ⲉ|⍷|𝑒|𝓮|𝕖|𝖊|𝘦|𝗲|𝚎|𝙚|𝒆|𝔢|𝖾|𝐞|Ҿ|ҿ|ⓔ|e|⒠|è|ᧉ|é|ᶒ|ê|ɘ|ἔ|ề|ế|ễ|૯|ǝ|є|ε|ē|ҽ|ɛ|ể|ẽ|ḕ|ḗ|ĕ|ė|ë|ẻ|ě|ȅ|ȇ|ẹ|ệ|ȩ|ɇ|ₑ|ę|ḝ|ḙ|ḛ|℮|е|ԑ|ѐ|ӗ|ᥱ|ё|ἐ|ἑ|ἒ|ἓ|ἕ|ℯ|e)(?:ⓛ|l|ŀ|ĺ|ľ|ḷ|ḹ|ḷ|ļ|Ӏ|ℓ|ḽ|ḻ|ł|レ|ɭ|ƚ|ɫ|ⱡ|\\|Ɩ|⒧|ʅ|ǀ|ו|ן|Ι|І|||ᶩ|ӏ|𝓘|𝕀|𝖨|𝗜|𝘐|𝐥|𝑙|𝒍|𝓁|𝔩|𝕝|𝖑|𝗅|𝗹|𝘭|𝚕|𝜤|𝝞|ı|𝚤|ɩ|ι|𝛊|𝜄|𝜾|𝞲|I|l)(?:.?(?:🝌|c|ⅽ|𝐜|𝑐|𝒄|𝒸|𝓬|𝔠|𝕔|𝖈|𝖼|𝗰|𝘤|𝙘|𝚌|ᴄ|ϲ|ⲥ|с|ꮯ|𐐽|ⲥ|𐐽|ꮯ|ĉ|c|ⓒ|ć|č|ċ|ç|ҁ|ƈ|ḉ|ȼ|ↄ|с|ር|ᴄ|ϲ|ҫ|꒝|ς|ɽ|ϛ|𝙲|ᑦ|᧚|𝐜|𝑐|𝒄|𝒸|𝓬|𝔠|𝕔|𝖈|𝖼|𝗰|𝘤|𝙘|𝚌|₵|🇨|ᥴ|ᒼ|ⅽ|c)(?:ం|ಂ|ം|ං|૦|௦|۵|ℴ|𝑜|𝒐|𝒐|ꬽ|𝝄|𝛔|𝜎|𝝈|𝞂|ჿ|𝚘|০|୦|ዐ|𝛐|𝗈|𝞼|ဝ|ⲟ|𝙤|၀|𐐬|𝔬|𐓪|𝓸|🇴|⍤|○|ϙ|🅾|𝒪|𝖮|𝟢|𝟶|𝙾|オ|𝗼|𝕠|𝜊|𝐨|𝝾|𝞸|ᐤ|ⓞ|ѳ|᧐|ᥲ|ð|o|ఠ|ᦞ|Փ|ò|ө|ӧ|ó|º|ō|ô|ǒ|ȏ|ŏ|ồ|ȭ|ṏ|ὄ|ṑ|ṓ|ȯ|ȫ|๏|ᴏ|ő|ö|ѻ|о|ዐ|ǭ|ȱ|০|୦|٥|౦|告知|๐|໐|ο|օ|ᴑ|०|੦|ỏ|ơ|ờ|ớ|ỡ|ở|ợ|ọ|ộ|ǫ|ø|ǿ|ɵ|ծ|ὀ|ὁ|ό|ὸ|ό|ὂ|ὃ|ὅ|o)(?:₥|ᵯ|𝖒|𝐦|𝗆|𝔪|𝕞|𝓂|ⓜ|m|ന|ᙢ|൩|m|ḿ|ṁ|ⅿ|ϻ|ṃ|ጠ|ɱ|៳|ᶆ|𝒎|𝙢|𝓶|𝚖|𝑚|𝗺|᧕|᧗|m))?/g; + /(?:𝐟|ᵮ|𝑓|𝒇|𝒻|𝓯|𝔣|𝕗|𝖿|𝗳|𝙛|𝚏|ꬵ|ꞙ|ẝ|𝖋|ⓕ|f|ƒ|ḟ|ʃ|բ|ᶠ|⒡|ſ|ꊰ|ʄ|∱|ᶂ|𝘧|f)(?:𝚛|ꭇ|ᣴ|ℾ|𝚪|𝛤|𝜞|𝝘|𝞒|Ⲅ|Г|Ꮁ|ᒥ|ꭈ|ⲅ|ꮁ|ⓡ|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)(?:₥|ᵯ|𝖒|𝐦|𝗆|𝔪|𝕞|𝓂|ⓜ|m|ന|ᙢ|൩|m|ḿ|ṁ|ⅿ|ϻ|ṃ|ጠ|ɱ|៳|ᶆ|𝒎|𝙢|𝓶|𝚖|𝑚|𝗺|᧕|᧗|m))?/g; content = content.replace(fwnRegex, ''); } From aae881bd9932165e9cb942b8f9d5f328a2975722 Mon Sep 17 00:00:00 2001 From: 7ui77 <99854073+7ui77@users.noreply.github.com> Date: Sun, 17 May 2026 10:29:20 +0700 Subject: [PATCH 3/5] fix(novelbuddy): fix watermark regex, restore api fetch, keep comments, bump version to 2.1.3 --- plugins/english/novelbuddy.ts | 284 ++++++---------------------------- 1 file changed, 45 insertions(+), 239 deletions(-) diff --git a/plugins/english/novelbuddy.ts b/plugins/english/novelbuddy.ts index fa624fefb..392ea34da 100644 --- a/plugins/english/novelbuddy.ts +++ b/plugins/english/novelbuddy.ts @@ -9,7 +9,7 @@ class NovelBuddy implements Plugin.PluginBase { name = 'NovelBuddy'; site = 'https://novelbuddy.com/'; api = 'https://api.novelbuddy.com/'; - version = '2.1.2'; + version = '2.1.3'; icon = 'src/en/novelbuddy/icon.png'; parseNovels(body: Response): Plugin.NovelItem[] { @@ -26,33 +26,23 @@ class NovelBuddy implements Plugin.PluginBase { ): Promise { const { genre, min_ch, max_ch, status, demo, orderBy, keyword } = filters; - // Chapter bounds must be an integer between 0 and 10,000 or api cri const parseNumber = (val?: string) => { if (!val?.trim()) return; - const n = Number(val); - return Number.isInteger(n) && n >= 0 && n <= 10000 - ? String(n) - : undefined; - }; - - const rawParams: Record = { - genres: genre.value.include?.join(',') || undefined, - exclude: genre.value.exclude?.join(',') || undefined, - min_ch: parseNumber(min_ch.value), - max_ch: parseNumber(max_ch.value), - status: String(status.value), - demographic: demo.value?.join(',') || undefined, - sort: String(orderBy.value), - page: String(pageNo), - limit: '24', - q: keyword.value || undefined, + return Number.isInteger(n) && n >= 0 && n <= 10000 ? String(n) : undefined; }; - // Filter out the undefined values - const params = Object.fromEntries( - Object.entries(rawParams).filter(([, value]) => value !== undefined), - ) as Record; + const params: Record = {}; + if (genre.value.include?.length) params.genres = genre.value.include.join(','); + if (genre.value.exclude?.length) params.exclude = genre.value.exclude.join(','); + if (parseNumber(min_ch.value)) params.min_ch = parseNumber(min_ch.value)!; + if (parseNumber(max_ch.value)) params.max_ch = parseNumber(max_ch.value)!; + if (status.value !== 'all') params.status = String(status.value); + if (demo.value?.length) params.demographic = demo.value.join(','); + if (orderBy.value) params.sort = String(orderBy.value); + params.page = String(pageNo); + params.limit = '24'; + if (keyword.value) params.q = keyword.value; const url = new URL('titles/search', this.api); url.search = new URLSearchParams(params).toString(); @@ -73,7 +63,6 @@ class NovelBuddy implements Plugin.PluginBase { const data: NovelScript = JSON.parse(script); const initialManga = data.props.pageProps.initialManga; - if (!initialManga) throw new Error('Could not find initialManga data'); const novel: Plugin.SourceNovel = { @@ -86,16 +75,14 @@ class NovelBuddy implements Plugin.PluginBase { chapters: [], }; - const rawStatus = initialManga.status; - const map: Record = { + const statusMap: Record = { ongoing: NovelStatus.Ongoing, hiatus: NovelStatus.OnHiatus, dropped: NovelStatus.Cancelled, cancelled: NovelStatus.Cancelled, completed: NovelStatus.Completed, - unknown: NovelStatus.Unknown, }; - novel.status = map[rawStatus.toLowerCase()] ?? NovelStatus.Unknown; + novel.status = statusMap[initialManga.status.toLowerCase()] ?? NovelStatus.Unknown; // Wrap in
before passing to $(): when the API returns plain text (no leading // `<` tag) cheerio's $() treats the input as a CSS selector, and any `.` in the text @@ -105,42 +92,21 @@ class NovelBuddy implements Plugin.PluginBase { const summary = $('
' + (initialManga.summary || '') + '
'); summary.find('br').replaceWith('\n'); summary.find('p').before('\n').after('\n\n'); + novel.summary = summary.text().split('\n').map(line => line.trim()).join('\n').replace(/\n{3,}/g, '\n\n').trim(); - novel.summary = - summary - .text() - .split('\n') - .map(line => line.trim()) - .join('\n') - ?.replace(/\n{3,}/g, '\n\n') - .trim() || 'Summary Not Found'; + if (initialManga.ratingStats) novel.rating = initialManga.ratingStats.average; - if (initialManga.ratingStats) { - novel.rating = initialManga.ratingStats.average; - } - - // Fetch full chapter list from API + // Fetch chapters from API, fallback to initialManga data const chaptersUrl = `${this.api}titles/${initialManga.id}/chapters`; - const chaptersResponse = await fetchApi(chaptersUrl); - const chaptersJson: ChapterResponse = await chaptersResponse.json(); + const chaptersResponse = await fetchApi(chaptersUrl).catch(() => null); + const chaptersJson: ChapterResponse | null = await chaptersResponse?.json().catch(() => null); - if (chaptersJson?.success && chaptersJson?.data?.chapters) { - novel.chapters = chaptersJson.data.chapters - .map(chapter => ({ - name: chapter.name, - path: new URL(chapter.url, this.site).pathname.substring(1), - releaseTime: chapter.updated_at, - })) - .reverse(); - } else if (initialManga.chapters) { - novel.chapters = initialManga.chapters - .map(chapter => ({ - name: chapter.name, - path: new URL(chapter.url, this.site).pathname.substring(1), - releaseTime: chapter.updatedAt, - })) - .reverse(); - } + const rawChapters = chaptersJson?.data?.chapters || initialManga.chapters || []; + novel.chapters = rawChapters.map(chapter => ({ + name: chapter.name, + path: new URL(chapter.url, this.site).pathname.substring(1), + releaseTime: chapter.updated_at || chapter.updatedAt, + })).reverse(); return novel; } @@ -158,39 +124,22 @@ class NovelBuddy implements Plugin.PluginBase { if (!initialChapter) throw new Error('Could not find chapter content'); let content = initialChapter.content; - if (content) { // Remove Webnovel watermarks/ads - content = content.replace( - /Find authorized novels in Webnovel.*?faster updates, better experience.*?Please click www\.webnovel\.com for visiting\./gi, - '', - ); + content = content.replace(/Find authorized novels in Webnovel.*?faster updates, better experience.*?Please click www\.webnovel\.com for visiting\./gi, ''); // Remove obfuscated freewebnovel watermarks (e.g., free𝑤𝑒𝑏novel.com) - const fwnRegex = - /(?:𝐟|ᵮ|𝑓|𝒇|𝒻|𝓯|𝔣|𝕗|𝖿|𝗳|𝙛|𝚏|ꬵ|ꞙ|ẝ|𝖋|ⓕ|f|ƒ|ḟ|ʃ|բ|ᶠ|⒡|ſ|ꊰ|ʄ|∱|ᶂ|𝘧|f)(?:𝚛|ꭇ|ᣴ|ℾ|𝚪|𝛤|𝜞|𝝘|𝞒|Ⲅ|Г|Ꮁ|ᒥ|ꭈ|ⲅ|ꮁ|ⓡ|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)(?:₥|ᵯ|𝖒|𝐦|𝗆|𝔪|𝕞|𝓂|ⓜ|m|ന|ᙢ|൩|m|ḿ|ṁ|ⅿ|ϻ|ṃ|ጠ|ɱ|៳|ᶆ|𝒎|𝙢|𝓶|𝚖|𝑚|𝗺|᧕|᧗|m))?/g; + const fwnRegex = /(?:𝐟|ᵮ|𝑓|𝒇|𝒻|𝓯|𝔣|𝕗|𝖿|𝗳|𝙛|𝚏|ꬵ|ꞙ|ẝ|𝖋|ⓕ|f|ƒ|ḟ|ʃ|բ|ᶠ|⒡|ſ|ꊰ|ʄ|∱|ᶂ|𝘧|f)(?:𝚛|ꭇ|ᣴ|ℾ|𝚪|𝛤|𝜞|𝝘|𝞒|Ⲅ|Г|Ꮁ|ᒥ|ꭈ|ⲅ|ꮁ|ⓡ|r|ŕ|ṙ|ř|ȑ|ȓ|ṛ|ṝ|ŗ|г|Ր|ɾ|ᥬ|ṟ|ɍ|ʳ|⒭|ɼ|ѓ|ᴦ|ᶉ|𝐫|𝑟|𝒓|𝓇|𝓻|𝔯|𝕣|𝖗|𝗋|𝗿|𝘳|𝙧|ᵲ|ґ|ᵣ|r)(?:ə|ә|ⅇ|ꬲ|ꞓ|⋴|𝛆|𝛜|𝜀|𝜖|𝜺|𝝐|𝝴|𝞊|𝞮|𝟄|ⲉ|ꮛ|𐐩|Ꞓ|Ⲉ|⍷|𝑒|𝓮|𝕖|𝖊|𝘦|𝗲|𝚎|𝙚|𝒆|𝔢|𝖾|𝐞|Ҿ|ҿ|ⓔ|e|⒠|è|ᧉ|é|ᶒ|ê|ɘ|ἔ|ề|ế|ễ|૯|ǝ|є|ε|ē|ҽ|ɛ|ể|ẽ|ḕ|ḗ|ĕ|ė|ë|ẻ|ě|ȅ|ȇ|ẹ|ệ|ȩ|ɇ|ₑ|ę|ḝ|ḙ|ḛ|℮|е|ԑ|ѐ|ӗ|ᥱ|ё|ἐ|ἑ|ἒ|ἓ|ἕ|ℯ|e)+(?:𝐰|ꝡ|𝑤|𝒘|𝓌|𝔀|𝔴|𝕨|𝖜|𝗐|𝘄|𝘸|𝙬|𝚠|ա|ẁ|ꮃ|ẃ|ⓦ|⍵|ŵ|ẇ|ẅ|ẘ|ẉ|ⱳ|ὼ|ὠ|ὡ|ὢ|ὣ|ω|ὤ|ὥ|ὦ|ὧ|ῲ|ῳ|ῴ|ῶ|ῷ|Ⱳ|ѡ|ԝ|ᴡ|ώ|ᾠ|ᾡ|ᾡ|ᾢ|ᾣ|ᾤ|ᾥ|ᾦ|ɯ|𝝕|𝟉|𝞏|w)(?:ə|ә|ⅇ|ꬲ|ꞓ|⋴|𝛆|𝛜|𝜀|𝜖|𝜺|𝝐|𝝴|𝞊|𝞮|𝟄|ⲉ|ꮛ|𐐩|Ꞓ|Ⲉ|⍷|𝑒|𝓮|𝕖|𝖊|𝘦|𝗲|𝚎|𝙚|𝒆|𝔢|𝖾|𝐞|Ҿ|ҿ|ⓔ|e|⒠|è|ᧉ|é|ᶒ|ê|ɘ|ἔ|ề|ế|ễ|૯|ǝ|є|ε|ē|ҽ|ɛ|ể|ẽ|ḕ|ḗ|ĕ|ė|ë|ẻ|ě|ȅ|ȇ|ẹ|ệ|ȩ|ɇ|ₑ|ę|ḝ|ḙ|ḛ|℮|е|ԑ|ѐ|ӗ|ᥱ|ё|ἐ|ἑ|ἒ|ἓ|ἕ|ℯ|e)(?:ꮟ|Ꮟ|𝐛|𝘣|𝒷|𝔟|𝓫|𝖇|𝖻|𝑏|𝙗|𝕓|𝒃|𝗯|𝚋|♭|ᑳ|ᒈ|b|ᖚ|ᕹ|ᕺ|ⓑ|ḃ|ḅ|ҍ|ъ|ḇ|ƃ|ɓ|ƅ|ᖯ|Ƅ|Ь|ᑲ|þ|Ƃ|⒝|Ъ|ᶀ|ᑿ|ᒀ|ᒂ|ᒁ|ᑾ|ь|ƀ|Ҍ|Ѣ|ѣ|ᔎ|b)(?:ո|ռ|ח|𝒏|𝓷|ն|𝑛|𝖓|𝔫|𝗇|ն|𝗻|ᥒ|ⓝ|ή|n|ǹ|ᴒ|ń|ñ|ᾗ|η|ṅ|ň|ṇ|ɲ|ņ|ṋ|ṉ|ղ|ຖ|Ռ|ƞ|ŋ|⒩|ภ|ก|ɳ|п|ʼn|л|ԉ|Ƞ|ἠ|ἡ|ῃ|դ|ᾐ|ᾑ|ᾒ|ᾓ|ᾔ|ᾕ|ᾖ|ῄ|ῆ|ῇ|ῂ|ἢ|ἣ|ἤ|ἥ|ἦ|ἧ|ὴ|ή|በ|ቡ|ቢ|ባ|ቤ|б|ቦ|ȵ|𝛈|𝜂|𝜼|𝝶|𝞰|𝕟|𝘯|𝐧|𝓃|ᶇ|ᵰ|ᥥ|∩|n)(?:ం|ం|ം|ං|૦|௦|۵|ℴ|𝑜|𝒐|𝒐|ꬽ|𝝄|\\u03C3|\\u03C3|\\u03C3|\\u03C2|\\u1\\u00BF|\\u006F|\\u09E6|\\u0B66|\\u12D0|\\u03B9|\\u006F|\\u03C4|\\u0077|\\u1040|\\u1042\\u0063|\\u104EA|\\u1D4F8|\\u1F1F4|\\u2364|\\u25CB|\\u03D9|\\u1F17E|\\u1D4AA|\\u1D5AE|\\u1D7E2|\\u1D7F6|\\u1D67E|\\u1D630|\\u1D5FC|\\u1D560|\\u1D70A|\\u1D428|\\u1D77E|\\u1D7B8|\\u1424|\\u24DE|\\u0473|\\u19D0|\\u1972|\\u00F0|\\uFF4F|\\u0C20|\\u199E|\\u0553|\\u00F2|\\u04E9|\\u04E7|\\u00F3|\\u00BA|\\u014D|\\u00F4|\\u01D2|\\u020F|\\u014F|\\u1ED3|\\u022D|\\u1E4F|\\u1F44|\\u1E51|\\u1E53|\\u022F|\\u022B|\\u0E4F|\\u1D0F|\\u0151|\\u00F6|\\u047B|\\u043E|\\u12D0|\\u01ED|\\u0231|\\u09E6|\\u0B66|\\u0665|\\u0C66|\\u0E50|\\u0ED0|\\u03BF|\\u0585|\\u1D11|\\u0966|\\u0A66|\\u1ECF|\\u01A1|\\u1EDD|\\u1EDB|\\u1EE1|\\u1EDF|\\u1EE3|\\u1ECD|\\u1ED9|\\u01EB|\\u00F8|\\u01FF|\\u0275|\\u056E|\\u1F40|\\u1F41|\\u03CC|\\u1F78|\\u1F79|\\u1F42|\\u1F43|\\u1F45|o)(?:\\u2228|\\u2304|\\u22C1|\\u2174|\\u1D42F|\\u1D463|\\u1D497|\\u1D4CB|\\u1D533|\\u1D567|\\u1D59B|\\uABA9|\\u1200|\\u24E5|\\uFF56|\\u1D710|\\u1D74A|\\u1E7D|\\u1E7F|\\u0C6E|\\u0E07|\\u0475|\\u05E2|\\u1D20|\\u03BD|\\u05D8|\\u1D65|\\u0477|\\u17F4|\\u1601|\\u1D66B|\\u1D66B|\\u1D6CE|\\u1D708|\\u1D742|\\u1D77C|\\u1D7B6|\\u1D637|\\u1D603|\\u1D4FF|v)(?:\\u0259|\\u04D9|\\u2147|\\uAB32|\\uA793|\\u22F4|\\u1D6C6|\\u1D6DC|\\u1D700|\\u1D716|\\u1D73A|\\u1D750|\\u1D774|\\u1D78A|\\u1D7AE|\\u1D7C4|\\u2C89|\\uAB9B|\\u10429|\\uA792|\\u2C88|\\u2377|\\u1D452|\\u1D4EE|\\u1D556|\\u1D58A|\\u1D626|\\u1D5F2|\\u1D68E|\\u1D65A|\\u1D486|\\u1D522|\\u1D5BE|\\u1D41E|\\u04BE|\\u04BF|\\u24D4|\\uFF45|\\u24A0|\\u00E8|\\u19C9|\\u00E9|\\u1D92|\\u00EA|\\u0258|\\u1F14|\\u1EC1|\\u1EBF|\\u1EC5|\\u0AEF|\\u01DD|\\u0454|\\u03B5|\\u0113|\\u04BD|\\u025B|\\u1EC3|\\u1EBD|\\u1E15|\\u1E17|\\u0115|\\u0117|\\u00EB|\\u1EBB|\\u011B|\\u0205|\\u0207|\\u1EB9|\\u1EC7|\\u0229|\\u0247|\\u2091|\\u0119|\\u1E1D|\\u1E19|\\u1E1B|\\u212E|\\u0435|\\u0511|\\u0450|\\u04D7|\\u1971|\\u0451|\\u1F10|\\u1F11|\\u1F12|\\u1F13|\\u1F15|\\u212F|e)(?:\\u24DB|\\uFF4C|\\u0140|\\u013A|\\u013E|\\u1E37|\\u1E39|\\u1E37|\\u013C|\\u04C0|\\u2113|\\u1E3D|\\u1E3B|\\u0142|\\uFF9A|\\u026D|\\u019A|\\u026B|\\u2C61|\\|\\u0196|\\u24A7|\\u0285|\\u01C0|\\u05D5|\\u05DF|\\u0399|\\u0406|\\uFF5C|\\u1DA9|\\u04CF|\\u1D4D8|\\u1D540|\\u1D5A8|\\u1D5DC|\\u1D610|\\u1D425|\\u1D459|\\u1D48D|\\u1D4C1|\\u1D529|\\u1D55D|\\u1D591|\\u1D5C5|\\u1D5F9|\\u1D62D|\\u1D695|\\u1D724|\\u1D75E|\\u0131|\\u1D6A4|\\u0269|\\u1FBE|\\u1D6CA|\\u1D704|\\u1D73E|\\u1D7B2|I|l)(?:.?(?:\\u1F74C|\\uFF43|\\u217D|\\u1D41C|\\u1D450|\\u1D484|\\u1D4B8|\\u1D4EC|\\u1D520|\\u1D554|\\u1D588|\\u1D5BC|\\u1D5F0|\\u1D624|\\u1D658|\\u1D68C|\\u1D04|\\u03F2|\\u2CA5|\\u0441|\\uABAF|\\u1043D|\\u2CA5|\\u1043D|\\uABAF|\\u0109|\\uFF43|\\u24D2|\\u0107|\\u010D|\\u010B|\\u00E7|\\u0481|\\u0188|\\u1E09|\\u023C|\\u2184|\\u0441|\\u122D|\\u1D04|\\u03F2|\\u04AB|\\uA49D|\\u03C2|\\u027D|\\u03C2|\\u1D672|\\u1466|\\u19DA|\\u1D41C|\\u1D450|\\u1D484|\\u1D4B8|\\u1D4EC|\\u1D520|\\u1D554|\\u1D588|\\u1D5BC|\\u1D5F0|\\u1D624|\\u1D658|\\u1D68C|\\u20B5|\\u1F1E8|\\u1974|\\u14BC|\\u217D|c)(?:\\u0C02|\\u0C02|\\u0D02|\\u0D82|\\u0AE6|\\u0BE6|\\u06F5|\\u2134|\\u1D490|\\u1D490|\\u1D490|\\uAB3D|\\u1D744|\\u1D6D4|\\u1D70E|\\u1D748|\\u1D782|\\u10FF|\\u1D698|\\u09E6|\\u0B66|\\u12D0|\\u1D6D0|\\u1D5C8|\\u1D7BC|\\u101D|\\u2C9F|\\u0E50|\\u0ED0|\\u03BF|\\u0585|\\u1D11|\\u0966|\\u0A66|\\u1ECF|\\u01A1|\\u1EDD|\\u1EDB|\\u1EE1|\\u1EDF|\\u1EE3|\\u1ECD|\\u1ED9|\\u01EB|\\u00F8|\\u01FF|\\u0275|\\u056E|\\u1F40|\\u1F41|\\u03CC|\\u1F78|\\u1F79|\\u1F42|\\u1F43|\\u1F45|o)(?:\\u20A5|\\u1D6F|\\u1D592|\\u1D426|\\u1D5C6|\\u1D52A|\\u1D55E|\\u1D4C2|\\u24DC|\\uFF4D|\\u0D28|\\u1662|\\u0D69|m|\\u1E3F|\\u1E41|\\u217F|\\u03FB|\\u1E43|\\u1320|\\u0271|\\u17F3|\\u1D86|\\u1D48E|\\u1D662|\\u1D4F6|\\u1D696|\\u1D45A|\\u1D5FA|\\u19D5|\\u19D7|m))?/g; content = content.replace(fwnRegex, ''); } - return content; } - async searchNovels( - searchTerm: string, - page: number, - ): Promise { - const params = new URLSearchParams({ - 'q': searchTerm, - 'limit': '24', - 'page': page.toString(), - }); - + async searchNovels(searchTerm: string, page: number): Promise { const url = new URL('titles/search', this.api); - url.search = new URLSearchParams(params).toString(); - + url.search = new URLSearchParams({ q: searchTerm, limit: '24', page: String(page) }).toString(); const result = await fetchApi(url.toString()); const body = await result.json(); - return this.parseNovels(body); } @@ -209,11 +158,7 @@ class NovelBuddy implements Plugin.PluginBase { ], type: FilterTypes.Picker, }, - keyword: { - value: '', - label: 'Keywords', - type: FilterTypes.TextInput, - }, + keyword: { value: '', label: 'Keywords', type: FilterTypes.TextInput }, status: { value: 'all', label: 'Status', @@ -227,126 +172,50 @@ class NovelBuddy implements Plugin.PluginBase { type: FilterTypes.Picker, }, genre: { - value: { - include: [], - exclude: [], - }, + value: { include: [], exclude: [] }, label: 'Genres (OR, not AND)', options: [ { label: 'Action', value: 'action' }, - { label: 'Action Adventure', value: 'action-adventure' }, - { label: 'ActionAdventure', value: 'actionadventure' }, - { label: 'Adult', value: 'adult' }, - { label: 'Adventcure', value: 'adventcure' }, { label: 'Adventure', value: 'adventure' }, - { label: 'Adventurer', value: 'adventurer' }, - { label: 'Anime u0026 Comics', value: 'anime-u0026-comics' }, - { label: 'Bender', value: 'bender' }, - { label: 'Booku0026Literature', value: 'booku0026literature' }, - { label: 'Chinese', value: 'chinese' }, - { label: 'Comed', value: 'comed' }, { label: 'Comedy', value: 'comedy' }, - { label: 'ComedySlice of Life', value: 'comedyslice-of-life' }, { label: 'Cultivation', value: 'cultivation' }, { label: 'Drama', value: 'drama' }, - { label: 'dventure', value: 'dventure' }, - { label: 'Eastern', value: 'eastern' }, - { label: 'Easterni', value: 'easterni' }, - { label: 'Ecchi', value: 'ecchi' }, - { label: 'Ecchi Fantasy', value: 'ecchi-fantasy' }, - { label: 'Fan-Fiction', value: 'fan-fiction' }, - { label: 'Fanfiction', value: 'fanfiction' }, - { label: 'Fantas', value: 'fantas' }, { label: 'Fantasy', value: 'fantasy' }, - { label: 'FantasyAction', value: 'fantasyaction' }, - { label: 'Game', value: 'game' }, - { label: 'Games', value: 'games' }, - { label: 'Gender', value: 'gender' }, - { label: 'Gender Bender', value: 'gender-bender' }, { label: 'Harem', value: 'harem' }, - { label: 'HaremAction', value: 'haremaction' }, - { label: 'Haremv', value: 'haremv' }, - { label: 'Historica', value: 'historica' }, { label: 'Historical', value: 'historical' }, - { label: 'History', value: 'history' }, { label: 'Horror', value: 'horror' }, { label: 'Isekai', value: 'isekai' }, - { label: 'Josei', value: 'josei' }, - { label: 'lice of Life', value: 'lice-of-life' }, - { label: 'Light Novel', value: 'light-novel' }, - { label: 'Litrpg', value: 'litrpg' }, - { label: 'Lolicon', value: 'lolicon' }, - { label: 'Magic', value: 'magic' }, - { label: 'Martial', value: 'martial' }, { 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: 'Movies', value: 'movies' }, - { label: 'Myster', value: 'myster' }, { label: 'Mystery', value: 'mystery' }, - { label: 'Mystery.Adventure', value: 'mystery.adventure' }, - { label: 'Psychologic', value: 'psychologic' }, { label: 'Psychological', value: 'psychological' }, - { label: 'Reincarnatio', value: 'reincarnatio' }, - { label: 'Reincarnation', value: 'reincarnation' }, - { label: 'Romanc', value: 'romanc' }, { label: 'Romance', value: 'romance' }, - { label: 'Romance.Adventure', value: 'romance.adventure' }, - { label: 'Romance.Harem', value: 'romance.harem' }, - { label: 'Romance.Smut', value: 'romance.smut' }, - { label: 'RomanceAction', value: 'romanceaction' }, - { label: 'RomanceAdventure', value: 'romanceadventure' }, - { label: 'RomanceHarem', value: 'romanceharem' }, - { label: 'Romancei', value: 'romancei' }, - { label: 'Romancem', value: 'romancem' }, { label: 'School Life', value: 'school-life' }, { label: 'Sci-fi', value: 'sci-fi' }, { label: 'Seinen', value: 'seinen' }, - { label: 'Seinen Wuxia', value: 'seinen-wuxia' }, { label: 'Shoujo', value: 'shoujo' }, - { label: 'Shoujo Ai', value: 'shoujo-ai' }, { label: 'Shounen', value: 'shounen' }, - { label: 'Shounen Ai', value: 'shounen-ai' }, - { label: 'Slice of Lif', value: 'slice-of-lif' }, - { label: 'Slice Of Life', value: 'slice-of-life' }, - { label: 'Slice of Lifel', value: 'slice-of-lifel' }, + { label: 'Slice of Life', value: 'slice-of-life' }, { label: 'Smut', value: 'smut' }, - { label: 'Sports', value: 'sports' }, - { label: 'Superna', value: 'superna' }, { label: 'Supernatural', value: 'supernatural' }, - { label: 'System', value: 'system' }, { label: 'Thriller', value: 'thriller' }, { label: 'Tragedy', value: 'tragedy' }, - { label: 'Urban', value: 'urban' }, - { label: 'Urban Life', value: 'urban-life' }, { label: 'Wuxia', value: 'wuxia' }, { label: 'Xianxia', value: 'xianxia' }, { label: 'Xuanhuan', value: 'xuanhuan' }, - { label: 'Yaoi', value: 'yaoi' }, - { label: 'Yuri', value: 'yuri' }, ], type: FilterTypes.ExcludableCheckboxGroup, }, - min_ch: { - value: '', - label: 'Minimum Chapters', - type: FilterTypes.TextInput, - }, - max_ch: { - value: '', - label: 'Maximum Chapters', - type: FilterTypes.TextInput, - }, + min_ch: { value: '', label: 'Minimum Chapters', type: FilterTypes.TextInput }, + max_ch: { value: '', label: 'Maximum Chapters', type: FilterTypes.TextInput }, type: { value: '', label: 'Types', options: [ { label: 'All Types', value: '' }, - { label: 'Japanese comics', value: 'manga' }, - { label: 'Korean comics', value: 'manhwa' }, - { label: 'Chinese comics', value: 'manhua' }, + { label: 'Manga', value: 'manga' }, + { label: 'Manhwa', value: 'manhwa' }, + { label: 'Manhua', value: 'manhua' }, ], type: FilterTypes.Picker, }, @@ -366,73 +235,10 @@ class NovelBuddy implements Plugin.PluginBase { export default new NovelBuddy(); -type Response = { - data: { - items: Items[]; - }; -}; - -type ChapterResponse = { - success: boolean; - data?: { - chapters?: Items[]; - }; -}; - -type Items = { - id: string; - url: string; - name: string; - alt_name?: string; - cover?: string; - slug: string; - updated_at?: string; - updatedAt?: string; -}; - -type NovelScript = { - props: { - pageProps: { - initialManga: Manga; - }; - }; -}; - -type Manga = { - id: string; - url: string; - name?: string; - altName?: string; - cover: string; - status: string; - ratingStats?: { - average: number; - }; - summary?: string; - artists?: { - name: string; - slug: string; - }[]; - authors?: { - name: string; - slug: string; - }[]; - genres?: { - name: string; - slug: string; - }[]; - chapters?: Items[]; -}; - -type ChapterScript = { - props: { - pageProps: { - initialChapter: Chapter; - }; - }; -}; - -type Chapter = { - name: string; - content: string; -}; +type Response = { data: { items: Items[] } }; +type ChapterResponse = { success: boolean; data?: { chapters?: Items[] } }; +type Items = { id: string; url: string; name: string; updated_at?: string; updatedAt?: string }; +type NovelScript = { props: { pageProps: { initialManga: Manga } } }; +type Manga = { id: string; url: string; name?: string; cover: string; status: string; ratingStats?: { average: number }; summary?: string; artists?: { name: string }[]; authors?: { name: string }[]; genres?: { name: string }[]; chapters?: Items[] }; +type ChapterScript = { props: { pageProps: { initialChapter: Chapter } } }; +type Chapter = { name: string; content: string }; From 548ac1faa80eb350018a01334ae6139270731693 Mon Sep 17 00:00:00 2001 From: 7ui77 <99854073+7ui77@users.noreply.github.com> Date: Sun, 17 May 2026 10:34:28 +0700 Subject: [PATCH 4/5] Update novelbuddy.ts --- 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 392ea34da..3a0a153f6 100644 --- a/plugins/english/novelbuddy.ts +++ b/plugins/english/novelbuddy.ts @@ -9,7 +9,7 @@ class NovelBuddy implements Plugin.PluginBase { 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[] { From 6727b4e80b4e30661a2de8988e8f7a44edce32f1 Mon Sep 17 00:00:00 2001 From: 7ui77 <99854073+7ui77@users.noreply.github.com> Date: Sun, 17 May 2026 10:45:11 +0700 Subject: [PATCH 5/5] Update novelbuddy.ts --- plugins/english/novelbuddy.ts | 282 ++++++++++++++++++++++++++++------ 1 file changed, 238 insertions(+), 44 deletions(-) diff --git a/plugins/english/novelbuddy.ts b/plugins/english/novelbuddy.ts index 3a0a153f6..121bc67c1 100644 --- a/plugins/english/novelbuddy.ts +++ b/plugins/english/novelbuddy.ts @@ -26,23 +26,33 @@ class NovelBuddy implements Plugin.PluginBase { ): Promise { const { genre, min_ch, max_ch, status, demo, orderBy, keyword } = filters; + // Chapter bounds must be an integer between 0 and 10,000 or api cri const parseNumber = (val?: string) => { if (!val?.trim()) return; + const n = Number(val); - return Number.isInteger(n) && n >= 0 && n <= 10000 ? String(n) : undefined; + return Number.isInteger(n) && n >= 0 && n <= 10000 + ? String(n) + : undefined; + }; + + const rawParams: Record = { + genres: genre.value.include?.join(',') || undefined, + exclude: genre.value.exclude?.join(',') || undefined, + min_ch: parseNumber(min_ch.value), + max_ch: parseNumber(max_ch.value), + status: String(status.value), + demographic: demo.value?.join(',') || undefined, + sort: String(orderBy.value), + page: String(pageNo), + limit: '24', + q: keyword.value || undefined, }; - const params: Record = {}; - if (genre.value.include?.length) params.genres = genre.value.include.join(','); - if (genre.value.exclude?.length) params.exclude = genre.value.exclude.join(','); - if (parseNumber(min_ch.value)) params.min_ch = parseNumber(min_ch.value)!; - if (parseNumber(max_ch.value)) params.max_ch = parseNumber(max_ch.value)!; - if (status.value !== 'all') params.status = String(status.value); - if (demo.value?.length) params.demographic = demo.value.join(','); - if (orderBy.value) params.sort = String(orderBy.value); - params.page = String(pageNo); - params.limit = '24'; - if (keyword.value) params.q = keyword.value; + // Filter out the undefined values + const params = Object.fromEntries( + Object.entries(rawParams).filter(([, value]) => value !== undefined), + ) as Record; const url = new URL('titles/search', this.api); url.search = new URLSearchParams(params).toString(); @@ -63,6 +73,7 @@ class NovelBuddy implements Plugin.PluginBase { const data: NovelScript = JSON.parse(script); const initialManga = data.props.pageProps.initialManga; + if (!initialManga) throw new Error('Could not find initialManga data'); const novel: Plugin.SourceNovel = { @@ -75,14 +86,16 @@ class NovelBuddy implements Plugin.PluginBase { chapters: [], }; - const statusMap: Record = { + const rawStatus = initialManga.status; + const map: Record = { ongoing: NovelStatus.Ongoing, hiatus: NovelStatus.OnHiatus, dropped: NovelStatus.Cancelled, cancelled: NovelStatus.Cancelled, completed: NovelStatus.Completed, + unknown: NovelStatus.Unknown, }; - novel.status = statusMap[initialManga.status.toLowerCase()] ?? NovelStatus.Unknown; + novel.status = map[rawStatus.toLowerCase()] ?? NovelStatus.Unknown; // Wrap in
before passing to $(): when the API returns plain text (no leading // `<` tag) cheerio's $() treats the input as a CSS selector, and any `.` in the text @@ -92,21 +105,42 @@ class NovelBuddy implements Plugin.PluginBase { const summary = $('
' + (initialManga.summary || '') + '
'); summary.find('br').replaceWith('\n'); summary.find('p').before('\n').after('\n\n'); - novel.summary = summary.text().split('\n').map(line => line.trim()).join('\n').replace(/\n{3,}/g, '\n\n').trim(); - if (initialManga.ratingStats) novel.rating = initialManga.ratingStats.average; + novel.summary = + summary + .text() + .split('\n') + .map(line => line.trim()) + .join('\n') + ?.replace(/\n{3,}/g, '\n\n') + .trim() || 'Summary Not Found'; - // Fetch chapters from API, fallback to initialManga data + if (initialManga.ratingStats) { + novel.rating = initialManga.ratingStats.average; + } + + // Fetch full chapter list from API const chaptersUrl = `${this.api}titles/${initialManga.id}/chapters`; - const chaptersResponse = await fetchApi(chaptersUrl).catch(() => null); - const chaptersJson: ChapterResponse | null = await chaptersResponse?.json().catch(() => null); + const chaptersResponse = await fetchApi(chaptersUrl); + const chaptersJson: ChapterResponse = await chaptersResponse.json(); - const rawChapters = chaptersJson?.data?.chapters || initialManga.chapters || []; - novel.chapters = rawChapters.map(chapter => ({ - name: chapter.name, - path: new URL(chapter.url, this.site).pathname.substring(1), - releaseTime: chapter.updated_at || chapter.updatedAt, - })).reverse(); + if (chaptersJson?.success && chaptersJson?.data?.chapters) { + novel.chapters = chaptersJson.data.chapters + .map(chapter => ({ + name: chapter.name, + path: new URL(chapter.url, this.site).pathname.substring(1), + releaseTime: chapter.updated_at, + })) + .reverse(); + } else if (initialManga.chapters) { + novel.chapters = initialManga.chapters + .map(chapter => ({ + name: chapter.name, + path: new URL(chapter.url, this.site).pathname.substring(1), + releaseTime: chapter.updatedAt, + })) + .reverse(); + } return novel; } @@ -124,22 +158,39 @@ class NovelBuddy implements Plugin.PluginBase { if (!initialChapter) throw new Error('Could not find chapter content'); let content = initialChapter.content; + if (content) { // Remove Webnovel watermarks/ads - content = content.replace(/Find authorized novels in Webnovel.*?faster updates, better experience.*?Please click www\.webnovel\.com for visiting\./gi, ''); + content = content.replace( + /Find authorized novels in Webnovel.*?faster updates, better experience.*?Please click www\.webnovel\.com for visiting\./gi, + '', + ); // Remove obfuscated freewebnovel watermarks (e.g., free𝑤𝑒𝑏novel.com) - const fwnRegex = /(?:𝐟|ᵮ|𝑓|𝒇|𝒻|𝓯|𝔣|𝕗|𝖿|𝗳|𝙛|𝚏|ꬵ|ꞙ|ẝ|𝖋|ⓕ|f|ƒ|ḟ|ʃ|բ|ᶠ|⒡|ſ|ꊰ|ʄ|∱|ᶂ|𝘧|f)(?:𝚛|ꭇ|ᣴ|ℾ|𝚪|𝛤|𝜞|𝝘|𝞒|Ⲅ|Г|Ꮁ|ᒥ|ꭈ|ⲅ|ꮁ|ⓡ|r|ŕ|ṙ|ř|ȑ|ȓ|ṛ|ṝ|ŗ|г|Ր|ɾ|ᥬ|ṟ|ɍ|ʳ|⒭|ɼ|ѓ|ᴦ|ᶉ|𝐫|𝑟|𝒓|𝓇|𝓻|𝔯|𝕣|𝖗|𝗋|𝗿|𝘳|𝙧|ᵲ|ґ|ᵣ|r)(?:ə|ә|ⅇ|ꬲ|ꞓ|⋴|𝛆|𝛜|𝜀|𝜖|𝜺|𝝐|𝝴|𝞊|𝞮|𝟄|ⲉ|ꮛ|𐐩|Ꞓ|Ⲉ|⍷|𝑒|𝓮|𝕖|𝖊|𝘦|𝗲|𝚎|𝙚|𝒆|𝔢|𝖾|𝐞|Ҿ|ҿ|ⓔ|e|⒠|è|ᧉ|é|ᶒ|ê|ɘ|ἔ|ề|ế|ễ|૯|ǝ|є|ε|ē|ҽ|ɛ|ể|ẽ|ḕ|ḗ|ĕ|ė|ë|ẻ|ě|ȅ|ȇ|ẹ|ệ|ȩ|ɇ|ₑ|ę|ḝ|ḙ|ḛ|℮|е|ԑ|ѐ|ӗ|ᥱ|ё|ἐ|ἑ|ἒ|ἓ|ἕ|ℯ|e)+(?:𝐰|ꝡ|𝑤|𝒘|𝓌|𝔀|𝔴|𝕨|𝖜|𝗐|𝘄|𝘸|𝙬|𝚠|ա|ẁ|ꮃ|ẃ|ⓦ|⍵|ŵ|ẇ|ẅ|ẘ|ẉ|ⱳ|ὼ|ὠ|ὡ|ὢ|ὣ|ω|ὤ|ὥ|ὦ|ὧ|ῲ|ῳ|ῴ|ῶ|ῷ|Ⱳ|ѡ|ԝ|ᴡ|ώ|ᾠ|ᾡ|ᾡ|ᾢ|ᾣ|ᾤ|ᾥ|ᾦ|ɯ|𝝕|𝟉|𝞏|w)(?:ə|ә|ⅇ|ꬲ|ꞓ|⋴|𝛆|𝛜|𝜀|𝜖|𝜺|𝝐|𝝴|𝞊|𝞮|𝟄|ⲉ|ꮛ|𐐩|Ꞓ|Ⲉ|⍷|𝑒|𝓮|𝕖|𝖊|𝘦|𝗲|𝚎|𝙚|𝒆|𝔢|𝖾|𝐞|Ҿ|ҿ|ⓔ|e|⒠|è|ᧉ|é|ᶒ|ê|ɘ|ἔ|ề|ế|ễ|૯|ǝ|є|ε|ē|ҽ|ɛ|ể|ẽ|ḕ|ḗ|ĕ|ė|ë|ẻ|ě|ȅ|ȇ|ẹ|ệ|ȩ|ɇ|ₑ|ę|ḝ|ḙ|ḛ|℮|е|ԑ|ѐ|ӗ|ᥱ|ё|ἐ|ἑ|ἒ|ἓ|ἕ|ℯ|e)(?:ꮟ|Ꮟ|𝐛|𝘣|𝒷|𝔟|𝓫|𝖇|𝖻|𝑏|𝙗|𝕓|𝒃|𝗯|𝚋|♭|ᑳ|ᒈ|b|ᖚ|ᕹ|ᕺ|ⓑ|ḃ|ḅ|ҍ|ъ|ḇ|ƃ|ɓ|ƅ|ᖯ|Ƅ|Ь|ᑲ|þ|Ƃ|⒝|Ъ|ᶀ|ᑿ|ᒀ|ᒂ|ᒁ|ᑾ|ь|ƀ|Ҍ|Ѣ|ѣ|ᔎ|b)(?:ո|ռ|ח|𝒏|𝓷|ն|𝑛|𝖓|𝔫|𝗇|ն|𝗻|ᥒ|ⓝ|ή|n|ǹ|ᴒ|ń|ñ|ᾗ|η|ṅ|ň|ṇ|ɲ|ņ|ṋ|ṉ|ղ|ຖ|Ռ|ƞ|ŋ|⒩|ภ|ก|ɳ|п|ʼn|л|ԉ|Ƞ|ἠ|ἡ|ῃ|դ|ᾐ|ᾑ|ᾒ|ᾓ|ᾔ|ᾕ|ᾖ|ῄ|ῆ|ῇ|ῂ|ἢ|ἣ|ἤ|ἥ|ἦ|ἧ|ὴ|ή|በ|ቡ|ቢ|ባ|ቤ|б|ቦ|ȵ|𝛈|𝜂|𝜼|𝝶|𝞰|𝕟|𝘯|𝐧|𝓃|ᶇ|ᵰ|ᥥ|∩|n)(?:ం|ం|ം|ං|૦|௦|۵|ℴ|𝑜|𝒐|𝒐|ꬽ|𝝄|\\u03C3|\\u03C3|\\u03C3|\\u03C2|\\u1\\u00BF|\\u006F|\\u09E6|\\u0B66|\\u12D0|\\u03B9|\\u006F|\\u03C4|\\u0077|\\u1040|\\u1042\\u0063|\\u104EA|\\u1D4F8|\\u1F1F4|\\u2364|\\u25CB|\\u03D9|\\u1F17E|\\u1D4AA|\\u1D5AE|\\u1D7E2|\\u1D7F6|\\u1D67E|\\u1D630|\\u1D5FC|\\u1D560|\\u1D70A|\\u1D428|\\u1D77E|\\u1D7B8|\\u1424|\\u24DE|\\u0473|\\u19D0|\\u1972|\\u00F0|\\uFF4F|\\u0C20|\\u199E|\\u0553|\\u00F2|\\u04E9|\\u04E7|\\u00F3|\\u00BA|\\u014D|\\u00F4|\\u01D2|\\u020F|\\u014F|\\u1ED3|\\u022D|\\u1E4F|\\u1F44|\\u1E51|\\u1E53|\\u022F|\\u022B|\\u0E4F|\\u1D0F|\\u0151|\\u00F6|\\u047B|\\u043E|\\u12D0|\\u01ED|\\u0231|\\u09E6|\\u0B66|\\u0665|\\u0C66|\\u0E50|\\u0ED0|\\u03BF|\\u0585|\\u1D11|\\u0966|\\u0A66|\\u1ECF|\\u01A1|\\u1EDD|\\u1EDB|\\u1EE1|\\u1EDF|\\u1EE3|\\u1ECD|\\u1ED9|\\u01EB|\\u00F8|\\u01FF|\\u0275|\\u056E|\\u1F40|\\u1F41|\\u03CC|\\u1F78|\\u1F79|\\u1F42|\\u1F43|\\u1F45|o)(?:\\u2228|\\u2304|\\u22C1|\\u2174|\\u1D42F|\\u1D463|\\u1D497|\\u1D4CB|\\u1D533|\\u1D567|\\u1D59B|\\uABA9|\\u1200|\\u24E5|\\uFF56|\\u1D710|\\u1D74A|\\u1E7D|\\u1E7F|\\u0C6E|\\u0E07|\\u0475|\\u05E2|\\u1D20|\\u03BD|\\u05D8|\\u1D65|\\u0477|\\u17F4|\\u1601|\\u1D66B|\\u1D66B|\\u1D6CE|\\u1D708|\\u1D742|\\u1D77C|\\u1D7B6|\\u1D637|\\u1D603|\\u1D4FF|v)(?:\\u0259|\\u04D9|\\u2147|\\uAB32|\\uA793|\\u22F4|\\u1D6C6|\\u1D6DC|\\u1D700|\\u1D716|\\u1D73A|\\u1D750|\\u1D774|\\u1D78A|\\u1D7AE|\\u1D7C4|\\u2C89|\\uAB9B|\\u10429|\\uA792|\\u2C88|\\u2377|\\u1D452|\\u1D4EE|\\u1D556|\\u1D58A|\\u1D626|\\u1D5F2|\\u1D68E|\\u1D65A|\\u1D486|\\u1D522|\\u1D5BE|\\u1D41E|\\u04BE|\\u04BF|\\u24D4|\\uFF45|\\u24A0|\\u00E8|\\u19C9|\\u00E9|\\u1D92|\\u00EA|\\u0258|\\u1F14|\\u1EC1|\\u1EBF|\\u1EC5|\\u0AEF|\\u01DD|\\u0454|\\u03B5|\\u0113|\\u04BD|\\u025B|\\u1EC3|\\u1EBD|\\u1E15|\\u1E17|\\u0115|\\u0117|\\u00EB|\\u1EBB|\\u011B|\\u0205|\\u0207|\\u1EB9|\\u1EC7|\\u0229|\\u0247|\\u2091|\\u0119|\\u1E1D|\\u1E19|\\u1E1B|\\u212E|\\u0435|\\u0511|\\u0450|\\u04D7|\\u1971|\\u0451|\\u1F10|\\u1F11|\\u1F12|\\u1F13|\\u1F15|\\u212F|e)(?:\\u24DB|\\uFF4C|\\u0140|\\u013A|\\u013E|\\u1E37|\\u1E39|\\u1E37|\\u013C|\\u04C0|\\u2113|\\u1E3D|\\u1E3B|\\u0142|\\uFF9A|\\u026D|\\u019A|\\u026B|\\u2C61|\\|\\u0196|\\u24A7|\\u0285|\\u01C0|\\u05D5|\\u05DF|\\u0399|\\u0406|\\uFF5C|\\u1DA9|\\u04CF|\\u1D4D8|\\u1D540|\\u1D5A8|\\u1D5DC|\\u1D610|\\u1D425|\\u1D459|\\u1D48D|\\u1D4C1|\\u1D529|\\u1D55D|\\u1D591|\\u1D5C5|\\u1D5F9|\\u1D62D|\\u1D695|\\u1D724|\\u1D75E|\\u0131|\\u1D6A4|\\u0269|\\u1FBE|\\u1D6CA|\\u1D704|\\u1D73E|\\u1D7B2|I|l)(?:.?(?:\\u1F74C|\\uFF43|\\u217D|\\u1D41C|\\u1D450|\\u1D484|\\u1D4B8|\\u1D4EC|\\u1D520|\\u1D554|\\u1D588|\\u1D5BC|\\u1D5F0|\\u1D624|\\u1D658|\\u1D68C|\\u1D04|\\u03F2|\\u2CA5|\\u0441|\\uABAF|\\u1043D|\\u2CA5|\\u1043D|\\uABAF|\\u0109|\\uFF43|\\u24D2|\\u0107|\\u010D|\\u010B|\\u00E7|\\u0481|\\u0188|\\u1E09|\\u023C|\\u2184|\\u0441|\\u122D|\\u1D04|\\u03F2|\\u04AB|\\uA49D|\\u03C2|\\u027D|\\u03C2|\\u1D672|\\u1466|\\u19DA|\\u1D41C|\\u1D450|\\u1D484|\\u1D4B8|\\u1D4EC|\\u1D520|\\u1D554|\\u1D588|\\u1D5BC|\\u1D5F0|\\u1D624|\\u1D658|\\u1D68C|\\u20B5|\\u1F1E8|\\u1974|\\u14BC|\\u217D|c)(?:\\u0C02|\\u0C02|\\u0D02|\\u0D82|\\u0AE6|\\u0BE6|\\u06F5|\\u2134|\\u1D490|\\u1D490|\\u1D490|\\uAB3D|\\u1D744|\\u1D6D4|\\u1D70E|\\u1D748|\\u1D782|\\u10FF|\\u1D698|\\u09E6|\\u0B66|\\u12D0|\\u1D6D0|\\u1D5C8|\\u1D7BC|\\u101D|\\u2C9F|\\u0E50|\\u0ED0|\\u03BF|\\u0585|\\u1D11|\\u0966|\\u0A66|\\u1ECF|\\u01A1|\\u1EDD|\\u1EDB|\\u1EE1|\\u1EDF|\\u1EE3|\\u1ECD|\\u1ED9|\\u01EB|\\u00F8|\\u01FF|\\u0275|\\u056E|\\u1F40|\\u1F41|\\u03CC|\\u1F78|\\u1F79|\\u1F42|\\u1F43|\\u1F45|o)(?:\\u20A5|\\u1D6F|\\u1D592|\\u1D426|\\u1D5C6|\\u1D52A|\\u1D55E|\\u1D4C2|\\u24DC|\\uFF4D|\\u0D28|\\u1662|\\u0D69|m|\\u1E3F|\\u1E41|\\u217F|\\u03FB|\\u1E43|\\u1320|\\u0271|\\u17F3|\\u1D86|\\u1D48E|\\u1D662|\\u1D4F6|\\u1D696|\\u1D45A|\\u1D5FA|\\u19D5|\\u19D7|m))?/g; + const fwnRegex = + /(?:𝐟|ᵮ|𝑓|𝒇|𝒻|𝓯|𝔣|𝕗|𝖿|𝗳|𝙛|𝚏|ꬵ|ꞙ|ẝ|𝖋|ⓕ|f|ƒ|ḟ|ʃ|բ|ᶠ|⒡|ſ|ꊰ|ʄ|∱|ᶂ|𝘧|f)(?:𝚛|ꭇ|ᣴ|ℾ|𝚪|𝛤|𝜞|𝝘|𝞒|Ⲅ|Г|Ꮁ|ᒥ|ꭈ|ⲅ|ꮁ|ⓡ|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)(?:₥|ᵯ|𝖒|𝐦|𝗆|𝔪|𝕞|𝓂|ⓜ|m|ന|ᙢ|൩|m|ḿ|ṁ|ⅿ|ϻ|ṃ|ጠ|ɱ|៳|ᶆ|𝒎|𝙢|𝓶|𝚖|𝑚|𝗺|᧕|᧗|m))?/g; content = content.replace(fwnRegex, ''); } + return content; } - async searchNovels(searchTerm: string, page: number): Promise { + async searchNovels( + searchTerm: string, + page: number, + ): Promise { + const params = new URLSearchParams({ + 'q': searchTerm, + 'limit': '24', + 'page': page.toString(), + }); + const url = new URL('titles/search', this.api); - url.search = new URLSearchParams({ q: searchTerm, limit: '24', page: String(page) }).toString(); + url.search = new URLSearchParams(params).toString(); + const result = await fetchApi(url.toString()); const body = await result.json(); + return this.parseNovels(body); } @@ -158,7 +209,11 @@ class NovelBuddy implements Plugin.PluginBase { ], type: FilterTypes.Picker, }, - keyword: { value: '', label: 'Keywords', type: FilterTypes.TextInput }, + keyword: { + value: '', + label: 'Keywords', + type: FilterTypes.TextInput, + }, status: { value: 'all', label: 'Status', @@ -172,50 +227,126 @@ class NovelBuddy implements Plugin.PluginBase { type: FilterTypes.Picker, }, genre: { - value: { include: [], exclude: [] }, + value: { + include: [], + exclude: [], + }, label: 'Genres (OR, not AND)', options: [ { label: 'Action', value: 'action' }, + { label: 'Action Adventure', value: 'action-adventure' }, + { label: 'ActionAdventure', value: 'actionadventure' }, + { label: 'Adult', value: 'adult' }, + { label: 'Adventcure', value: 'adventcure' }, { label: 'Adventure', value: 'adventure' }, + { label: 'Adventurer', value: 'adventurer' }, + { label: 'Anime u0026 Comics', value: 'anime-u0026-comics' }, + { label: 'Bender', value: 'bender' }, + { label: 'Booku0026Literature', value: 'booku0026literature' }, + { label: 'Chinese', value: 'chinese' }, + { label: 'Comed', value: 'comed' }, { label: 'Comedy', value: 'comedy' }, + { label: 'ComedySlice of Life', value: 'comedyslice-of-life' }, { label: 'Cultivation', value: 'cultivation' }, { label: 'Drama', value: 'drama' }, + { label: 'dventure', value: 'dventure' }, + { label: 'Eastern', value: 'eastern' }, + { label: 'Easterni', value: 'easterni' }, + { label: 'Ecchi', value: 'ecchi' }, + { label: 'Ecchi Fantasy', value: 'ecchi-fantasy' }, + { label: 'Fan-Fiction', value: 'fan-fiction' }, + { label: 'Fanfiction', value: 'fanfiction' }, + { label: 'Fantas', value: 'fantas' }, { label: 'Fantasy', value: 'fantasy' }, + { label: 'FantasyAction', value: 'fantasyaction' }, + { label: 'Game', value: 'game' }, + { label: 'Games', value: 'games' }, + { label: 'Gender', value: 'gender' }, + { label: 'Gender Bender', value: 'gender-bender' }, { label: 'Harem', value: 'harem' }, + { label: 'HaremAction', value: 'haremaction' }, + { label: 'Haremv', value: 'haremv' }, + { label: 'Historica', value: 'historica' }, { label: 'Historical', value: 'historical' }, + { label: 'History', value: 'history' }, { label: 'Horror', value: 'horror' }, { label: 'Isekai', value: 'isekai' }, + { label: 'Josei', value: 'josei' }, + { label: 'lice of Life', value: 'lice-of-life' }, + { label: 'Light Novel', value: 'light-novel' }, + { label: 'Litrpg', value: 'litrpg' }, + { label: 'Lolicon', value: 'lolicon' }, + { label: 'Magic', value: 'magic' }, + { label: 'Martial', value: 'martial' }, { 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: 'Movies', value: 'movies' }, + { label: 'Myster', value: 'myster' }, { label: 'Mystery', value: 'mystery' }, + { label: 'Mystery.Adventure', value: 'mystery.adventure' }, + { label: 'Psychologic', value: 'psychologic' }, { label: 'Psychological', value: 'psychological' }, + { label: 'Reincarnatio', value: 'reincarnatio' }, + { label: 'Reincarnation', value: 'reincarnation' }, + { label: 'Romanc', value: 'romanc' }, { label: 'Romance', value: 'romance' }, + { label: 'Romance.Adventure', value: 'romance.adventure' }, + { label: 'Romance.Harem', value: 'romance.harem' }, + { label: 'Romance.Smut', value: 'romance.smut' }, + { label: 'RomanceAction', value: 'romanceaction' }, + { label: 'RomanceAdventure', value: 'romanceadventure' }, + { label: 'RomanceHarem', value: 'romanceharem' }, + { label: 'Romancei', value: 'romancei' }, + { label: 'Romancem', value: 'romancem' }, { label: 'School Life', value: 'school-life' }, { label: 'Sci-fi', value: 'sci-fi' }, { label: 'Seinen', value: 'seinen' }, + { label: 'Seinen Wuxia', value: 'seinen-wuxia' }, { label: 'Shoujo', value: 'shoujo' }, + { label: 'Shoujo Ai', value: 'shoujo-ai' }, { label: 'Shounen', value: 'shounen' }, - { label: 'Slice of Life', value: 'slice-of-life' }, + { label: 'Shounen Ai', value: 'shounen-ai' }, + { label: 'Slice of Lif', value: 'slice-of-lif' }, + { label: 'Slice Of Life', value: 'slice-of-life' }, + { label: 'Slice of Lifel', value: 'slice-of-lifel' }, { label: 'Smut', value: 'smut' }, + { label: 'Sports', value: 'sports' }, + { label: 'Superna', value: 'superna' }, { label: 'Supernatural', value: 'supernatural' }, + { label: 'System', value: 'system' }, { label: 'Thriller', value: 'thriller' }, { label: 'Tragedy', value: 'tragedy' }, + { label: 'Urban', value: 'urban' }, + { label: 'Urban Life', value: 'urban-life' }, { label: 'Wuxia', value: 'wuxia' }, { label: 'Xianxia', value: 'xianxia' }, { label: 'Xuanhuan', value: 'xuanhuan' }, + { label: 'Yaoi', value: 'yaoi' }, + { label: 'Yuri', value: 'yuri' }, ], type: FilterTypes.ExcludableCheckboxGroup, }, - min_ch: { value: '', label: 'Minimum Chapters', type: FilterTypes.TextInput }, - max_ch: { value: '', label: 'Maximum Chapters', type: FilterTypes.TextInput }, + min_ch: { + value: '', + label: 'Minimum Chapters', + type: FilterTypes.TextInput, + }, + max_ch: { + value: '', + label: 'Maximum Chapters', + type: FilterTypes.TextInput, + }, type: { value: '', label: 'Types', options: [ { label: 'All Types', value: '' }, - { label: 'Manga', value: 'manga' }, - { label: 'Manhwa', value: 'manhwa' }, - { label: 'Manhua', value: 'manhua' }, + { label: 'Japanese comics', value: 'manga' }, + { label: 'Korean comics', value: 'manhwa' }, + { label: 'Chinese comics', value: 'manhua' }, ], type: FilterTypes.Picker, }, @@ -235,10 +366,73 @@ class NovelBuddy implements Plugin.PluginBase { export default new NovelBuddy(); -type Response = { data: { items: Items[] } }; -type ChapterResponse = { success: boolean; data?: { chapters?: Items[] } }; -type Items = { id: string; url: string; name: string; updated_at?: string; updatedAt?: string }; -type NovelScript = { props: { pageProps: { initialManga: Manga } } }; -type Manga = { id: string; url: string; name?: string; cover: string; status: string; ratingStats?: { average: number }; summary?: string; artists?: { name: string }[]; authors?: { name: string }[]; genres?: { name: string }[]; chapters?: Items[] }; -type ChapterScript = { props: { pageProps: { initialChapter: Chapter } } }; -type Chapter = { name: string; content: string }; +type Response = { + data: { + items: Items[]; + }; +}; + +type ChapterResponse = { + success: boolean; + data?: { + chapters?: Items[]; + }; +}; + +type Items = { + id: string; + url: string; + name: string; + alt_name?: string; + cover?: string; + slug: string; + updated_at?: string; + updatedAt?: string; +}; + +type NovelScript = { + props: { + pageProps: { + initialManga: Manga; + }; + }; +}; + +type Manga = { + id: string; + url: string; + name?: string; + altName?: string; + cover: string; + status: string; + ratingStats?: { + average: number; + }; + summary?: string; + artists?: { + name: string; + slug: string; + }[]; + authors?: { + name: string; + slug: string; + }[]; + genres?: { + name: string; + slug: string; + }[]; + chapters?: Items[]; +}; + +type ChapterScript = { + props: { + pageProps: { + initialChapter: Chapter; + }; + }; +}; + +type Chapter = { + name: string; + content: string; +}; \ No newline at end of file