@@ -9,21 +9,34 @@ import { storage } from '@libs/storage';
99class NovelFire implements Plugin . PluginBase {
1010 id = 'novelfire' ;
1111 name = 'Novel Fire' ;
12- version = '1.2 .0' ;
12+ version = '1.3 .0' ;
1313 icon = 'src/en/novelfire/icon.png' ;
1414 site = 'https://novelfire.net/' ;
15-
15+ webStorageUtilized = true ;
1616 novelList : string [ ] = [ ] ;
17+ draw = 0 ;
1718
18- singlePage = storage . get ( 'singlePage' ) ;
1919 pluginSettings = {
20+ pageLength : {
21+ value : '-1' ,
22+ label : 'Page Mode (Change if Broken)' ,
23+ type : 'Select' ,
24+ options : [
25+ { label : '100 (Fallback Mode)' , value : '100' } ,
26+ { label : '200' , value : '200' } ,
27+ { label : '500' , value : '500' } ,
28+ { label : 'All' , value : '-1' } ,
29+ ] ,
30+ } ,
2031 singlePage : {
2132 value : '' ,
2233 label :
2334 'Force load all chapters on a single page (Slower & use more data)' ,
2435 type : 'Switch' ,
2536 } ,
2637 } ;
38+ singlePage = storage . get ( 'singlePage' ) ;
39+ pageLength = storage . get ( 'pageLength' ) ;
2740
2841 async getCheerio ( url : string , search : boolean ) : Promise < CheerioAPI > {
2942 const r = await fetchApi ( url ) ;
@@ -46,18 +59,20 @@ class NovelFire implements Plugin.PluginBase {
4659 ) : Plugin . NovelItem [ ] {
4760 return loadedCheerio ( selector )
4861 . map ( ( _ , el ) => {
49- const titleElement = loadedCheerio ( el ) . find ( '.novel-title > a' ) ;
50- const fallbackElement = loadedCheerio ( el ) . find ( 'a' ) ;
62+ const $el = loadedCheerio ( el ) ;
63+ const titleElement = $el . find ( '.novel-title > a' ) ;
64+ const fallbackElement = $el . find ( 'a' ) ;
5165
5266 const novelName =
5367 titleElement . text ( ) ||
5468 fallbackElement . attr ( 'title' ) ||
5569 'No Title Found' ;
5670
57- const imgElement = loadedCheerio ( el ) . find ( '.novel-cover > img' ) ;
58- const novelCover =
59- this . site +
60- deSlash ( imgElement . attr ( 'data-src' ) || imgElement . attr ( 'src' ) || '' ) ;
71+ const imgElement = $el . find ( '.novel-cover > img' ) ;
72+ const rawSrc = imgElement . attr ( 'data-src' ) ?? imgElement . attr ( 'src' ) ;
73+ const novelCover = rawSrc
74+ ? new URL ( rawSrc , this . site ) . href
75+ : defaultCover ;
6176
6277 const novelPath =
6378 titleElement . attr ( 'href' ) || fallbackElement . attr ( 'href' ) ;
@@ -67,7 +82,7 @@ class NovelFire implements Plugin.PluginBase {
6782 return {
6883 name : novelName ,
6984 cover : novelCover ,
70- path : deSlash ( novelPath . replace ( this . site , '' ) ) ,
85+ path : new URL ( novelPath , this . site ) . pathname . substring ( 1 ) ,
7186 } ;
7287 } )
7388 . get ( )
@@ -91,6 +106,7 @@ class NovelFire implements Plugin.PluginBase {
91106 ) : Promise < Plugin . NovelItem [ ] > {
92107 if ( pageNo === 1 ) {
93108 this . novelList = [ ] ;
109+ this . draw = 0 ;
94110 }
95111 const url = this . site + 'search-adv' ;
96112 const params = new URLSearchParams ( ) ;
@@ -121,9 +137,44 @@ class NovelFire implements Plugin.PluginBase {
121137 async getAllChapters (
122138 novelPath : string ,
123139 post_id : string ,
140+ page : string ,
124141 ) : Promise < Plugin . ChapterItem [ ] > {
125- const url = `${ this . site } listChapterDataAjax?post_id=${ post_id } ` ;
126- const result = await fetchApi ( url ) ;
142+ const length = parseInt ( this . pageLength ) || - 1 ;
143+ const url = `${ this . site } ajax/listChapterDataAjax` ;
144+ const start = length === - 1 ? 0 : ( parseInt ( page ) - 1 ) * length ;
145+ this . draw ++ ;
146+ const params = new URLSearchParams ( {
147+ draw : this . draw . toString ( ) ,
148+ 'columns[0][data]' : 'n_sort' ,
149+ 'columns[0][name]' : 'cmm_posts_detail.n_sort' ,
150+ 'columns[0][searchable]' : 'true' ,
151+ 'columns[0][orderable]' : 'true' ,
152+ 'columns[0][search][value]' : '' ,
153+ 'columns[0][search][regex]' : 'false' ,
154+
155+ 'columns[1][data]' : 'bookmark_created_at' ,
156+ 'columns[1][name]' : 'bookmark_chapters.created_at' ,
157+ 'columns[1][searchable]' : 'false' ,
158+ 'columns[1][orderable]' : 'true' ,
159+ 'columns[1][search][value]' : '' ,
160+ 'columns[1][search][regex]' : 'false' ,
161+
162+ 'order[0][column]' : '0' ,
163+ 'order[0][dir]' : 'asc' ,
164+ 'order[0][name]' : 'cmm_posts_detail.n_sort' ,
165+
166+ start : start . toString ( ) ,
167+ length : length . toString ( ) ,
168+ 'search[value]' : '' ,
169+ 'search[regex]' : 'false' ,
170+ post_id : post_id ,
171+ only_bookmark : 'false' ,
172+ _ : Date . now ( ) . toString ( ) ,
173+ } ) ;
174+
175+ const result = await fetchApi ( `${ url } ?${ params . toString ( ) } ` ) ;
176+ if ( result . status === 429 ) throw new NovelFireThrottlingError ( ) ;
177+
127178 const body = await result . text ( ) ;
128179
129180 if ( body . includes ( 'You are being rate limited' ) ) {
@@ -135,7 +186,7 @@ class NovelFire implements Plugin.PluginBase {
135186 }
136187
137188 const json = JSON . parse ( body ) ;
138- const chapters = json . data
189+ const chapters = ( json . data || [ ] )
139190 . map ( ( index : { title ?: string ; slug : string ; n_sort : number } ) => {
140191 const chapterName = load ( index . title || index . slug ) . text ( ) ;
141192 const chapterPath = `${ novelPath } /chapter-${ index . n_sort } ` ;
@@ -223,13 +274,16 @@ class NovelFire implements Plugin.PluginBase {
223274 }
224275
225276 async parseNovel (
226- novelPathRaw : string ,
277+ novelPath : string ,
227278 ) : Promise < Plugin . SourceNovel & { totalPages : number } > {
228- const novelPath = deSlash ( novelPathRaw ) ;
279+ this . draw = 0 ;
229280 const $ = await this . getCheerio ( this . site + novelPath , false ) ;
230281 const baseUrl = this . site ;
231282
232- let post_id = '0' ;
283+ const post_id = $ ( '#novel-report' ) . attr ( 'report-post_id' ) ;
284+ if ( post_id ) {
285+ storage . set ( `novelfire_postid_${ novelPath } ` , post_id ) ;
286+ }
233287
234288 const novel : Partial < Plugin . SourceNovel & { totalPages : number } > = {
235289 path : novelPath ,
@@ -286,55 +340,68 @@ class NovelFire implements Plugin.PluginBase {
286340
287341 novel . rating = parseFloat ( $ ( '.nub' ) . text ( ) . trim ( ) ) ;
288342
289- post_id = $ ( '#novel-report' ) . attr ( 'report-post_id' ) || '0' ;
290-
291- try {
292- novel . chapters = await this . getAllChapters ( novelPath , post_id ) ;
293- } catch ( error ) {
294- const totalChapters = $ ( '.header-stats .icon-book-open' )
295- . parent ( )
296- . text ( )
297- . trim ( ) ;
298- novel . totalPages = Math . ceil ( parseInt ( totalChapters ) / 50 ) ;
299- if ( this . singlePage ) {
300- novel . chapters = await this . getAllChaptersForce (
301- novelPath ,
302- novel . totalPages ,
303- ) ;
304- if ( novel . totalPages > 1 && novel . chapters . length > 50 ) {
305- novel . totalPages = 1 ;
306- }
307- }
343+ const totalChapters = $ ( '.header-stats i.icon-book-open' )
344+ . parent ( )
345+ . text ( )
346+ . trim ( ) ;
347+ const length = parseInt ( this . pageLength ) || - 1 ;
348+ novel . totalPages =
349+ length === - 1 ? 1 : Math . ceil ( parseInt ( totalChapters ) / length ) || 1 ;
350+ if ( length === 100 && this . singlePage ) {
351+ novel . chapters = await this . getAllChaptersForce (
352+ novel . path as string ,
353+ novel . totalPages ,
354+ ) ;
355+ novel . totalPages = 1 ;
308356 }
309357
310358 return novel as Plugin . SourceNovel & { totalPages : number } ;
311359 }
312360
313361 async parsePage ( novelPath : string , page : string ) : Promise < Plugin . SourcePage > {
314- const url = `${ this . site } ${ novelPath } /chapters?page=${ page } ` ;
315- const result = await fetchApi ( url ) ;
316- const body = await result . text ( ) ;
317-
318- const loadedCheerio = load ( body ) ;
319-
320- const chapters = loadedCheerio ( '.chapter-list li' )
321- . map ( ( index , ele ) => {
322- const chapterName =
323- loadedCheerio ( ele ) . find ( 'a' ) . attr ( 'title' ) || 'No Title Found' ;
324- const chapterPath = loadedCheerio ( ele ) . find ( 'a' ) . attr ( 'href' ) ;
325-
326- if ( ! chapterPath ) return null ;
362+ const post_id = storage . get ( `novelfire_postid_${ novelPath } ` ) ;
363+
364+ if ( post_id && ! isNaN ( Number ( post_id ) ) ) {
365+ try {
366+ const chapters = await this . getAllChapters ( novelPath , post_id , page ) ;
367+ return { chapters } ;
368+ } catch ( e ) {
369+ // Fallback to scraping if AJAX fails
370+ }
371+ }
327372
328- return {
329- name : chapterName ,
330- path : deSlash ( chapterPath . replace ( this . site , '' ) ) ,
331- } ;
332- } )
333- . get ( )
334- . filter ( chapter => chapter !== null ) as Plugin . ChapterItem [ ] ;
373+ // Fallback only works for multiples of 100
374+ const length = parseInt ( this . pageLength ) || - 1 ;
375+ if ( length === 100 ) {
376+ const url = `${ this . site } ${ novelPath } /chapters?page=${ page } ` ;
377+ const result = await fetchApi ( url ) ;
378+ const body = await result . text ( ) ;
379+
380+ const loadedCheerio = load ( body ) ;
381+
382+ const chapters = loadedCheerio ( '.chapter-list li' )
383+ . map ( ( index , ele ) => {
384+ const chapterName =
385+ loadedCheerio ( ele ) . find ( 'a' ) . attr ( 'title' ) || 'No Title Found' ;
386+ const chapterPath = loadedCheerio ( ele ) . find ( 'a' ) . attr ( 'href' ) ;
387+
388+ if ( ! chapterPath ) return null ;
389+
390+ return {
391+ name : chapterName ,
392+ path : new URL ( chapterPath , this . site ) . pathname . substring ( 1 ) ,
393+ } ;
394+ } )
395+ . get ( )
396+ . filter ( chapter => chapter !== null ) as Plugin . ChapterItem [ ] ;
397+
398+ return {
399+ chapters,
400+ } ;
401+ }
335402
336403 return {
337- chapters,
404+ chapters : [ ] ,
338405 } ;
339406 }
340407
@@ -362,6 +429,7 @@ class NovelFire implements Plugin.PluginBase {
362429 ) : Promise < Plugin . NovelItem [ ] > {
363430 if ( page === 1 ) {
364431 this . novelList = [ ] ;
432+ this . draw = 0 ;
365433 }
366434 const params = new URLSearchParams ( ) ;
367435 params . append ( 'keyword' , searchTerm ) ;
@@ -544,15 +612,3 @@ class NovelFireAjaxNotFound extends Error {
544612 this . name = 'NovelFireAjaxError' ;
545613 }
546614}
547-
548- function deSlash ( url : string ) : string {
549- let clean : string ;
550-
551- if ( url . charAt ( 0 ) == '/' ) {
552- clean = url . substring ( 1 ) ;
553- } else {
554- clean = url ;
555- }
556-
557- return clean ;
558- }
0 commit comments