@@ -18,7 +18,7 @@ class Genesis implements Plugin.PluginBase {
18
18
icon = 'src/en/genesis/icon.png' ;
19
19
customCSS = 'src/en/genesis/customCSS.css' ;
20
20
site = 'https://genesistudio.com' ;
21
- version = '1.0.4 ' ;
21
+ version = '1.0.5 ' ;
22
22
23
23
imageRequestInit ?: Plugin . ImageRequestInit | undefined = {
24
24
headers : {
@@ -54,12 +54,15 @@ class Genesis implements Plugin.PluginBase {
54
54
55
55
async parseNovel ( novelPath : string ) : Promise < Plugin . SourceNovel > {
56
56
const url = `${ this . site } ${ novelPath } /__data.json?x-sveltekit-invalidated=001` ;
57
+
58
+ // Fetch the novel's data in JSON format
57
59
const json = await fetchApi ( url ) . then ( r => r . json ( ) ) ;
58
60
const nodes = json . nodes ;
59
- const data = nodes
60
- . filter ( ( node : { type : string } ) => node . type === 'data' )
61
- . map ( ( node : { data : any } ) => node . data ) [ 0 ] ;
62
61
62
+ // Extract the main novel data from the nodes
63
+ const data = this . extractNovelData ( nodes ) ;
64
+
65
+ // Initialize the novel object with default values
63
66
const novel : Plugin . SourceNovel = {
64
67
path : novelPath ,
65
68
name : '' ,
@@ -70,53 +73,258 @@ class Genesis implements Plugin.PluginBase {
70
73
chapters : [ ] ,
71
74
} ;
72
75
76
+ // Parse and assign novel metadata (title, cover, summary, author, etc.)
77
+ this . populateNovelMetadata ( novel , data ) ;
78
+
79
+ // Parse the chapters if available and assign them to the novel object
80
+ novel . chapters = this . extractChapters ( data ) ;
81
+
82
+ return novel ;
83
+ }
84
+
85
+ // Helper function to extract novel data from nodes
86
+ extractNovelData ( nodes : any [ ] ) : any {
87
+ return nodes
88
+ . filter ( ( node : { type : string } ) => node . type === 'data' )
89
+ . map ( ( node : { data : any } ) => node . data ) [ 0 ] ;
90
+ }
91
+
92
+ // Helper function to populate novel metadata
93
+ populateNovelMetadata ( novel : Plugin . SourceNovel , data : any ) : void {
73
94
for ( const key in data ) {
74
95
const value = data [ key ] ;
75
- if ( typeof value === 'object' && value !== null ) {
76
- if ( 'novel_title' in value ) {
77
- novel . name = data [ value . novel_title ] ;
78
- novel . cover = data [ value . cover ] ;
79
- novel . summary = data [ value . synopsis ] ;
80
- novel . author = data [ value . author ] ;
81
- novel . genres = ( data [ value . genres ] as number [ ] )
96
+
97
+ if (
98
+ typeof value === 'object' &&
99
+ value !== null &&
100
+ 'novel_title' in value
101
+ ) {
102
+ novel . name = data [ value . novel_title ] || 'Unknown Title' ;
103
+ novel . cover = data [ value . cover ] || '' ;
104
+ novel . summary = data [ value . synopsis ] || '' ;
105
+ novel . author = data [ value . author ] || 'Unknown Author' ;
106
+ novel . genres =
107
+ ( data [ value . genres ] as number [ ] )
82
108
. map ( ( genreId : number ) => data [ genreId ] )
83
- . join ( ', ' ) ;
84
- novel . status = value . release_days ? 'Ongoing' : 'Completed' ;
85
- } else if ( 'chapters_list' in value ) {
86
- const chaptersFunction = data [ value . chapters_list ] ;
87
- const chapterMatches = chaptersFunction . match (
88
- / ' i d ' : ( (? ! _ ) \w + ) , ' c h a p t e r _ t i t l e ' : (?: ' ( [ ^ ' \\ ] * (?: \\ .[ ^ ' \\ ] * ) * ) ' | ( \w + \( [ ^ \) ] + \) ) ) , ' c h a p t e r _ n u m b e r ' : ( \w + ) , ' r e q u i r e d _ t i e r ' : ( \w + ) , ' d a t e _ c r e a t e d ' : ( [ ^ , ] * ) , / g,
89
- ) ;
90
-
91
- if ( chapterMatches ) {
92
- novel . chapters = chapterMatches
93
- . map ( ( match : string ) => {
94
- const [ , id , title , , number , requiredTier , dateCreated ] =
95
- match . match (
96
- / ' i d ' : ( \w + ) , ' c h a p t e r _ t i t l e ' : (?: ' ( [ ^ ' \\ ] * (?: \\ .[ ^ ' \\ ] * ) * ) ' | ( \w + \( [ ^ \) ] + \) ) ) , ' c h a p t e r _ n u m b e r ' : ( \w + ) , ' r e q u i r e d _ t i e r ' : ( \w + ) , ' d a t e _ c r e a t e d ' : ( [ ^ , ] * ) , / ,
97
- ) ! ;
98
-
99
- if ( parseInt ( requiredTier , 16 ) === 0 ) {
100
- return {
101
- name : `Chapter ${ parseInt ( number , 16 ) } : ${ title || 'Unknown Title' } ` ,
102
- path : `/viewer/${ parseInt ( id , 16 ) } ` ,
103
- releaseTime : dateCreated . replace ( / ^ ' | ' $ / g, '' ) ,
104
- chapterNumber : parseInt ( number , 16 ) ,
105
- } ;
106
- }
107
- return null ;
108
- } )
109
- . filter (
110
- (
111
- chapter : Plugin . ChapterItem | null ,
112
- ) : chapter is Plugin . ChapterItem => chapter !== null ,
113
- ) ;
114
- }
115
- }
109
+ . join ( ', ' ) || 'Unknown Genre' ;
110
+ novel . status = value . release_days ? 'Ongoing' : 'Completed' ;
111
+ break ; // Break the loop once metadata is found
112
+ }
113
+ }
114
+ }
115
+
116
+ // Helper function to extract and format chapters
117
+ extractChapters ( data : any ) : Plugin . ChapterItem [ ] {
118
+ for ( const key in data ) {
119
+ const value = data [ key ] ;
120
+
121
+ // Change string here if the chapters are stored under a different key
122
+ const chapterKey = 'chapters' ;
123
+ if ( typeof value === 'object' && value !== null && chapterKey in value ) {
124
+ const chapterData = this . decodeData ( data [ value [ chapterKey ] ] ) ;
125
+
126
+ // Object.values will give us an array of arrays (any[][])
127
+ const chapterArrays : any [ ] [ ] = Object . values ( chapterData ) ;
128
+
129
+ // Flatten and format the chapters
130
+ return chapterArrays . flatMap ( ( chapters : any [ ] ) => {
131
+ return chapters
132
+ . map ( ( chapter : any ) => this . formatChapter ( chapter ) )
133
+ . filter (
134
+ ( chapter ) : chapter is Plugin . ChapterItem => chapter !== null ,
135
+ ) ;
136
+ } ) ;
116
137
}
117
138
}
118
139
119
- return novel ;
140
+ return [ ] ;
141
+ }
142
+
143
+ // Helper function to format an individual chapter
144
+ formatChapter ( chapter : any ) : Plugin . ChapterItem | null {
145
+ const { id, chapter_title, chapter_number, required_tier, date_created } =
146
+ chapter ;
147
+
148
+ // Ensure required fields are present and valid
149
+ if (
150
+ id &&
151
+ chapter_title &&
152
+ chapter_number &&
153
+ required_tier !== null &&
154
+ date_created
155
+ ) {
156
+ const number = parseInt ( chapter_number , 10 ) || 0 ;
157
+ const requiredTier = parseInt ( required_tier , 10 ) || 0 ;
158
+
159
+ // Only process chapters with a 'requiredTier' of 0
160
+ if ( requiredTier === 0 ) {
161
+ return {
162
+ name : `Chapter ${ number } : ${ chapter_title } ` ,
163
+ path : `/viewer/${ id } ` ,
164
+ releaseTime : date_created ,
165
+ chapterNumber : number ,
166
+ } ;
167
+ }
168
+ }
169
+
170
+ return null ;
171
+ }
172
+
173
+ decodeData ( code : any ) {
174
+ const offset = this . getOffsetIndex ( code ) ;
175
+ const params = this . getDecodeParams ( code ) ;
176
+ const constant = this . getConstant ( code ) ;
177
+ const data = this . getStringsArrayRaw ( code ) ;
178
+
179
+ const getDataAt = ( x : number ) => data [ x - offset ] ;
180
+
181
+ //reshuffle data array
182
+ // eslint-disable-next-line no-constant-condition
183
+ while ( true ) {
184
+ try {
185
+ const some_number = this . applyDecodeParams ( params , getDataAt ) ;
186
+ if ( some_number === constant ) break ;
187
+ else data . push ( data . shift ( ) ) ;
188
+ } catch ( err ) {
189
+ data . push ( data . shift ( ) ) ;
190
+ }
191
+ }
192
+
193
+ return this . getChapterData ( code , getDataAt ) ;
194
+ }
195
+
196
+ getOffsetIndex ( code : string ) {
197
+ // @ts -ignore
198
+ const string = / { ( \w + ) = \1- 0 x (?< offset > [ 0 - 9 a - f ] + ) ; / . exec ( code ) . groups . offset ;
199
+ return parseInt ( string , 16 ) ;
200
+ }
201
+
202
+ /**
203
+ * @returns {string[] }
204
+ */
205
+ getStringsArrayRaw ( code : string ) {
206
+ // @ts -ignore
207
+ let json = / f u n c t i o n \w + \( \) { var \w + = (?< array > \[ ' .+ ' ] ) ; / . exec ( code ) . groups
208
+ . array ;
209
+
210
+ //replace string single quotes with double quotes and add escaped chars
211
+ json = json . replace ( / ' ( .+ ?) ' ( [ , \] ] ) / g, ( match , p1 , p2 ) => {
212
+ return `"${ p1 . replace ( / \\ x ( [ 0 - 9 a - z ] { 2 } ) / g, ( match : any , p1 : string ) => {
213
+ //hexadecimal unicode escape chars
214
+ return String . fromCharCode ( parseInt ( p1 , 16 ) ) ;
215
+ } ) } "${ p2 } `;
216
+ } ) ;
217
+
218
+ return JSON . parse ( json ) ;
219
+ }
220
+
221
+ /**
222
+ * @returns {{offset: number, divider: number, negated: boolean}[][] }
223
+ */
224
+ getDecodeParams ( code : string ) {
225
+ // @ts -ignore
226
+ const jsDecodeInt = / w h i l e \( ! ! \[ ] \) { try{ v a r \w + = (?< code > .+ ?) ; / . exec ( code )
227
+ . groups . code ;
228
+ const decodeSections = jsDecodeInt . split ( '+' ) ;
229
+ const params = [ ] ;
230
+ for ( const section of decodeSections ) {
231
+ params . push ( this . decodeParamSection ( section ) ) ;
232
+ }
233
+ return params ;
234
+ }
235
+
236
+ /**
237
+ * @param {string } section
238
+ * @returns {{offset: number, divider: number, negated: boolean}[] }
239
+ */
240
+ decodeParamSection ( section : string ) {
241
+ const sections = section . split ( '*' ) ;
242
+ const params = [ ] ;
243
+ for ( const section of sections ) {
244
+ // @ts -ignore
245
+ const offsetStr = / p a r s e I n t \( \w + \( 0 x (?< offset > [ 0 - 9 a - f ] + ) \) \) / . exec (
246
+ section ,
247
+ ) . groups . offset ;
248
+ const offset = parseInt ( offsetStr , 16 ) ;
249
+ // @ts -ignore
250
+ const dividerStr = / \/ 0 x (?< divider > [ 0 - 9 a - f ] + ) / . exec ( section ) . groups
251
+ . divider ;
252
+ const divider = parseInt ( dividerStr , 16 ) ;
253
+ const negated = section . includes ( '-' ) ;
254
+ params . push ( { offset, divider, negated } ) ;
255
+ }
256
+ return params ;
257
+ }
258
+
259
+ getConstant ( code : string ) {
260
+ // @ts -ignore
261
+ const constantStr = / } } } \( \w + , 0 x (?< constant > [ 0 - 9 a - f ] + ) \) , / . exec ( code ) . groups
262
+ . constant ;
263
+ return parseInt ( constantStr , 16 ) ;
264
+ }
265
+
266
+ getChapterData (
267
+ code : string ,
268
+ getDataAt : { ( x : number ) : any ; ( arg0 : number ) : any } ,
269
+ ) {
270
+ let chapterDataStr =
271
+ // @ts -ignore
272
+ / \) , \( f u n c t i o n \( \) { var \w + = \w + ; r e t u r n (?< data > { .+ ?} ) ; / . exec ( code ) . groups
273
+ . data ;
274
+
275
+ //replace hex with decimal
276
+ chapterDataStr = chapterDataStr . replace ( / : 0 x ( [ 0 - 9 a - f ] + ) / g, ( match , p1 ) => {
277
+ const hex = parseInt ( p1 , 16 ) ;
278
+ return `: ${ hex } ` ;
279
+ } ) ;
280
+
281
+ //replace ![] with false and !![] with true
282
+ chapterDataStr = chapterDataStr
283
+ . replace ( / : ! ! \[ ] / g, ':true' )
284
+ . replace ( / : ! \[ ] / g, ':false' ) ;
285
+
286
+ //replace string single quotes with double quotes and add escaped chars
287
+ chapterDataStr = chapterDataStr . replace (
288
+ / ' ( .+ ?) ' ( [ , \] } : ] ) / g,
289
+ ( match , p1 , p2 ) => {
290
+ return `"${ p1 . replace ( / \\ x ( [ 0 - 9 a - z ] { 2 } ) / g, ( match : any , p1 : string ) => {
291
+ //hexadecimal unicode escape chars
292
+ return String . fromCharCode ( parseInt ( p1 , 16 ) ) ;
293
+ } ) } "${ p2 } `;
294
+ } ,
295
+ ) ;
296
+
297
+ //parse the data getting methods
298
+ chapterDataStr = chapterDataStr . replace (
299
+ // @ts -ignore
300
+ / : \w + \( 0 x (?< offset > [ 0 - 9 a - f ] + ) \) / g,
301
+ ( match , p1 ) => {
302
+ const offset = parseInt ( p1 , 16 ) ;
303
+ return `:${ JSON . stringify ( getDataAt ( offset ) ) } ` ;
304
+ } ,
305
+ ) ;
306
+
307
+ return JSON . parse ( chapterDataStr ) ;
308
+ }
309
+
310
+ /**
311
+ * @param {{offset: number, divider: number, negated: boolean}[][] } params
312
+ * @param {function(number): string } getDataAt
313
+ */
314
+ applyDecodeParams (
315
+ params : { offset : number ; divider : number ; negated : boolean } [ ] [ ] ,
316
+ getDataAt : { ( x : number ) : any ; ( arg0 : any ) : string } ,
317
+ ) {
318
+ let res = 0 ;
319
+ for ( const paramAdd of params ) {
320
+ let resInner = 1 ;
321
+ for ( const paramMul of paramAdd ) {
322
+ resInner *= parseInt ( getDataAt ( paramMul . offset ) ) / paramMul . divider ;
323
+ if ( paramMul . negated ) resInner *= - 1 ;
324
+ }
325
+ res += resInner ;
326
+ }
327
+ return res ;
120
328
}
121
329
122
330
async parseChapter ( chapterPath : string ) : Promise < string > {
@@ -126,7 +334,7 @@ class Genesis implements Plugin.PluginBase {
126
334
const data = nodes
127
335
. filter ( ( node : { type : string } ) => node . type === 'data' )
128
336
. map ( ( node : { data : any } ) => node . data ) [ 0 ] ;
129
- const content = data [ 19 ] ;
337
+ const content = data [ data [ 0 ] . gs ] ?? data [ 19 ] ;
130
338
const footnotes = data [ data [ 0 ] . footnotes ] ;
131
339
return content + ( footnotes ?? '' ) ;
132
340
}
0 commit comments