From da48e9f4f44242ff9f442619eb5f450f3af53d54 Mon Sep 17 00:00:00 2001
From: Yemen
Date: Sat, 12 Sep 2026 21:12:32 +0300
Subject: [PATCH 1/3] =?UTF-8?q?feat(ar/mknov):=20add=20=D9=85=D9=85=D9=84?=
=?UTF-8?q?=D9=83=D9=87=20=D8=A7=D9=84=D8=B1=D9=88=D8=A7=D9=8A=D8=A7=D8=AA?=
=?UTF-8?q?=20plugin?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Add source plugin for mknov.com (مملكه الروايات), an Arabic novel site.
Features:
- popularNovels, parseNovel, parseChapter, searchNovels, resolveUrl
- Decodes the site's font-cmap obfuscation (19 rotating cipher maps)
- Handles Next.js RSC flight payload extraction for chapter content
Co-Authored-By: Claude Code
---
plugins/arabic/mknov.ts | 1047 +++++++++++++++++++++++++++
public/static/src/ar/mknov/icon.png | Bin 0 -> 7487 bytes
2 files changed, 1047 insertions(+)
create mode 100644 plugins/arabic/mknov.ts
create mode 100644 public/static/src/ar/mknov/icon.png
diff --git a/plugins/arabic/mknov.ts b/plugins/arabic/mknov.ts
new file mode 100644
index 000000000..d871c2930
--- /dev/null
+++ b/plugins/arabic/mknov.ts
@@ -0,0 +1,1047 @@
+import { fetchApi } from '@libs/fetch';
+import { Plugin } from '@/types/plugin';
+import { NovelStatus } from '@libs/novelStatus';
+
+type MKWork = {
+ id: number;
+ slug: string;
+ title: string;
+ titleAr: string;
+ author?: string | null;
+ translator?: string | null;
+ status?: string | null;
+ year?: number | null;
+ totalViews?: number;
+ totalChapters?: number;
+ genres?: string[];
+ description?: string;
+ image?: string;
+};
+
+type MKChapter = {
+ id: number;
+ chapterNumber: number;
+ chapterTitle: string;
+ publishDate?: string;
+ views?: number;
+};
+
+type MKVolume = {
+ volumeNumber: number;
+ title: string;
+ chapters: MKChapter[];
+};
+
+type MKWorkListResponse = MKWork[]; // /api/works returns an array directly
+
+/**
+ * Substitution maps extracted from each `protected-font-N.woff2`
+ * version that the site rotates through. The chapter RSC content is
+ * encrypted by mapping each real Arabic letter to a sibling, then
+ * displayed through a @font-face whose glyph cmap holds the cipher.
+ * The page tells us which font (and therefore which cipher) applies via
+ * the `protected-font-N` name in the chapter HTML.
+ *
+ * Each map: raw codepoint in the RSC text -> real Arabic letter.
+ * The site also rotates long dashes `―` per map, omitted here.
+ */
+const FONT_CMAPS: Record> = {
+ 1: {
+ 'آ': 'د',
+ 'أ': 'إ',
+ 'ؤ': 'ر',
+ 'إ': 'ا',
+ 'ئ': 'غ',
+ 'ا': 'أ',
+ 'ب': 'ن',
+ 'ة': 'ز',
+ 'ت': 'ض',
+ 'ث': 'ت',
+ 'ج': 'ث',
+ 'ح': 'ع',
+ 'خ': 'ب',
+ 'د': 'ة',
+ 'ذ': 'آ',
+ 'ر': 'ذ',
+ 'ز': 'و',
+ 'س': 'ك',
+ 'ش': 'ف',
+ 'ص': 'م',
+ 'ض': 'ظ',
+ 'ط': 'س',
+ 'ظ': 'ق',
+ 'ع': 'ح',
+ 'غ': 'ئ',
+ 'ف': 'ش',
+ 'ق': 'ي',
+ 'ك': 'ص',
+ 'ل': 'ط',
+ 'م': 'ه',
+ 'ن': 'ج',
+ 'ه': 'ل',
+ 'و': 'ؤ',
+ 'ي': 'خ',
+ '۱': '١',
+ '۳': '٣',
+ '۸': '٨',
+ '۹': '٩',
+ },
+ 2: {
+ 'آ': 'أ',
+ 'أ': 'ا',
+ 'ؤ': 'و',
+ 'إ': 'ز',
+ 'ئ': 'ق',
+ 'ا': 'ؤ',
+ 'ب': 'غ',
+ 'ة': 'ذ',
+ 'ت': 'ئ',
+ 'ث': 'ظ',
+ 'ج': 'ب',
+ 'ح': 'ه',
+ 'خ': 'ج',
+ 'د': 'آ',
+ 'ذ': 'ة',
+ 'ر': 'إ',
+ 'ز': 'د',
+ 'س': 'م',
+ 'ش': 'ض',
+ 'ص': 'س',
+ 'ض': 'ت',
+ 'ط': 'ح',
+ 'ظ': 'ف',
+ 'ع': 'ط',
+ 'غ': 'خ',
+ 'ف': 'ي',
+ 'ق': 'ن',
+ 'ك': 'ص',
+ 'ل': 'ع',
+ 'م': 'ل',
+ 'ن': 'ش',
+ 'ه': 'ك',
+ 'و': 'ر',
+ 'ي': 'ث',
+ '۱': '١',
+ '۳': '٣',
+ '۸': '٨',
+ '۹': '٩',
+ },
+ 3: {
+ 'آ': 'ز',
+ 'أ': 'ؤ',
+ 'ؤ': 'و',
+ 'إ': 'ة',
+ 'ئ': 'غ',
+ 'ا': 'أ',
+ 'ب': 'ج',
+ 'ة': 'إ',
+ 'ت': 'ش',
+ 'ث': 'ض',
+ 'ج': 'ق',
+ 'ح': 'م',
+ 'خ': 'ئ',
+ 'د': 'ر',
+ 'ذ': 'د',
+ 'ر': 'ا',
+ 'ز': 'آ',
+ 'س': 'ه',
+ 'ش': 'ظ',
+ 'ص': 'ل',
+ 'ض': 'خ',
+ 'ط': 'س',
+ 'ظ': 'ي',
+ 'ع': 'ص',
+ 'غ': 'ن',
+ 'ف': 'ب',
+ 'ق': 'ث',
+ 'ك': 'ع',
+ 'ل': 'ح',
+ 'م': 'ط',
+ 'ن': 'ت',
+ 'ه': 'ك',
+ 'و': 'ذ',
+ 'ي': 'ف',
+ '۱': '١',
+ '۳': '٣',
+ '۸': '٨',
+ '۹': '٩',
+ },
+ 4: {
+ 'آ': 'ذ',
+ 'أ': 'ة',
+ 'ؤ': 'أ',
+ 'إ': 'ر',
+ 'ئ': 'غ',
+ 'ا': 'د',
+ 'ب': 'ش',
+ 'ة': 'آ',
+ 'ت': 'ف',
+ 'ث': 'ض',
+ 'ج': 'ي',
+ 'ح': 'ك',
+ 'خ': 'ج',
+ 'د': 'و',
+ 'ذ': 'ز',
+ 'ر': 'ؤ',
+ 'ز': 'إ',
+ 'س': 'ه',
+ 'ش': 'خ',
+ 'ص': 'ح',
+ 'ض': 'ب',
+ 'ط': 'ل',
+ 'ظ': 'ن',
+ 'ع': 'ص',
+ 'غ': 'ق',
+ 'ف': 'ت',
+ 'ق': 'ئ',
+ 'ك': 'ع',
+ 'ل': 'س',
+ 'م': 'ط',
+ 'ن': 'ظ',
+ 'ه': 'م',
+ 'و': 'ا',
+ 'ي': 'ث',
+ '۱': '١',
+ '۳': '٣',
+ '۸': '٨',
+ '۹': '٩',
+ },
+ 5: {
+ 'آ': 'و',
+ 'أ': 'ا',
+ 'ؤ': 'ر',
+ 'إ': 'ذ',
+ 'ئ': 'خ',
+ 'ا': 'د',
+ 'ب': 'ج',
+ 'ة': 'إ',
+ 'ت': 'ث',
+ 'ث': 'ظ',
+ 'ج': 'غ',
+ 'ح': 'ع',
+ 'خ': 'ش',
+ 'د': 'ز',
+ 'ذ': 'آ',
+ 'ر': 'ؤ',
+ 'ز': 'ة',
+ 'س': 'م',
+ 'ش': 'ت',
+ 'ص': 'ه',
+ 'ض': 'ئ',
+ 'ط': 'ص',
+ 'ظ': 'ق',
+ 'ع': 'ط',
+ 'غ': 'ي',
+ 'ف': 'ن',
+ 'ق': 'ض',
+ 'ك': 'ل',
+ 'ل': 'ح',
+ 'م': 'س',
+ 'ن': 'ب',
+ 'ه': 'ك',
+ 'و': 'أ',
+ 'ي': 'ف',
+ '۱': '١',
+ '۳': '٣',
+ '۸': '٨',
+ '۹': '٩',
+ },
+ 6: {
+ 'آ': 'ؤ',
+ 'أ': 'ة',
+ 'ؤ': 'ا',
+ 'إ': 'آ',
+ 'ئ': 'خ',
+ 'ا': 'ز',
+ 'ب': 'ج',
+ 'ة': 'أ',
+ 'ت': 'ب',
+ 'ث': 'ف',
+ 'ج': 'ض',
+ 'ح': 'ط',
+ 'خ': 'ش',
+ 'د': 'و',
+ 'ذ': 'د',
+ 'ر': 'ذ',
+ 'ز': 'إ',
+ 'س': 'ح',
+ 'ش': 'ق',
+ 'ص': 'ك',
+ 'ض': 'ي',
+ 'ط': 'ص',
+ 'ظ': 'ئ',
+ 'ع': 'ه',
+ 'غ': 'ن',
+ 'ف': 'ث',
+ 'ق': 'ظ',
+ 'ك': 'ل',
+ 'ل': 'س',
+ 'م': 'ع',
+ 'ن': 'ت',
+ 'ه': 'م',
+ 'و': 'ر',
+ 'ي': 'غ',
+ '۱': '١',
+ '۳': '٣',
+ '۸': '٨',
+ '۹': '٩',
+ },
+ 7: {
+ 'آ': 'د',
+ 'أ': 'ة',
+ 'ؤ': 'إ',
+ 'إ': 'ذ',
+ 'ئ': 'ف',
+ 'ا': 'ر',
+ 'ب': 'ي',
+ 'ة': 'ز',
+ 'ت': 'ض',
+ 'ث': 'ج',
+ 'ج': 'ظ',
+ 'ح': 'ع',
+ 'خ': 'ش',
+ 'د': 'أ',
+ 'ذ': 'و',
+ 'ر': 'آ',
+ 'ز': 'ؤ',
+ 'س': 'ك',
+ 'ش': 'ق',
+ 'ص': 'ح',
+ 'ض': 'ت',
+ 'ط': 'ص',
+ 'ظ': 'غ',
+ 'ع': 'ل',
+ 'غ': 'خ',
+ 'ف': 'ن',
+ 'ق': 'ب',
+ 'ك': 'م',
+ 'ل': 'ط',
+ 'م': 'ه',
+ 'ن': 'ث',
+ 'ه': 'س',
+ 'و': 'ا',
+ 'ي': 'ئ',
+ '۱': '١',
+ '۳': '٣',
+ '۸': '٨',
+ '۹': '٩',
+ },
+ 8: {
+ 'آ': 'ا',
+ 'أ': 'د',
+ 'ؤ': 'ذ',
+ 'إ': 'ز',
+ 'ئ': 'ي',
+ 'ا': 'إ',
+ 'ب': 'ش',
+ 'ة': 'ؤ',
+ 'ت': 'ث',
+ 'ث': 'ت',
+ 'ج': 'ف',
+ 'ح': 'س',
+ 'خ': 'ئ',
+ 'د': 'و',
+ 'ذ': 'أ',
+ 'ر': 'ة',
+ 'ز': 'ر',
+ 'س': 'ع',
+ 'ش': 'ق',
+ 'ص': 'م',
+ 'ض': 'ظ',
+ 'ط': 'ل',
+ 'ظ': 'ب',
+ 'ع': 'ه',
+ 'غ': 'ن',
+ 'ف': 'غ',
+ 'ق': 'ض',
+ 'ك': 'ص',
+ 'ل': 'ط',
+ 'م': 'ك',
+ 'ن': 'ج',
+ 'ه': 'ح',
+ 'و': 'آ',
+ 'ي': 'خ',
+ '۱': '١',
+ '۳': '٣',
+ '۸': '٨',
+ '۹': '٩',
+ },
+ 9: {
+ 'آ': 'ؤ',
+ 'أ': 'د',
+ 'ؤ': 'و',
+ 'إ': 'ة',
+ 'ئ': 'غ',
+ 'ا': 'ذ',
+ 'ب': 'ت',
+ 'ة': 'إ',
+ 'ت': 'ن',
+ 'ث': 'ش',
+ 'ج': 'ث',
+ 'ح': 'ك',
+ 'خ': 'ي',
+ 'د': 'ز',
+ 'ذ': 'آ',
+ 'ر': 'أ',
+ 'ز': 'ا',
+ 'س': 'م',
+ 'ش': 'ق',
+ 'ص': 'س',
+ 'ض': 'ئ',
+ 'ط': 'ع',
+ 'ظ': 'ج',
+ 'ع': 'ل',
+ 'غ': 'خ',
+ 'ف': 'ب',
+ 'ق': 'ف',
+ 'ك': 'ه',
+ 'ل': 'ص',
+ 'م': 'ح',
+ 'ن': 'ظ',
+ 'ه': 'ط',
+ 'و': 'ر',
+ 'ي': 'ض',
+ '۱': '١',
+ '۳': '٣',
+ '۸': '٨',
+ '۹': '٩',
+ },
+ 10: {
+ 'آ': 'د',
+ 'أ': 'آ',
+ 'ؤ': 'ز',
+ 'إ': 'ذ',
+ 'ئ': 'ش',
+ 'ا': 'ة',
+ 'ب': 'ف',
+ 'ة': 'إ',
+ 'ت': 'ض',
+ 'ث': 'ج',
+ 'ج': 'ئ',
+ 'ح': 'ع',
+ 'خ': 'ث',
+ 'د': 'و',
+ 'ذ': 'ؤ',
+ 'ر': 'أ',
+ 'ز': 'ا',
+ 'س': 'ح',
+ 'ش': 'ظ',
+ 'ص': 'م',
+ 'ض': 'ن',
+ 'ط': 'ك',
+ 'ظ': 'ب',
+ 'ع': 'ط',
+ 'غ': 'ت',
+ 'ف': 'خ',
+ 'ق': 'غ',
+ 'ك': 'ل',
+ 'ل': 'ه',
+ 'م': 'ص',
+ 'ن': 'ي',
+ 'ه': 'س',
+ 'و': 'ر',
+ 'ي': 'ق',
+ '۱': '١',
+ '۳': '٣',
+ '۸': '٨',
+ '۹': '٩',
+ },
+ 11: {
+ 'آ': 'و',
+ 'أ': 'ؤ',
+ 'ؤ': 'ر',
+ 'إ': 'ز',
+ 'ئ': 'ف',
+ 'ا': 'د',
+ 'ب': 'ج',
+ 'ة': 'إ',
+ 'ت': 'ي',
+ 'ث': 'ق',
+ 'ج': 'غ',
+ 'ح': 'ص',
+ 'خ': 'ن',
+ 'د': 'آ',
+ 'ذ': 'ا',
+ 'ر': 'أ',
+ 'ز': 'ة',
+ 'س': 'ح',
+ 'ش': 'خ',
+ 'ص': 'ك',
+ 'ض': 'ت',
+ 'ط': 'ه',
+ 'ظ': 'ب',
+ 'ع': 'ل',
+ 'غ': 'ض',
+ 'ف': 'ظ',
+ 'ق': 'ش',
+ 'ك': 'م',
+ 'ل': 'ط',
+ 'م': 'س',
+ 'ن': 'ئ',
+ 'ه': 'ع',
+ 'و': 'ذ',
+ 'ي': 'ث',
+ '۱': '١',
+ '۳': '٣',
+ '۸': '٨',
+ '۹': '٩',
+ },
+ 12: {
+ 'آ': 'إ',
+ 'أ': 'ا',
+ 'ؤ': 'أ',
+ 'إ': 'ر',
+ 'ئ': 'ن',
+ 'ا': 'آ',
+ 'ب': 'ض',
+ 'ة': 'ذ',
+ 'ت': 'غ',
+ 'ث': 'ش',
+ 'ج': 'ق',
+ 'ح': 'ص',
+ 'خ': 'ي',
+ 'د': 'ة',
+ 'ذ': 'و',
+ 'ر': 'ز',
+ 'ز': 'ؤ',
+ 'س': 'م',
+ 'ش': 'خ',
+ 'ص': 'ه',
+ 'ض': 'ئ',
+ 'ط': 'ك',
+ 'ظ': 'ب',
+ 'ع': 'ل',
+ 'غ': 'ف',
+ 'ف': 'ظ',
+ 'ق': 'ث',
+ 'ك': 'ح',
+ 'ل': 'ط',
+ 'م': 'ع',
+ 'ن': 'ج',
+ 'ه': 'س',
+ 'و': 'د',
+ 'ي': 'ت',
+ '۱': '١',
+ '۳': '٣',
+ '۸': '٨',
+ '۹': '٩',
+ },
+ 13: {
+ 'آ': 'ر',
+ 'أ': 'ؤ',
+ 'ؤ': 'ذ',
+ 'إ': 'ة',
+ 'ئ': 'ث',
+ 'ا': 'آ',
+ 'ب': 'ئ',
+ 'ة': 'إ',
+ 'ت': 'ب',
+ 'ث': 'ق',
+ 'ج': 'ش',
+ 'ح': 'ص',
+ 'خ': 'ي',
+ 'د': 'و',
+ 'ذ': 'د',
+ 'ر': 'أ',
+ 'ز': 'ا',
+ 'س': 'ه',
+ 'ش': 'ف',
+ 'ص': 'ع',
+ 'ض': 'ن',
+ 'ط': 'ك',
+ 'ظ': 'ج',
+ 'ع': 'ط',
+ 'غ': 'ض',
+ 'ف': 'ظ',
+ 'ق': 'ت',
+ 'ك': 'س',
+ 'ل': 'م',
+ 'م': 'ح',
+ 'ن': 'غ',
+ 'ه': 'ل',
+ 'و': 'ز',
+ 'ي': 'خ',
+ '۱': '١',
+ '۳': '٣',
+ '۸': '٨',
+ '۹': '٩',
+ },
+ 14: {
+ 'آ': 'أ',
+ 'أ': 'ة',
+ 'ؤ': 'ر',
+ 'إ': 'ذ',
+ 'ئ': 'ي',
+ 'ا': 'إ',
+ 'ب': 'ش',
+ 'ة': 'ا',
+ 'ت': 'ئ',
+ 'ث': 'ق',
+ 'ج': 'ن',
+ 'ح': 'م',
+ 'خ': 'ظ',
+ 'د': 'و',
+ 'ذ': 'آ',
+ 'ر': 'ؤ',
+ 'ز': 'د',
+ 'س': 'ح',
+ 'ش': 'ض',
+ 'ص': 'س',
+ 'ض': 'ب',
+ 'ط': 'ص',
+ 'ظ': 'غ',
+ 'ع': 'ط',
+ 'غ': 'ث',
+ 'ف': 'ج',
+ 'ق': 'ف',
+ 'ك': 'ه',
+ 'ل': 'ك',
+ 'م': 'ع',
+ 'ن': 'ت',
+ 'ه': 'ل',
+ 'و': 'ز',
+ 'ي': 'خ',
+ '۱': '١',
+ '۳': '٣',
+ '۸': '٨',
+ '۹': '٩',
+ },
+ 15: {
+ 'آ': 'ز',
+ 'أ': 'ذ',
+ 'ؤ': 'د',
+ 'إ': 'ا',
+ 'ئ': 'ش',
+ 'ا': 'إ',
+ 'ب': 'ت',
+ 'ة': 'و',
+ 'ت': 'ظ',
+ 'ث': 'ق',
+ 'ج': 'ي',
+ 'ح': 'ه',
+ 'خ': 'ئ',
+ 'د': 'أ',
+ 'ذ': 'ة',
+ 'ر': 'ؤ',
+ 'ز': 'ر',
+ 'س': 'ع',
+ 'ش': 'ب',
+ 'ص': 'ك',
+ 'ض': 'ث',
+ 'ط': 'ل',
+ 'ظ': 'ف',
+ 'ع': 'م',
+ 'غ': 'ن',
+ 'ف': 'غ',
+ 'ق': 'خ',
+ 'ك': 'ط',
+ 'ل': 'ح',
+ 'م': 'س',
+ 'ن': 'ج',
+ 'ه': 'ص',
+ 'و': 'آ',
+ 'ي': 'ض',
+ '۱': '١',
+ '۳': '٣',
+ '۸': '٨',
+ '۹': '٩',
+ },
+ 16: {
+ 'آ': 'و',
+ 'أ': 'ؤ',
+ 'ؤ': 'ر',
+ 'إ': 'آ',
+ 'ئ': 'ت',
+ 'ا': 'ة',
+ 'ب': 'غ',
+ 'ة': 'د',
+ 'ت': 'ث',
+ 'ث': 'ظ',
+ 'ج': 'خ',
+ 'ح': 'ط',
+ 'خ': 'ش',
+ 'د': 'ذ',
+ 'ذ': 'ز',
+ 'ر': 'أ',
+ 'ز': 'إ',
+ 'س': 'ص',
+ 'ش': 'ن',
+ 'ص': 'ك',
+ 'ض': 'ق',
+ 'ط': 'ل',
+ 'ظ': 'ئ',
+ 'ع': 'ه',
+ 'غ': 'ج',
+ 'ف': 'ي',
+ 'ق': 'ض',
+ 'ك': 'م',
+ 'ل': 'س',
+ 'م': 'ع',
+ 'ن': 'ف',
+ 'ه': 'ح',
+ 'و': 'ا',
+ 'ي': 'ب',
+ '۱': '١',
+ '۳': '٣',
+ '۸': '٨',
+ '۹': '٩',
+ },
+ 17: {
+ 'آ': 'إ',
+ 'أ': 'ة',
+ 'ؤ': 'و',
+ 'إ': 'آ',
+ 'ئ': 'ب',
+ 'ا': 'ؤ',
+ 'ب': 'ق',
+ 'ة': 'ز',
+ 'ت': 'غ',
+ 'ث': 'ش',
+ 'ج': 'ث',
+ 'ح': 'ه',
+ 'خ': 'ن',
+ 'د': 'ر',
+ 'ذ': 'د',
+ 'ر': 'أ',
+ 'ز': 'ا',
+ 'س': 'ك',
+ 'ش': 'ف',
+ 'ص': 'ط',
+ 'ض': 'ي',
+ 'ط': 'م',
+ 'ظ': 'ئ',
+ 'ع': 'س',
+ 'غ': 'ظ',
+ 'ف': 'ت',
+ 'ق': 'خ',
+ 'ك': 'ل',
+ 'ل': 'ع',
+ 'م': 'ص',
+ 'ن': 'ض',
+ 'ه': 'ح',
+ 'و': 'ذ',
+ 'ي': 'ج',
+ '۱': '١',
+ '۳': '٣',
+ '۸': '٨',
+ '۹': '٩',
+ },
+ 18: {
+ 'آ': 'ا',
+ 'أ': 'ذ',
+ 'ؤ': 'آ',
+ 'إ': 'ر',
+ 'ئ': 'ض',
+ 'ا': 'ؤ',
+ 'ب': 'خ',
+ 'ة': 'إ',
+ 'ت': 'ف',
+ 'ث': 'ئ',
+ 'ج': 'ظ',
+ 'ح': 'م',
+ 'خ': 'ش',
+ 'د': 'أ',
+ 'ذ': 'ز',
+ 'ر': 'و',
+ 'ز': 'د',
+ 'س': 'ح',
+ 'ش': 'ي',
+ 'ص': 'ط',
+ 'ض': 'ب',
+ 'ط': 'ع',
+ 'ظ': 'ن',
+ 'ع': 'ه',
+ 'غ': 'ث',
+ 'ف': 'ق',
+ 'ق': 'ج',
+ 'ك': 'ص',
+ 'ل': 'س',
+ 'م': 'ك',
+ 'ن': 'غ',
+ 'ه': 'ل',
+ 'و': 'ة',
+ 'ي': 'ت',
+ '۱': '١',
+ '۳': '٣',
+ '۸': '٨',
+ '۹': '٩',
+ },
+ 19: {
+ 'آ': 'ذ',
+ 'أ': 'ؤ',
+ 'ؤ': 'ة',
+ 'إ': 'ا',
+ 'ئ': 'خ',
+ 'ا': 'ز',
+ 'ب': 'ت',
+ 'ة': 'د',
+ 'ت': 'ش',
+ 'ث': 'ن',
+ 'ج': 'ب',
+ 'ح': 'ع',
+ 'خ': 'ي',
+ 'د': 'ر',
+ 'ذ': 'و',
+ 'ر': 'أ',
+ 'ز': 'آ',
+ 'س': 'ل',
+ 'ش': 'ق',
+ 'ص': 'ك',
+ 'ض': 'ظ',
+ 'ط': 'س',
+ 'ظ': 'ض',
+ 'ع': 'ه',
+ 'غ': 'ث',
+ 'ف': 'غ',
+ 'ق': 'ج',
+ 'ك': 'م',
+ 'ل': 'ح',
+ 'م': 'ص',
+ 'ن': 'ئ',
+ 'ه': 'ط',
+ 'و': 'إ',
+ 'ي': 'ف',
+ '۱': '١',
+ '۳': '٣',
+ '۸': '٨',
+ '۹': '٩',
+ },
+};
+
+/** Pick the cipher matching the font version found on the page. */
+function pickCmap(fontVersion: number): Record {
+ return FONT_CMAPS[fontVersion] ?? FONT_CMAPS[14];
+}
+
+/** Decode a raw T-record payload with the given cipher. */
+function decodeChapter(content: string, cmap: Record): string {
+ return (
+ [...content]
+ .map(ch => cmap[ch] ?? ch)
+ .join('')
+ // CJK / Hangul / Katakana & other scripts the font renders as nothing
+ .replace(/[-ヿ一-鿿가-ㇰ-ㇿ㈀-䶿豈-]/g, '')
+ // Zero-width, format & bidi controls
+ .replace(/[\u200B-\u200F\u2060-\u2063\u202A-\u202E\u061C\uFEFF]/g, '')
+ // Arabic diacritics (harakat, sukun, superscript alef) are noise here,
+ // listed individually so ESLint's no-misleading-character-class stays quiet
+ .replace(/[\u064B\u064C\u064D\u064E\u064F\u0650\u0651\u0652\u0670]/g, '')
+ );
+}
+
+class MKNOV implements Plugin.PluginBase {
+ id = 'mknov';
+ name = 'مملكه الروايات';
+ version = '1.0.0';
+ icon = 'src/ar/mknov/icon.png';
+ site = 'https://mknov.com';
+
+ private baseUrl = 'https://mknov.com';
+ private allWorksCache: MKWork[] | null = null;
+ private readonly maxOffset = 2000;
+
+ /** Fetch JSON from an endpoint. */
+ private async fetchJson(url: string): Promise {
+ const res = await fetchApi(url);
+ if (!res.ok) {
+ throw new Error(`Could not reach site (${res.status})`);
+ }
+ return res.json() as Promise;
+ }
+
+ /** Fetch raw text. */
+ private async fetchText(url: string): Promise {
+ const res = await fetchApi(url);
+ if (!res.ok) {
+ throw new Error(`Could not reach site (${res.status})`);
+ }
+ return res.text();
+ }
+
+ /** /api/works returns a `MKWork[]` array (all works) directly. */
+ private async fetchAllWorks(): Promise {
+ if (this.allWorksCache) {
+ return this.allWorksCache;
+ }
+ const works = await this.fetchJson(
+ `${this.baseUrl}/api/works?limit=${this.maxOffset}`,
+ );
+ this.allWorksCache = works || [];
+ return this.allWorksCache;
+ }
+
+ /** Return only the fields we're sure the site provides. */
+ private toNovelItem(work: MKWork): Plugin.NovelItem {
+ return {
+ name: work.titleAr || work.title || `رواية ${work.id}`,
+ path: `/novel/${work.id}`,
+ cover: work.image || '',
+ };
+ }
+
+ async popularNovels(pageNo: number): Promise {
+ const limit = 50;
+ const offset = (pageNo - 1) * limit;
+
+ // Fetching more than the site allows would fail; keep pagination naive.
+ if (offset >= this.maxOffset) {
+ return [];
+ }
+
+ const works = await this.fetchJson(
+ `${this.baseUrl}/api/works?offset=${offset}&limit=${limit}`,
+ );
+
+ return (works || []).map(work => this.toNovelItem(work));
+ }
+
+ async parseNovel(novelPath: string): Promise {
+ const id = novelPath.replace('/novel/', '').replace(/\/$/, '');
+ const works = await this.fetchAllWorks();
+ const work = works.find(w => String(w.id) === id);
+
+ const novel: Plugin.SourceNovel = {
+ path: novelPath,
+ name: work?.titleAr || work?.title || '',
+ cover: work?.image || '',
+ author: work?.author || undefined,
+ summary: work?.description || '',
+ genres: (work?.genres || []).join(', '),
+ status: (work?.status || '').toLowerCase().includes('مستمر')
+ ? NovelStatus.Ongoing
+ : (work?.status || '').toLowerCase().includes('مكتمل')
+ ? NovelStatus.Completed
+ : NovelStatus.Unknown,
+ };
+
+ // Fetch chapter list
+ const data = await this.fetchJson<{ volumes?: MKVolume[] }>(
+ `${this.baseUrl}/api/works/${id}/chapters`,
+ );
+
+ const chapters: Plugin.ChapterItem[] = [];
+ const volumes = data.volumes || [];
+
+ for (const volume of volumes) {
+ for (const ch of volume.chapters || []) {
+ chapters.push({
+ name: ch.chapterTitle || `الفصل ${ch.chapterNumber}`,
+ path: `/novel/${id}/chapter/${ch.id}`,
+ releaseTime: ch.publishDate || '',
+ chapterNumber: ch.chapterNumber,
+ });
+ }
+ }
+
+ novel.chapters = chapters;
+ return novel;
+ }
+
+ async parseChapter(chapterPath: string): Promise {
+ const url = `${this.baseUrl}${chapterPath}`;
+ const html = await this.fetchText(url);
+
+ // Extract all RSC flight chunks (self.__next_f.push([1, "..."]))
+ const allChunks: string[] = [];
+ const textRe =
+ /self\.__next_f\.push\(\[\s*1\s*,\s*("(?:[^"\\]|\\.)*")\s*\]/g;
+ let match;
+ while ((match = textRe.exec(html)) !== null) {
+ try {
+ allChunks.push(JSON.parse(match[1]));
+ } catch {
+ // skip unparseable chunks
+ }
+ }
+
+ const fullPayload = allChunks.join('');
+
+ // Chapter content is streamed as a Next.js Flight T-record:
+ // [:T,
+ const tRe = /([0-9a-f]+):T([0-9a-f]+),/g;
+ let content = '';
+ while ((match = tRe.exec(fullPayload)) !== null) {
+ const hlen = parseInt(match[2], 16);
+ const start = match.index + match[0].length;
+ content += fullPayload.slice(start, start + hlen);
+ }
+
+ // If no T-record, try a raw "content"-style paragraph block in the payload
+ if (!content.trim()) {
+ const contentIdx = fullPayload.indexOf('"content":');
+ if (contentIdx > -1) {
+ const pStart = fullPayload.indexOf('"', contentIdx + 9);
+ if (pStart > -1) {
+ let depth = 0;
+ let inStr = false;
+ for (let i = pStart + 1; i < fullPayload.length; i++) {
+ const ch = fullPayload[i];
+ if (ch === '\\') {
+ i++;
+ continue;
+ }
+ if (inStr) {
+ if (ch === '"') inStr = false;
+ continue;
+ }
+ if (ch === '{') depth++;
+ else if (ch === '}') {
+ depth--;
+ if (depth === 0) {
+ content = fullPayload.slice(pStart + 1, i);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (!content.trim()) {
+ return ']المحتوى غير متاح.
';
+ }
+
+ // The running protected-font version drives which cipher applies.
+ let fontVer = 0;
+ const fontMatch = html.match(/protected-font-(\d+)/);
+ if (fontMatch) {
+ fontVer = parseInt(fontMatch[1], 10);
+ }
+ const cmap = pickCmap(fontVer);
+
+ // Decode the font-cmap obfuscation, then drop any leftover noise.
+ const cleaned = decodeChapter(content, cmap);
+ const readable = cleaned.replace(/[^-ۿݐ-ݿ\s،.;?!…0-9"'()\-–—:«»]/g, '');
+
+ // Wrap paragraphs
+ return readable
+ .split(/\n{2,}/)
+ .map(p => p.trim())
+ .filter(Boolean)
+ .map(p => `${p.replace(/\n/g, '
')}
`)
+ .join('');
+ }
+
+ async searchNovels(
+ searchTerm: string,
+ pageNo: number,
+ ): Promise {
+ if (pageNo !== 1) return [];
+
+ const works = await this.fetchAllWorks();
+ const term = searchTerm.trim().toLowerCase();
+
+ const filtered = works.filter(
+ w =>
+ w.titleAr?.toLowerCase().includes(term) ||
+ w.title?.toLowerCase().includes(term) ||
+ w.author?.toLowerCase().includes(term),
+ );
+
+ return filtered.map(w => this.toNovelItem(w));
+ }
+
+ resolveUrl = (path: string): string => this.site + path;
+}
+
+export default new MKNOV();
diff --git a/public/static/src/ar/mknov/icon.png b/public/static/src/ar/mknov/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..486a7e6b400018394a7a45abac41888b37530192
GIT binary patch
literal 7487
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
Date: Sun, 13 Sep 2026 08:25:13 +0300
Subject: [PATCH 2/3] fix(ar/mknov): use Array.from for chapter decode
TypeScript downlevels [...content] to a __spreadArray helper that requires
a real array and throws "Cannot use 'in' operator" on strings during
parseChapter. Switch to Array.from which iterates strings safely.
Co-Authored-By: Claude Code
---
plugins/arabic/mknov.ts | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/plugins/arabic/mknov.ts b/plugins/arabic/mknov.ts
index d871c2930..227d24a90 100644
--- a/plugins/arabic/mknov.ts
+++ b/plugins/arabic/mknov.ts
@@ -816,7 +816,9 @@ function pickCmap(fontVersion: number): Record {
/** Decode a raw T-record payload with the given cipher. */
function decodeChapter(content: string, cmap: Record): string {
return (
- [...content]
+ // Array.from() rather than `[...content]` — TypeScript downlevels the
+ // spread to a helper that requires a real array and breaks on strings.
+ Array.from(content)
.map(ch => cmap[ch] ?? ch)
.join('')
// CJK / Hangul / Katakana & other scripts the font renders as nothing
From da473c2495fc42e9f7e2242fdad76f37f1528261 Mon Sep 17 00:00:00 2001
From: Yemen
Date: Sun, 13 Sep 2026 08:34:19 +0300
Subject: [PATCH 3/3] chore(ar/mknov): add compiled plugin for immediate
testing
Co-Authored-By: Claude Code
---
.js/src/plugins/arabic/mknov.js | 1 +
1 file changed, 1 insertion(+)
create mode 100644 .js/src/plugins/arabic/mknov.js
diff --git a/.js/src/plugins/arabic/mknov.js b/.js/src/plugins/arabic/mknov.js
new file mode 100644
index 000000000..99c46cf71
--- /dev/null
+++ b/.js/src/plugins/arabic/mknov.js
@@ -0,0 +1 @@
+var t=this&&this.__awaiter||function(t,e,n,r){return new(n||(n=Promise))((function(o,i){function s(t){try{c(r.next(t))}catch(t){i(t)}}function a(t){try{c(r.throw(t))}catch(t){i(t)}}function c(t){var e;t.done?o(t.value):(e=t.value,e instanceof n?e:new n((function(t){t(e)}))).then(s,a)}c((r=r.apply(t,e||[])).next())}))},e=this&&this.__generator||function(t,e){var n,r,o,i={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]},s=Object.create(("function"==typeof Iterator?Iterator:Object).prototype);return s.next=a(0),s.throw=a(1),s.return=a(2),"function"==typeof Symbol&&(s[Symbol.iterator]=function(){return this}),s;function a(a){return function(c){return function(a){if(n)throw new TypeError("Generator is already executing.");for(;s&&(s=0,a[0]&&(i=0)),i;)try{if(n=1,r&&(o=2&a[0]?r.return:a[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,a[1])).done)return o;switch(r=0,o&&(a=[2&a[0],o.value]),a[0]){case 0:case 1:o=a;break;case 4:return i.label++,{value:a[1],done:!1};case 5:i.label++,r=a[1],a=[0];continue;case 7:a=i.ops.pop(),i.trys.pop();continue;default:if(!(o=i.trys,(o=o.length>0&&o[o.length-1])||6!==a[0]&&2!==a[0])){i=0;continue}if(3===a[0]&&(!o||a[1]>o[0]&&a[1]=this.maxOffset?[2,[]]:[4,this.fetchJson("".concat(this.baseUrl,"/api/works?offset=").concat(t,"&limit=").concat(50))];case 1:return[2,(e.sent()||[]).map((function(t){return r.toNovelItem(t)}))]}}))}))},i.prototype.parseNovel=function(n){return t(this,void 0,void 0,(function(){var t,o,i,s,a,c,u,l,h,f,p,v,d;return e(this,(function(e){switch(e.label){case 0:return t=n.replace("/novel/","").replace(/\/$/,""),[4,this.fetchAllWorks()];case 1:return o=e.sent(),i=o.find((function(e){return String(e.id)===t})),s={path:n,name:(null==i?void 0:i.titleAr)||(null==i?void 0:i.title)||"",cover:(null==i?void 0:i.image)||"",author:(null==i?void 0:i.author)||void 0,summary:(null==i?void 0:i.description)||"",genres:((null==i?void 0:i.genres)||[]).join(", "),status:((null==i?void 0:i.status)||"").toLowerCase().includes("مستمر")?r.NovelStatus.Ongoing:((null==i?void 0:i.status)||"").toLowerCase().includes("مكتمل")?r.NovelStatus.Completed:r.NovelStatus.Unknown},[4,this.fetchJson("".concat(this.baseUrl,"/api/works/").concat(t,"/chapters"))];case 2:for(a=e.sent(),c=[],u=a.volumes||[],l=0,h=u;l-1&&(v=c.indexOf('"',p+9))>-1)for(d=0,m=!1,b=v+1;b".concat(t.replace(/\n/g,"
"),"
")})).join("")]):[2,"المحتوى غير متاح.
"]}}))}))},i.prototype.searchNovels=function(n,r){return t(this,void 0,void 0,(function(){var t,o,i,s=this;return e(this,(function(e){switch(e.label){case 0:return 1!==r?[2,[]]:[4,this.fetchAllWorks()];case 1:return t=e.sent(),o=n.trim().toLowerCase(),i=t.filter((function(t){var e,n,r;return(null===(e=t.titleAr)||void 0===e?void 0:e.toLowerCase().includes(o))||(null===(n=t.title)||void 0===n?void 0:n.toLowerCase().includes(o))||(null===(r=t.author)||void 0===r?void 0:r.toLowerCase().includes(o))})),[2,i.map((function(t){return s.toNovelItem(t)}))]}}))}))},i}();exports.default=new i;
\ No newline at end of file