From 9829f9e4556af9d148df66cfd05ccfdd26183e07 Mon Sep 17 00:00:00 2001
From: Batorian <lukas.richter.berlin@web.de>
Date: Fri, 28 Feb 2025 17:59:06 +0100
Subject: [PATCH 1/6] genesis: fix

---
 src/plugins/english/genesis.ts | 427 +++++++++++++++++++--------------
 1 file changed, 241 insertions(+), 186 deletions(-)

diff --git a/src/plugins/english/genesis.ts b/src/plugins/english/genesis.ts
index fcd45cad7..730724702 100644
--- a/src/plugins/english/genesis.ts
+++ b/src/plugins/english/genesis.ts
@@ -18,7 +18,7 @@ class Genesis implements Plugin.PluginBase {
   icon = 'src/en/genesis/icon.png';
   customCSS = 'src/en/genesis/customCSS.css';
   site = 'https://genesistudio.com';
-  version = '1.0.7';
+  version = '1.1.0';
 
   imageRequestInit?: Plugin.ImageRequestInit | undefined = {
     headers: {
@@ -39,7 +39,7 @@ class Genesis implements Plugin.PluginBase {
     { showLatestNovels, filters }: Plugin.PopularNovelsOptions,
   ): Promise<Plugin.SourceNovel[]> {
     if (pageNo !== 1) return [];
-    let link = `${this.site}/api/search?`;
+    let link = `${this.site}/api/novels/search?`;
     if (showLatestNovels) {
       link += 'sort=Recent';
     } else {
@@ -120,20 +120,21 @@ class Genesis implements Plugin.PluginBase {
 
       // Change string here if the chapters are stored under a different key
       const chapterKey = 'chapters';
-      if (typeof value === 'object' && value !== null && chapterKey in value) {
-        const chapterData = this.decodeData(data[value[chapterKey]]);
-
-        // Object.values will give us an array of arrays (any[][])
-        const chapterArrays: any[][] = Object.values(chapterData);
 
-        // Flatten and format the chapters
-        return chapterArrays.flatMap((chapters: any[]) => {
-          return chapters
-            .map((chapter: any) => this.formatChapter(chapter))
-            .filter(
-              (chapter): chapter is Plugin.ChapterItem => chapter !== null,
-            );
-        });
+      if (typeof value === 'object' && value !== null && chapterKey in value) {
+        const chapterData = data[value[chapterKey]];
+        const freeChapterData = chapterData.free;
+        const premiumChapterData = chapterData.premium;
+
+        const allChapterData = [
+          ...data[freeChapterData],
+          ...data[premiumChapterData],
+        ];
+
+        return allChapterData
+          .map((index: number) => data[index])
+          .map((chapter: any) => this.formatChapter(chapter, data))
+          .filter((chapter): chapter is Plugin.ChapterItem => chapter !== null);
       }
     }
 
@@ -141,27 +142,24 @@ class Genesis implements Plugin.PluginBase {
   }
 
   // Helper function to format an individual chapter
-  formatChapter(chapter: any): Plugin.ChapterItem | null {
-    const { id, chapter_title, chapter_number, required_tier, date_created } =
-      chapter;
-
+  formatChapter(chapter: any, data: any): Plugin.ChapterItem | null {
     // Ensure required fields are present and valid
     if (
-      id &&
-      chapter_title &&
-      chapter_number >= 0 &&
-      required_tier !== null &&
-      date_created
+      data[chapter.id] &&
+      data[chapter.chapter_title] &&
+      data[chapter.chapter_number] >= 0 &&
+      data[chapter.required_tier] !== null &&
+      data[chapter.date_created]
     ) {
-      const number = parseInt(chapter_number, 10) || 0;
-      const requiredTier = parseInt(required_tier, 10) || 0;
+      const number = parseInt(data[chapter.chapter_number], 10) || 0;
+      const requiredTier = parseInt(data[chapter.required_tier], 10) || 0;
 
       // Only process chapters with a 'requiredTier' of 0
       if (requiredTier === 0) {
         return {
-          name: `Chapter ${number}: ${chapter_title}`,
-          path: `/viewer/${id}`,
-          releaseTime: date_created,
+          name: `Chapter ${number} - ${data[chapter.chapter_title]}`,
+          path: `/viewer/${data[chapter.id]}`,
+          releaseTime: data[chapter.date_created],
           chapterNumber: number,
         };
       }
@@ -170,162 +168,219 @@ class Genesis implements Plugin.PluginBase {
     return null;
   }
 
-  decodeData(code: any) {
-    const offset = this.getOffsetIndex(code);
-    const params = this.getDecodeParams(code);
-    const constant = this.getConstant(code);
-    const data = this.getStringsArrayRaw(code);
-
-    const getDataAt = (x: number) => data[x - offset];
-
-    //reshuffle data array
-    // eslint-disable-next-line no-constant-condition
-    while (true) {
-      try {
-        const some_number = this.applyDecodeParams(params, getDataAt);
-        if (some_number === constant) break;
-        else data.push(data.shift());
-      } catch (err) {
-        data.push(data.shift());
-      }
-    }
-
-    return this.getChapterData(code, getDataAt);
-  }
-
-  getOffsetIndex(code: string) {
-    // @ts-ignore
-    const string = /{(\w+)=\1-0x(?<offset>[0-9a-f]+);/.exec(code).groups.offset;
-    return parseInt(string, 16);
-  }
-
-  /**
-   * @returns {string[]}
-   */
-  getStringsArrayRaw(code: string) {
-    // @ts-ignore
-    let json = /function \w+\(\){var \w+=(?<array>\['.+']);/.exec(code).groups
-      .array;
-
-    //replace string single quotes with double quotes and add escaped chars
-    json = json.replace(/'(.+?)'([,\]])/g, (match, p1, p2) => {
-      return `"${p1.replace(/\\x([0-9a-z]{2})/g, (match: any, p1: string) => {
-        //hexadecimal unicode escape chars
-        return String.fromCharCode(parseInt(p1, 16));
-      })}"${p2}`;
-    });
-
-    return JSON.parse(json);
-  }
-
-  /**
-   * @returns {{offset: number, divider: number, negated: boolean}[][]}
-   */
-  getDecodeParams(code: string) {
-    // @ts-ignore
-    const jsDecodeInt = /while\(!!\[]\){try{var \w+=(?<code>.+?);/.exec(code)
-      .groups.code;
-    const decodeSections = jsDecodeInt.split('+');
-    const params = [];
-    for (const section of decodeSections) {
-      params.push(this.decodeParamSection(section));
-    }
-    return params;
-  }
-
-  /**
-   * @param {string} section
-   * @returns {{offset: number, divider: number, negated: boolean}[]}
-   */
-  decodeParamSection(section: string) {
-    const sections = section.split('*');
-    const params = [];
-    for (const section of sections) {
-      // @ts-ignore
-      const offsetStr = /parseInt\(\w+\(0x(?<offset>[0-9a-f]+)\)\)/.exec(
-        section,
-      ).groups.offset;
-      const offset = parseInt(offsetStr, 16);
-      // @ts-ignore
-      const dividerStr = /\/0x(?<divider>[0-9a-f]+)/.exec(section).groups
-        .divider;
-      const divider = parseInt(dividerStr, 16);
-      const negated = section.includes('-');
-      params.push({ offset, divider, negated });
-    }
-    return params;
-  }
-
-  getConstant(code: string) {
-    // @ts-ignore
-    const constantStr = /}}}\(\w+,0x(?<constant>[0-9a-f]+)\),/.exec(code).groups
-      .constant;
-    return parseInt(constantStr, 16);
-  }
-
-  getChapterData(
-    code: string,
-    getDataAt: { (x: number): any; (arg0: number): any },
-  ) {
-    let chapterDataStr =
-      // @ts-ignore
-      /\),\(function\(\){var \w+=\w+;return(?<data>{.+?});/.exec(code).groups
-        .data;
-
-    //replace hex with decimal
-    chapterDataStr = chapterDataStr.replace(/:0x([0-9a-f]+)/g, (match, p1) => {
-      const hex = parseInt(p1, 16);
-      return `: ${hex}`;
-    });
-
-    //replace ![] with false and !![] with true
-    chapterDataStr = chapterDataStr
-      .replace(/:!!\[]/g, ':true')
-      .replace(/:!\[]/g, ':false');
-
-    //replace string single quotes with double quotes and add escaped chars
-    chapterDataStr = chapterDataStr.replace(
-      /'(.+?)'([,\]}:])/g,
-      (match, p1, p2) => {
-        return `"${p1.replace(/\\x([0-9a-z]{2})/g, (match: any, p1: string) => {
-          //hexadecimal unicode escape chars
-          return String.fromCharCode(parseInt(p1, 16));
-        })}"${p2}`;
-      },
-    );
-
-    //parse the data getting methods
-    chapterDataStr = chapterDataStr.replace(
-      // @ts-ignore
-      /:\w+\(0x(?<offset>[0-9a-f]+)\)/g,
-      (match, p1) => {
-        const offset = parseInt(p1, 16);
-        return `:${JSON.stringify(getDataAt(offset))}`;
-      },
-    );
-
-    return JSON.parse(chapterDataStr);
-  }
-
-  /**
-   * @param {{offset: number, divider: number, negated: boolean}[][]} params
-   * @param {function(number): string} getDataAt
-   */
-  applyDecodeParams(
-    params: { offset: number; divider: number; negated: boolean }[][],
-    getDataAt: { (x: number): any; (arg0: any): string },
-  ) {
-    let res = 0;
-    for (const paramAdd of params) {
-      let resInner = 1;
-      for (const paramMul of paramAdd) {
-        resInner *= parseInt(getDataAt(paramMul.offset)) / paramMul.divider;
-        if (paramMul.negated) resInner *= -1;
-      }
-      res += resInner;
-    }
-    return res;
-  }
+  // // Helper function to extract and format chapters
+  // extractChapters(data: any): Plugin.ChapterItem[] {
+  //   for (const key in data) {
+  //     const value = data[key];
+  //
+  //     // Change string here if the chapters are stored under a different key
+  //     const chapterKey = 'chapters';
+  //     if (typeof value === 'object' && value !== null && chapterKey in value) {
+  //       const chapterData = this.decodeData(data[value[chapterKey]]);
+  //
+  //       // Object.values will give us an array of arrays (any[][])
+  //       const chapterArrays: any[][] = Object.values(chapterData);
+  //
+  //       // Flatten and format the chapters
+  //       return chapterArrays.flatMap((chapters: any[]) => {
+  //         return chapters
+  //           .map((chapter: any) => this.formatChapter(chapter))
+  //           .filter(
+  //             (chapter): chapter is Plugin.ChapterItem => chapter !== null,
+  //           );
+  //       });
+  //     }
+  //   }
+  //
+  //   return [];
+  // }
+  //
+  // // Helper function to format an individual chapter
+  // formatChapter(chapter: any): Plugin.ChapterItem | null {
+  //   const { id, chapter_title, chapter_number, required_tier, date_created } =
+  //     chapter;
+  //
+  //   // Ensure required fields are present and valid
+  //   if (
+  //     id &&
+  //     chapter_title &&
+  //     chapter_number >= 0 &&
+  //     required_tier !== null &&
+  //     date_created
+  //   ) {
+  //     const number = parseInt(chapter_number, 10) || 0;
+  //     const requiredTier = parseInt(required_tier, 10) || 0;
+  //
+  //     // Only process chapters with a 'requiredTier' of 0
+  //     if (requiredTier === 0) {
+  //       return {
+  //         name: `Chapter ${number}: ${chapter_title}`,
+  //         path: `/viewer/${id}`,
+  //         releaseTime: date_created,
+  //         chapterNumber: number,
+  //       };
+  //     }
+  //   }
+  //
+  //   return null;
+  // }
+  //
+  // decodeData(code: any) {
+  //   const offset = this.getOffsetIndex(code);
+  //   const params = this.getDecodeParams(code);
+  //   const constant = this.getConstant(code);
+  //   const data = this.getStringsArrayRaw(code);
+  //
+  //   const getDataAt = (x: number) => data[x - offset];
+  //
+  //   //reshuffle data array
+  //   // eslint-disable-next-line no-constant-condition
+  //   while (true) {
+  //     try {
+  //       const some_number = this.applyDecodeParams(params, getDataAt);
+  //       if (some_number === constant) break;
+  //       else data.push(data.shift());
+  //     } catch (err) {
+  //       data.push(data.shift());
+  //     }
+  //   }
+  //
+  //   return this.getChapterData(code, getDataAt);
+  // }
+  //
+  // getOffsetIndex(code: string) {
+  //   // @ts-ignore
+  //   const string = /{(\w+)=\1-0x(?<offset>[0-9a-f]+);/.exec(code).groups.offset;
+  //   return parseInt(string, 16);
+  // }
+  //
+  // /**
+  //  * @returns {string[]}
+  //  */
+  // getStringsArrayRaw(code: string) {
+  //   // @ts-ignore
+  //   let json = /function \w+\(\){var \w+=(?<array>\['.+']);/.exec(code).groups
+  //     .array;
+  //
+  //   //replace string single quotes with double quotes and add escaped chars
+  //   json = json.replace(/'(.+?)'([,\]])/g, (match, p1, p2) => {
+  //     return `"${p1.replace(/\\x([0-9a-z]{2})/g, (match: any, p1: string) => {
+  //       //hexadecimal unicode escape chars
+  //       return String.fromCharCode(parseInt(p1, 16));
+  //     })}"${p2}`;
+  //   });
+  //
+  //   return JSON.parse(json);
+  // }
+  //
+  // /**
+  //  * @returns {{offset: number, divider: number, negated: boolean}[][]}
+  //  */
+  // getDecodeParams(code: string) {
+  //   // @ts-ignore
+  //   const jsDecodeInt = /while\(!!\[]\){try{var \w+=(?<code>.+?);/.exec(code)
+  //     .groups.code;
+  //   const decodeSections = jsDecodeInt.split('+');
+  //   const params = [];
+  //   for (const section of decodeSections) {
+  //     params.push(this.decodeParamSection(section));
+  //   }
+  //   return params;
+  // }
+  //
+  // /**
+  //  * @param {string} section
+  //  * @returns {{offset: number, divider: number, negated: boolean}[]}
+  //  */
+  // decodeParamSection(section: string) {
+  //   const sections = section.split('*');
+  //   const params = [];
+  //   for (const section of sections) {
+  //     // @ts-ignore
+  //     const offsetStr = /parseInt\(\w+\(0x(?<offset>[0-9a-f]+)\)\)/.exec(
+  //       section,
+  //     ).groups.offset;
+  //     const offset = parseInt(offsetStr, 16);
+  //     // @ts-ignore
+  //     const dividerStr = /\/0x(?<divider>[0-9a-f]+)/.exec(section).groups
+  //       .divider;
+  //     const divider = parseInt(dividerStr, 16);
+  //     const negated = section.includes('-');
+  //     params.push({ offset, divider, negated });
+  //   }
+  //   return params;
+  // }
+  //
+  // getConstant(code: string) {
+  //   // @ts-ignore
+  //   const constantStr = /}}}\(\w+,0x(?<constant>[0-9a-f]+)\),/.exec(code).groups
+  //     .constant;
+  //   return parseInt(constantStr, 16);
+  // }
+  //
+  // getChapterData(
+  //   code: string,
+  //   getDataAt: { (x: number): any; (arg0: number): any },
+  // ) {
+  //   let chapterDataStr =
+  //     // @ts-ignore
+  //     /\),\(function\(\){var \w+=\w+;return(?<data>{.+?});/.exec(code).groups
+  //       .data;
+  //
+  //   //replace hex with decimal
+  //   chapterDataStr = chapterDataStr.replace(/:0x([0-9a-f]+)/g, (match, p1) => {
+  //     const hex = parseInt(p1, 16);
+  //     return `: ${hex}`;
+  //   });
+  //
+  //   //replace ![] with false and !![] with true
+  //   chapterDataStr = chapterDataStr
+  //     .replace(/:!!\[]/g, ':true')
+  //     .replace(/:!\[]/g, ':false');
+  //
+  //   //replace string single quotes with double quotes and add escaped chars
+  //   chapterDataStr = chapterDataStr.replace(
+  //     /'(.+?)'([,\]}:])/g,
+  //     (match, p1, p2) => {
+  //       return `"${p1.replace(/\\x([0-9a-z]{2})/g, (match: any, p1: string) => {
+  //         //hexadecimal unicode escape chars
+  //         return String.fromCharCode(parseInt(p1, 16));
+  //       })}"${p2}`;
+  //     },
+  //   );
+  //
+  //   //parse the data getting methods
+  //   chapterDataStr = chapterDataStr.replace(
+  //     // @ts-ignore
+  //     /:\w+\(0x(?<offset>[0-9a-f]+)\)/g,
+  //     (match, p1) => {
+  //       const offset = parseInt(p1, 16);
+  //       return `:${JSON.stringify(getDataAt(offset))}`;
+  //     },
+  //   );
+  //
+  //   return JSON.parse(chapterDataStr);
+  // }
+  //
+  // /**
+  //  * @param {{offset: number, divider: number, negated: boolean}[][]} params
+  //  * @param {function(number): string} getDataAt
+  //  */
+  // applyDecodeParams(
+  //   params: { offset: number; divider: number; negated: boolean }[][],
+  //   getDataAt: { (x: number): any; (arg0: any): string },
+  // ) {
+  //   let res = 0;
+  //   for (const paramAdd of params) {
+  //     let resInner = 1;
+  //     for (const paramMul of paramAdd) {
+  //       resInner *= parseInt(getDataAt(paramMul.offset)) / paramMul.divider;
+  //       if (paramMul.negated) resInner *= -1;
+  //     }
+  //     res += resInner;
+  //   }
+  //   return res;
+  // }
 
   async parseChapter(chapterPath: string): Promise<string> {
     const url = `${this.site}${chapterPath}/__data.json?x-sveltekit-invalidated=001`;
@@ -347,7 +402,7 @@ class Genesis implements Plugin.PluginBase {
     pageNo: number,
   ): Promise<Plugin.SourceNovel[]> {
     if (pageNo !== 1) return [];
-    const url = `${this.site}/api/search?serialization=All&sort=Popular&title=${searchTerm}`;
+    const url = `${this.site}/api/novels/search?serialization=All&sort=Popular&title=${searchTerm}`;
     const json = await fetchApi(url).then(r => r.json());
     return this.parseNovels(json);
   }

From ba19747078ae0730e3eb4e52d0405c567964fa67 Mon Sep 17 00:00:00 2001
From: Batorian <lukas.richter.berlin@web.de>
Date: Fri, 28 Feb 2025 18:46:13 +0100
Subject: [PATCH 2/6] genesis: fix

---
 src/plugins/english/genesis.ts | 141 +++++++++++++++++++++++----------
 1 file changed, 98 insertions(+), 43 deletions(-)

diff --git a/src/plugins/english/genesis.ts b/src/plugins/english/genesis.ts
index 730724702..5abd3125c 100644
--- a/src/plugins/english/genesis.ts
+++ b/src/plugins/english/genesis.ts
@@ -52,6 +52,13 @@ class Genesis implements Plugin.PluginBase {
     return this.parseNovels(json);
   }
 
+  // Helper function to extract novel data from nodes
+  extractData(nodes: any[]): any {
+    return nodes
+      .filter((node: { type: string }) => node.type === 'data')
+      .map((node: { data: any }) => node.data)[0];
+  }
+
   async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
     const url = `${this.site}${novelPath}/__data.json?x-sveltekit-invalidated=001`;
 
@@ -60,7 +67,7 @@ class Genesis implements Plugin.PluginBase {
     const nodes = json.nodes;
 
     // Extract the main novel data from the nodes
-    const data = this.extractNovelData(nodes);
+    const data = this.extractData(nodes);
 
     // Initialize the novel object with default values
     const novel: Plugin.SourceNovel = {
@@ -82,13 +89,6 @@ class Genesis implements Plugin.PluginBase {
     return novel;
   }
 
-  // Helper function to extract novel data from nodes
-  extractNovelData(nodes: any[]): any {
-    return nodes
-      .filter((node: { type: string }) => node.type === 'data')
-      .map((node: { data: any }) => node.data)[0];
-  }
-
   // Helper function to populate novel metadata
   populateNovelMetadata(novel: Plugin.SourceNovel, data: any): void {
     for (const key in data) {
@@ -115,52 +115,73 @@ class Genesis implements Plugin.PluginBase {
 
   // Helper function to extract and format chapters
   extractChapters(data: any): Plugin.ChapterItem[] {
+    const chapterKey = 'chapters';
+    const chapters: Plugin.ChapterItem[] = [];
+
+    // Iterate over each property in data to find chapter containers
     for (const key in data) {
       const value = data[key];
 
-      // Change string here if the chapters are stored under a different key
-      const chapterKey = 'chapters';
+      // Look for an object with a 'chapters' key
+      if (value && typeof value === 'object' && chapterKey in value) {
+        const chaptersIndexKey = value[chapterKey];
+        const chapterData = data[chaptersIndexKey];
+        if (!chapterData || typeof chapterData !== 'object') continue;
 
-      if (typeof value === 'object' && value !== null && chapterKey in value) {
-        const chapterData = data[value[chapterKey]];
-        const freeChapterData = chapterData.free;
-        const premiumChapterData = chapterData.premium;
+        // Dynamically get chapter indices from every category (free, premium, etc.)
+        let allChapterIndices: number[] = [];
+        for (const category of Object.keys(chapterData)) {
+          const chapterIndices = data[chapterData[category]];
+          if (Array.isArray(chapterIndices)) {
+            allChapterIndices = allChapterIndices.concat(chapterIndices);
+          }
+        }
 
-        const allChapterData = [
-          ...data[freeChapterData],
-          ...data[premiumChapterData],
-        ];
-
-        return allChapterData
+        // Format each chapter and add only valid ones
+        const formattedChapters = allChapterIndices
           .map((index: number) => data[index])
           .map((chapter: any) => this.formatChapter(chapter, data))
           .filter((chapter): chapter is Plugin.ChapterItem => chapter !== null);
+
+        chapters.push(...formattedChapters);
       }
     }
 
-    return [];
+    return chapters;
   }
 
   // Helper function to format an individual chapter
   formatChapter(chapter: any, data: any): Plugin.ChapterItem | null {
-    // Ensure required fields are present and valid
+    const { id, chapter_title, chapter_number, required_tier, date_created } =
+      chapter;
+
+    // Destructure from data using computed property names based on chapter keys
+    const {
+      [id]: chapterId,
+      [chapter_title]: chapterTitle,
+      [chapter_number]: chapterNumberVal,
+      [required_tier]: requiredTierVal,
+      [date_created]: dateCreated,
+    } = data;
+
+    // Validate required fields
     if (
-      data[chapter.id] &&
-      data[chapter.chapter_title] &&
-      data[chapter.chapter_number] >= 0 &&
-      data[chapter.required_tier] !== null &&
-      data[chapter.date_created]
+      chapterId &&
+      chapterTitle &&
+      chapterNumberVal >= 0 &&
+      requiredTierVal !== null &&
+      dateCreated
     ) {
-      const number = parseInt(data[chapter.chapter_number], 10) || 0;
-      const requiredTier = parseInt(data[chapter.required_tier], 10) || 0;
+      const chapterNumber = parseInt(String(chapterNumberVal), 10) || 0;
+      const requiredTier = parseInt(String(requiredTierVal), 10) || 0;
 
-      // Only process chapters with a 'requiredTier' of 0
+      // Only process chapters that do not require a premium tier
       if (requiredTier === 0) {
         return {
-          name: `Chapter ${number} - ${data[chapter.chapter_title]}`,
-          path: `/viewer/${data[chapter.id]}`,
-          releaseTime: data[chapter.date_created],
-          chapterNumber: number,
+          name: `Chapter ${chapterNumber} - ${chapterTitle}`,
+          path: `/viewer/${chapterId}`,
+          releaseTime: dateCreated,
+          chapterNumber: chapterNumber,
         };
       }
     }
@@ -384,17 +405,51 @@ class Genesis implements Plugin.PluginBase {
 
   async parseChapter(chapterPath: string): Promise<string> {
     const url = `${this.site}${chapterPath}/__data.json?x-sveltekit-invalidated=001`;
+
+    // Fetch the novel's data in JSON format
     const json = await fetchApi(url).then(r => r.json());
     const nodes = json.nodes;
-    const data = nodes
-      .filter((node: { type: string }) => node.type === 'data')
-      .map((node: { data: any }) => node.data)[0];
-    const content = data[data[0].gs] ?? data[19];
-    const notes = data[data[0].notes];
-    const footnotes = data[data[0].footnotes];
-    return (
-      content + (notes ? `<h2>Notes</h2>${notes}` : '') + (footnotes ?? '')
-    );
+
+    // Extract the main novel data from the nodes
+    const data = this.extractData(nodes);
+
+    const contentKey = 'content';
+    const notesKey = 'notes';
+    const footnotesKey = 'footnotes';
+
+    // Iterate over each property in data to find chapter containers
+    for (const key in data) {
+      const mapping = data[key];
+
+      // Look for an object with a 'chapters' key
+      if (
+        mapping &&
+        typeof mapping === 'object' &&
+        contentKey in mapping &&
+        notesKey in mapping &&
+        footnotesKey in mapping
+      ) {
+        // Use the found mapping object to determine the actual keys.
+        const contentMappingKey = mapping[contentKey];
+        const notesMappingKey = mapping[notesKey];
+        const footnotesMappingKey = mapping[footnotesKey];
+
+        // Retrieve the chapter's content, notes, and footnotes using the mapping.
+        // Provide a fallback for content if needed.
+        const content = data[contentMappingKey] ?? data[19];
+        const notes = data[notesMappingKey];
+        const footnotes = data[footnotesMappingKey];
+
+        return (
+          content +
+          (notes ? `<b>Notes</b>${notes}` : '') +
+          (footnotes ? `<b>Footnotes</b>${footnotes}` : '')
+        );
+      }
+    }
+
+    // If no mapping object was found, return an empty string or handle appropriately.
+    return '';
   }
 
   async searchNovels(

From d868f6d90f44bdf96cb5790b46ce26e1bc5894fd Mon Sep 17 00:00:00 2001
From: Batorian <lukas.richter.berlin@web.de>
Date: Fri, 28 Feb 2025 18:50:17 +0100
Subject: [PATCH 3/6] genesis: fix popularNovels

---
 src/plugins/english/genesis.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/plugins/english/genesis.ts b/src/plugins/english/genesis.ts
index 5abd3125c..74e807d3b 100644
--- a/src/plugins/english/genesis.ts
+++ b/src/plugins/english/genesis.ts
@@ -41,7 +41,7 @@ class Genesis implements Plugin.PluginBase {
     if (pageNo !== 1) return [];
     let link = `${this.site}/api/novels/search?`;
     if (showLatestNovels) {
-      link += 'sort=Recent';
+      link += 'serialization=All&sort=Recent';
     } else {
       if (filters!.genres.value) {
         link += filters!.genres.value;

From c5a673dba260a0fec8fa81dce4edecb045c27922 Mon Sep 17 00:00:00 2001
From: Batorian <lukas.richter.berlin@web.de>
Date: Fri, 28 Feb 2025 19:04:39 +0100
Subject: [PATCH 4/6] genesis: add line breaks

---
 package.json                   | 2 +-
 src/plugins/english/genesis.ts | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/package.json b/package.json
index 6330496cf..3d39a4a74 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "lnreader-plugins",
-  "version": "3.0.0",
+  "version": "3.0.1",
   "description": "Plugins repo for LNReader",
   "main": "index.js",
   "type": "module",
diff --git a/src/plugins/english/genesis.ts b/src/plugins/english/genesis.ts
index 74e807d3b..aa08e702e 100644
--- a/src/plugins/english/genesis.ts
+++ b/src/plugins/english/genesis.ts
@@ -442,8 +442,8 @@ class Genesis implements Plugin.PluginBase {
 
         return (
           content +
-          (notes ? `<b>Notes</b>${notes}` : '') +
-          (footnotes ? `<b>Footnotes</b>${footnotes}` : '')
+          (notes ? `<b>Notes</b><br>${notes}` : '') +
+          (footnotes ? `<b>Footnotes</b><br>${footnotes}` : '')
         );
       }
     }

From d8d6f431acd256fb209b542ea89256d99a0ed5cd Mon Sep 17 00:00:00 2001
From: Batorian <lukas.richter.berlin@web.de>
Date: Fri, 28 Feb 2025 19:07:16 +0100
Subject: [PATCH 5/6] genesis: revert version

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 3d39a4a74..6330496cf 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "lnreader-plugins",
-  "version": "3.0.1",
+  "version": "3.0.0",
   "description": "Plugins repo for LNReader",
   "main": "index.js",
   "type": "module",

From c4e81a601ab7796aa4edc07044f9c98c0d093e5e Mon Sep 17 00:00:00 2001
From: Batorian <lukas.richter.berlin@web.de>
Date: Sat, 1 Mar 2025 03:26:23 +0100
Subject: [PATCH 6/6] genesis: fix popular novels

---
 src/plugins/english/genesis.ts | 58 +++++++++++++++++++---------------
 1 file changed, 33 insertions(+), 25 deletions(-)

diff --git a/src/plugins/english/genesis.ts b/src/plugins/english/genesis.ts
index aa08e702e..286e8d333 100644
--- a/src/plugins/english/genesis.ts
+++ b/src/plugins/english/genesis.ts
@@ -36,17 +36,25 @@ class Genesis implements Plugin.PluginBase {
 
   async popularNovels(
     pageNo: number,
-    { showLatestNovels, filters }: Plugin.PopularNovelsOptions,
-  ): Promise<Plugin.SourceNovel[]> {
+    {
+      showLatestNovels,
+      filters,
+    }: Plugin.PopularNovelsOptions<typeof this.filters>,
+  ): Promise<Plugin.NovelItem[]> {
     if (pageNo !== 1) return [];
     let link = `${this.site}/api/novels/search?`;
     if (showLatestNovels) {
       link += 'serialization=All&sort=Recent';
     } else {
-      if (filters!.genres.value) {
-        link += filters!.genres.value;
+      if (filters?.genres.value.length) {
+        link += 'genres=' + filters.genres.value.join('&genres=') + '&';
+      }
+      if (filters?.storyStatus.value) {
+        link += 'serialization=' + `${filters.storyStatus.value}` + '&';
+      }
+      if (filters?.sort.value) {
+        link += 'sort=' + `${filters.sort.value}`;
       }
-      link += `&${filters!.storyStatus.value}&${filters!.sort.value}`;
     }
     const json = await fetchApi(link).then(r => r.json());
     return this.parseNovels(json);
@@ -465,21 +473,21 @@ class Genesis implements Plugin.PluginBase {
   filters = {
     sort: {
       label: 'Sort Results By',
-      value: 'sort=Popular',
+      value: 'Popular',
       options: [
-        { label: 'Popular', value: 'sort=Popular' },
-        { label: 'Recent', value: 'sort=Recent' },
-        { label: 'Views', value: 'sort=Views' },
+        { label: 'Popular', value: 'Popular' },
+        { label: 'Recent', value: 'Recent' },
+        { label: 'Views', value: 'Views' },
       ],
       type: FilterTypes.Picker,
     },
     storyStatus: {
       label: 'Status',
-      value: 'serialization=All',
+      value: 'All',
       options: [
-        { label: 'All', value: 'serialization=All' },
-        { label: 'Ongoing', value: 'serialization=Ongoing' },
-        { label: 'Completed', value: 'serialization=Completed' },
+        { label: 'All', value: 'All' },
+        { label: 'Ongoing', value: 'Ongoing' },
+        { label: 'Completed', value: 'Completed' },
       ],
       type: FilterTypes.Picker,
     },
@@ -487,18 +495,18 @@ class Genesis implements Plugin.PluginBase {
       label: 'Genres',
       value: [],
       options: [
-        { label: 'Action', value: 'genres=Action' },
-        { label: 'Comedy', value: 'genres=Comedy' },
-        { label: 'Drama', value: 'genres=Drama' },
-        { label: 'Fantasy', value: 'genres=Fantasy' },
-        { label: 'Harem', value: 'genres=Harem' },
-        { label: 'Martial Arts', value: 'genres=Martial+Arts' },
-        { label: 'Modern', value: 'genres=Modern' },
-        { label: 'Mystery', value: 'genres=Mystery' },
-        { label: 'Psychological', value: 'genres=Psychological' },
-        { label: 'Romance', value: 'genres=Romance' },
-        { label: 'Slice of life', value: 'genres=Slice+of+Life' },
-        { label: 'Tragedy', value: 'genres=Tragedy' },
+        { label: 'Action', value: 'Action' },
+        { label: 'Comedy', value: 'Comedy' },
+        { label: 'Drama', value: 'Drama' },
+        { label: 'Fantasy', value: 'Fantasy' },
+        { label: 'Harem', value: 'Harem' },
+        { label: 'Martial Arts', value: 'Martial Arts' },
+        { label: 'Modern', value: 'Modern' },
+        { label: 'Mystery', value: 'Mystery' },
+        { label: 'Psychological', value: 'Psychological' },
+        { label: 'Romance', value: 'Romance' },
+        { label: 'Slice of life', value: 'Slice of Life' },
+        { label: 'Tragedy', value: 'Tragedy' },
       ],
       type: FilterTypes.CheckboxGroup,
     },
