@@ -4,177 +4,17 @@ import { Plugin } from '@/types/plugin';
44import { NovelStatus } from '@libs/novelStatus' ;
55import { FilterTypes , Filters } from '@libs/filterInputs' ;
66import { storage } from '@libs/storage' ;
7-
8- const BASE64_ALPHABET =
9- 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' ;
10-
11- const decodeBase64ToBytes = ( encoded : string ) : number [ ] => {
12- const normalized = encoded
13- . replace ( / - / g, '+' )
14- . replace ( / _ / g, '/' )
15- . replace ( / \s + / g, '' ) ;
16- const padded =
17- normalized . length % 4 === 0
18- ? normalized
19- : normalized + '=' . repeat ( 4 - ( normalized . length % 4 ) ) ;
20- const bytes : number [ ] = [ ] ;
21-
22- for ( let i = 0 ; i < padded . length ; i += 4 ) {
23- const c1 = BASE64_ALPHABET . indexOf ( padded . charAt ( i ) ) ;
24- const c2 = BASE64_ALPHABET . indexOf ( padded . charAt ( i + 1 ) ) ;
25- const c3Char = padded . charAt ( i + 2 ) ;
26- const c4Char = padded . charAt ( i + 3 ) ;
27- const c3 = c3Char === '=' ? 0 : BASE64_ALPHABET . indexOf ( c3Char ) ;
28- const c4 = c4Char === '=' ? 0 : BASE64_ALPHABET . indexOf ( c4Char ) ;
29-
30- if (
31- c1 < 0 ||
32- c2 < 0 ||
33- ( c3Char !== '=' && c3 < 0 ) ||
34- ( c4Char !== '=' && c4 < 0 )
35- ) {
36- continue ;
37- }
38-
39- const bitStream = ( c1 << 18 ) | ( c2 << 12 ) | ( c3 << 6 ) | c4 ;
40- bytes . push ( ( bitStream >> 16 ) & 255 ) ;
41- if ( c3Char !== '=' ) {
42- bytes . push ( ( bitStream >> 8 ) & 255 ) ;
43- }
44- if ( c4Char !== '=' ) {
45- bytes . push ( bitStream & 255 ) ;
46- }
47- }
48-
49- return bytes ;
50- } ;
51-
52- const utf8BytesToString = ( bytes : number [ ] ) : string => {
53- let out = '' ;
54- let i = 0 ;
55-
56- while ( i < bytes . length ) {
57- const c = bytes [ i ++ ] ;
58-
59- if ( c < 128 ) {
60- out += String . fromCharCode ( c ) ;
61- } else if ( c < 224 ) {
62- out += String . fromCharCode ( ( ( c & 31 ) << 6 ) | ( bytes [ i ++ ] & 63 ) ) ;
63- } else if ( c < 240 ) {
64- out += String . fromCharCode (
65- ( ( c & 15 ) << 12 ) | ( ( bytes [ i ++ ] & 63 ) << 6 ) | ( bytes [ i ++ ] & 63 ) ,
66- ) ;
67- } else {
68- let codePoint =
69- ( ( c & 7 ) << 18 ) |
70- ( ( bytes [ i ++ ] & 63 ) << 12 ) |
71- ( ( bytes [ i ++ ] & 63 ) << 6 ) |
72- ( bytes [ i ++ ] & 63 ) ;
73- codePoint -= 65536 ;
74- out += String . fromCharCode (
75- 55296 + ( codePoint >> 10 ) ,
76- 56320 + ( codePoint & 1023 ) ,
77- ) ;
78- }
79- }
80-
81- return out ;
82- } ;
83-
84- const decodeBase64Utf8 = ( encoded : string ) =>
85- utf8BytesToString ( decodeBase64ToBytes ( encoded ) ) ;
86-
87- const decodeXorChunk = ( encoded : string , key : string ) : string => {
88- const input = decodeBase64ToBytes ( encoded ) ;
89- if ( ! key ) {
90- return utf8BytesToString ( input ) ;
91- }
92-
93- const output : number [ ] = [ ] ;
94- for ( let i = 0 ; i < input . length ; i ++ ) {
95- output . push ( input [ i ] ^ key . charCodeAt ( i % key . length ) ) ;
96- }
97- return utf8BytesToString ( output ) ;
98- } ;
99-
100- const parseProtectedChunks = ( raw : string ) : string [ ] => {
101- if ( ! raw ) {
102- return [ ] ;
103- }
104-
105- try {
106- const parsed = JSON . parse ( raw ) as unknown ;
107- if ( Array . isArray ( parsed ) ) {
108- return parsed . filter ( ( item ) : item is string => typeof item === 'string' ) ;
109- }
110- } catch {
111- // fallback to single-chunk payload
112- }
113-
114- return [ raw ] ;
115- } ;
116-
117- const decodeProtectedContent = (
118- mode : string ,
119- key : string ,
120- chunks : string [ ] ,
121- ) : string => {
122- if ( ! chunks . length ) {
123- return '' ;
124- }
125-
126- const sortedChunks = [ ...chunks ] . sort ( ( a , b ) => {
127- const ai = Number . parseInt ( a . substring ( 0 , 4 ) , 10 ) ;
128- const bi = Number . parseInt ( b . substring ( 0 , 4 ) , 10 ) ;
129- if ( Number . isNaN ( ai ) || Number . isNaN ( bi ) ) {
130- return 0 ;
131- }
132- return ai - bi ;
133- } ) ;
134-
135- let content = '' ;
136-
137- for ( const chunk of sortedChunks ) {
138- const payload = / ^ \d { 4 } / . test ( chunk ) ? chunk . substring ( 4 ) : chunk ;
139-
140- if ( mode === 'xor_shuffle' ) {
141- content += decodeXorChunk ( payload , key ) ;
142- } else if ( mode === 'base64_reverse' ) {
143- content += decodeBase64Utf8 ( payload . split ( '' ) . reverse ( ) . join ( '' ) ) ;
144- } else {
145- content += decodeBase64Utf8 ( payload ) ;
146- }
147- }
148-
149- return content . replace (
150- / \[ n o t e ( \d + ) ] / gi,
151- '<span id="anchor-note$1" class="note-icon none-print inline note-tooltip" data-tooltip-content="#note$1 .note-content" data-note-id="note$1"><i class="fas fa-sticky-note"></i></span><a id="anchor-note$1" class="inline-print none" href="#note$1">[note]</a>' ,
152- ) ;
153- } ;
154-
155- const parseDmyToIso = ( value : string ) : string | undefined => {
156- const matched = value . trim ( ) . match ( / ^ ( \d { 1 , 2 } ) \/ ( \d { 1 , 2 } ) \/ ( \d { 4 } ) $ / ) ;
157- if ( ! matched ) {
158- return undefined ;
159- }
160-
161- const day = Number ( matched [ 1 ] ) ;
162- const month = Number ( matched [ 2 ] ) - 1 ;
163- const year = Number ( matched [ 3 ] ) ;
164- const date = new Date ( year , month , day ) ;
165-
166- if ( Number . isNaN ( date . getTime ( ) ) ) {
167- return undefined ;
168- }
169-
170- return date . toISOString ( ) ;
171- } ;
7+ import {
8+ parseDmyToIso ,
9+ parseProtectedChunks ,
10+ decodeProtectedContent ,
11+ } from './utils' ;
17212
17313class HakoPlugin implements Plugin . PluginBase {
17414 id = 'ln.hako.vn' ;
17515 name = 'Hako Novel' ;
17616 icon = 'src/vi/hakolightnovel/icon.png' ;
177- version = '1.1.20 ' ;
17+ version = '1.1.21 ' ;
17818
17919 pluginSettings = {
18020 usingDocln : {
0 commit comments