Skip to content

fix: Novel fire chapter fetch issue - #2099

Closed
G0yp wants to merge 4 commits into
lnreader:masterfrom
G0yp:novelFire-chapter-fetch-issue
Closed

fix: Novel fire chapter fetch issue#2099
G0yp wants to merge 4 commits into
lnreader:masterfrom
G0yp:novelFire-chapter-fetch-issue

Conversation

@G0yp

@G0yp G0yp commented Apr 2, 2026

Copy link
Copy Markdown
Contributor

Looks like in the paged mode changing the total pages to divide by the number of chapters per page to 50 instead of 100 fixed the missing chapters. Tested in Plugin Playground and the app and it seems to be working.

I think the issue started because Novel Fire used to have 100 chapters per page and they changed it to 50.

Closes issue #2097

Checklist

  • Update version code if an existing plugin was modified
  • Test changes in Plugin Playground or the app
  • Reference related issues in the PR body (e.g. Closes #xyz)

.text()
.trim();
novel.totalPages = Math.ceil(parseInt(totalChapters) / 100);
novel.totalPages = Math.ceil(parseInt(totalChapters) / 50);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there anyway to dynamically get this number of chapters per page instead of hard coding?

In case NovelFire changes it again in future, it would need a plugin update.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const firstPage = await this.parsePage(novelPath, '1');
const chaptersPerPage = firstPage.chapters.length;
novel.totalPages = chaptersPerPage > 0
  ? Math.ceil(parseInt(totalChapters) / chaptersPerPage)
  : 1;

This is what I've got after a bit of looking around. But this requires an extra API call, so not sure if there are better solutions.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or if we want to have a fallback -

let chaptersPerPage = 50; // fallback
try {
  const firstPage = await this.parsePage(novelPath, '1');
  const count = firstPage.chapters.length;
  if (count > 0) chaptersPerPage = count;
} catch (e) {
  console.warn('[NovelFire] Failed to get chaptersPerPage, falling back to 50:', e);
}
novel.totalPages = Math.ceil(parseInt(totalChapters) / chaptersPerPage);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's a bad idea to make the extra request first to get the correct number of pages. The getAllChaptersForce already makes a separate request for each page. The only other solution I have in mind would be to fetch the total amount of chapters from the main novel page and then keep iterating through pages until all the chapters are fetched. Either way is one more request. I'm not great at Javascript, so not sure what the exact implementation would look like, I just wanted to at least get the plugin into a working state.

Looking a little bit further in though, it looks like the page that the parsePage function fetches has the latest chapter number under the CSS selector: "header.container > p:nth-child(5) > a:nth-child(1)". I think it would be possible to just make the fetchPage function additionally return that value on top of what it returns already so we can have the total amount of chapters without making an extra request since we would always be grabbing the first page no matter what. Once we have that getAllChaptersForce would just have to be modified so it keeps fetching the next page until allChapters.length() is equal to the most recent chapter.

@vnkavali vnkavali Apr 5, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once we have that getAllChaptersForce would just have to be modified so it keeps fetching the next page until allChapters.length() is equal to the most recent chapter.

This might involve more changes than expected, as the API seems to be designed to do things in paralel based on the total number of pages passed along.

I am in favour of getting the fix in firstly to resolve the issue.

@Seuhen Seuhen Apr 5, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this might fix it for now but it should have a dynamic fix that contends to future updates and changes to the site. All in all, I am in favor of pushing this fix for now. It's feasible and will get the job done.

Also, I am a newbie at this kind of thing and would like your help in fixing it for myself temporarily because don't know when the issue will get fixed officially.

Thanks and good work, mate!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made this PR mostly as a hotfix to get things working again, I agree that it needs a minor revamp to prevent this issue in the future.

Also, I am a newbie at this kind of thing and would like your help in fixing it for myself temporarily because don't know when the issue will get fixed officially.

You should be able to fork the repo, make the one line change to the file, and then run "npm run publish:plugins -- --all-branches" on your local copy of the repo. I couldn't get the GitHub action working on my fork, but manually running the command worked. You can then add your repo as a plugin repo in the app.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, thank you!

@K1ngfish3r

K1ngfish3r commented Apr 9, 2026

Copy link
Copy Markdown
Collaborator

please add the following

    totalChapters: string,
  ): Promise<Plugin.ChapterItem[]> {
    const url = `${this.site}ajax/listChapterDataAjax`
    const params = new URLSearchParams({
      draw: "1",
      "columns[0][data]": "n_sort",
      "columns[0][name]": "cmm_posts_detail.n_sort",
      "columns[0][searchable]": "true",
      "columns[0][orderable]": "true",
      "columns[0][search][value]": "",
      "columns[0][search][regex]": "false",

      "columns[1][data]": "bookmark_created_at",
      "columns[1][name]": "bookmark_chapters.created_at",
      "columns[1][searchable]": "false",
      "columns[1][orderable]": "true",
      "columns[1][search][value]": "",
      "columns[1][search][regex]": "false",

      "order[0][column]": "0",
      "order[0][dir]": "asc",
      "order[0][name]": "cmm_posts_detail.n_sort",

      start: "0",
      length: totalChapters,
      "search[value]": "",
      "search[regex]": "false",
      post_id: post_id,
      only_bookmark: "false",
      _: Date.now().toString()
    });

    const result = await fetchApi(`${url}?${params.toString()}`);
    const post_id = $('#novel-report').attr('report-post_id') || '0';
    const totalChapters = $('.header-stats i.icon-book-open').parent().text().trim();

    try {
      novel.chapters = await this.getAllChapters(novelPath, post_id, totalChapters);
    } catch (error) {

@G0yp

G0yp commented Apr 10, 2026

Copy link
Copy Markdown
Contributor Author

@K1ngfish3r Please see new commit.

@K1ngfish3r

Copy link
Copy Markdown
Collaborator

The ajax url has a ajax/ before the listChapterDataAjax part

Rather than getting totalChapters directly, am now trying to find a way to smuggle post_id to parsePage to keep this undercover

@K1ngfish3r

Copy link
Copy Markdown
Collaborator

smuggling not required! here's some code, (remove comments)

  webStorageUtilized = true; // enable this
  draw = 0;
    if (pageNo === 1) {
      this.novelList = [];
      this.draw = 0; // reset this
    }
  async parseNovel(
    novelPath: string,
  ): Promise<Plugin.SourceNovel & { totalPages: number }> {
    this.draw = 0; // reset here too
   const post_id = $('#novel-report').attr('report-post_id');
   if (post_id) {
     storage.set(`${this.id}_postid_${novelPath}`, post_id);
   }
  async parsePage(novelPath: string, page: string): Promise<Plugin.SourcePage> {
   const post_id = storage.get(`${this.id}_postid_${novelPath}`);

   if (post_id && !isNaN(Number(post_id))) {
     try {
       const chapters = await this.getAllChapters(novelPath, post_id, page);
       return { chapters };
     } catch (e) {
       // Fallback to scraping if AJAX fails
     }
   }

   const url = `${this.site}${novelPath}/chapters?page=${page}`;
   ...
  async getAllChapters(
   novelPath: string,
   post_id: string,
   page: string,
 ): Promise<Plugin.ChapterItem[]> {
   const url = `${this.site}ajax/listChapterDataAjax`;
   const start = (parseInt(page) - 1) * 50;
   this.draw++;
   const params = new URLSearchParams({
     draw: this.draw.toString(),
     'columns[0][data]': 'n_sort',
     'columns[0][name]': 'cmm_posts_detail.n_sort',
     'columns[0][searchable]': 'true',
     'columns[0][orderable]': 'true',
     'columns[0][search][value]': '',
     'columns[0][search][regex]': 'false',

     'columns[1][data]': 'bookmark_created_at',
     'columns[1][name]': 'bookmark_chapters.created_at',
     'columns[1][searchable]': 'false',
     'columns[1][orderable]': 'true',
     'columns[1][search][value]': '',
     'columns[1][search][regex]': 'false',

     'order[0][column]': '0',
     'order[0][dir]': 'asc',
     'order[0][name]': 'cmm_posts_detail.n_sort',

     start: start.toString(),
     length: '50',
     'search[value]': '',
     'search[regex]': 'false',
     post_id: post_id,
     only_bookmark: 'false',
     _: Date.now().toString(),
   });

   const result = await fetchApi(`${url}?${params.toString()}`);

@K1ngfish3r

Copy link
Copy Markdown
Collaborator

this can also probably also be used for getAllChaptersForce, but I haven't written that bit

@G0yp

G0yp commented Apr 10, 2026

Copy link
Copy Markdown
Contributor Author

@K1ngfish3r Should all of this be in a separate PR? I just wanted to get a PR in so the plugin at least functions.

@K1ngfish3r

Copy link
Copy Markdown
Collaborator

@K1ngfish3r Should all of this be in a separate PR? I just wanted to get a PR in so the plugin at least functions.

a3cf999

Your changes has already been added lol
I'll make a separate PR then, mb

@G0yp

G0yp commented Apr 10, 2026

Copy link
Copy Markdown
Contributor Author

Sounds good, closing this then.

@G0yp G0yp closed this Apr 10, 2026
@G0yp
G0yp deleted the novelFire-chapter-fetch-issue branch April 23, 2026 03:36
@github-actions github-actions Bot locked as resolved and limited conversation to collaborators Jul 23, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants