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 diff --git a/plugins/arabic/mknov.ts b/plugins/arabic/mknov.ts new file mode 100644 index 000000000..227d24a90 --- /dev/null +++ b/plugins/arabic/mknov.ts @@ -0,0 +1,1049 @@ +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 ( + // 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 + .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 000000000..486a7e6b4 Binary files /dev/null and b/public/static/src/ar/mknov/icon.png differ