Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion plugins/multisrc/readnovelfull/sources.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@
"postSearch": true,
"noAjax": true,
"noPages": ["sort/most-popular"],
"pageAsPath": true
"pageAsPath": true,
"multiPageChapters": true,
"versionIncrements": 1
}
},
{
Expand Down Expand Up @@ -90,6 +92,8 @@
"noAjax": true,
"noPages": ["sort/most-popular"],
"pageAsPath": true,
"multiPageChapters": true,
"versionIncrements": 1,
"customJs": "$('.txt, #chr-content, #chapter-content').find('*').addBack().contents().filter((_, el) => el.type === 'text').each((_, el) => { el.data = el.data.replace(/(?:𝐟|ᵮ|𝑓|𝒇|𝒻|𝓯|𝔣|𝕗|𝖿|𝗳|𝙛|𝚏|ꬵ|ꞙ|ẝ|𝖋|ⓕ|f|ƒ|ḟ|ʃ|բ|ᶠ|⒡|ſ|ꊰ|ʄ|∱|ᶂ|𝘧|\\bf)(?:𝚛|ꭇ|ᣴ|ℾ|𝚪|𝛤|𝜞|𝝘|𝞒|Ⲅ|Г|Ꮁ|ᒥ|ꭈ|ⲅ|ꮁ|ⓡ|r|ŕ|ṙ|ř|ȑ|ȓ|ṛ|ṝ|ŗ|г|Ր|ɾ|ᥬ|ṟ|ɍ|ʳ|⒭|ɼ|ѓ|ᴦ|ᶉ|𝐫|𝑟|𝒓|𝓇|𝓻|𝔯|𝕣|𝖗|𝗋|𝗿|𝘳|𝙧|ᵲ|ґ|ᵣ|r)(?:ə|ә|ⅇ|ꬲ|ꞓ|⋴|𝛆|𝛜|𝜀|𝜖|𝜺|𝝐|𝝴|𝞊|𝞮|𝟄|ⲉ|ꮛ|𐐩|Ꞓ|Ⲉ|⍷|𝑒|𝓮|𝕖|𝖊|𝘦|𝗲|𝚎|𝙚|𝒆|𝔢|𝖾|𝐞|Ҿ|ҿ|ⓔ|e|⒠|è|ᧉ|é|ᶒ|ê|ɘ|ἔ|ề|ế|ễ|૯|ǝ|є|ε|ē|ҽ|ɛ|ể|ẽ|ḕ|ḗ|ĕ|ė|ë|ẻ|ě|ȅ|ȇ|ẹ|ệ|ȩ|ɇ|ₑ|ę|ḝ|ḙ|ḛ|℮|е|ԑ|ѐ|ӗ|ᥱ|ё|ἐ|ἑ|ἒ|ἓ|ἕ|ℯ|e)+(?:𝐰|ꝡ|𝑤|𝒘|𝓌|𝔀|𝔴|𝕨|𝖜|𝗐|𝘄|𝘸|𝙬|𝚠|ա|ẁ|ꮃ|ẃ|ⓦ|⍵|ŵ|ẇ|ẅ|ẘ|ẉ|ⱳ|ὼ|ὠ|ὡ|ὢ|ὣ|ω|ὤ|ὥ|ὦ|ὧ|ῲ|ῳ|ῴ|ῶ|ῷ|Ⱳ|ѡ|ԝ|ᴡ|ώ|ᾠ|ᾡ|ᾢ|ᾣ|ᾤ|ᾥ|ᾦ|ɯ|𝝕|𝟉|𝞏|w)(?:ə|ә|ⅇ|ꬲ|ꞓ|⋴|𝛆|𝛜|𝜀|𝜖|𝜺|𝝐|𝝴|𝞊|𝞮|𝟄|ⲉ|ꮛ|𐐩|Ꞓ|Ⲉ|⍷|𝑒|𝓮|𝕖|𝖊|𝘦|𝗲|𝚎|𝙚|𝒆|𝔢|𝖾|𝐞|Ҿ|ҿ|ⓔ|e|⒠|è|ᧉ|é|ᶒ|ê|ɘ|ἔ|ề|ế|ễ|૯|ǝ|є|ε|ē|ҽ|ɛ|ể|ẽ|ḕ|ḗ|ĕ|ė|ë|ẻ|ě|ȅ|ȇ|ẹ|ệ|ȩ|ɇ|ₑ|ę|ḝ|ḙ|ḛ|℮|е|ԑ|ѐ|ӗ|ᥱ|ё|ἐ|ἑ|ἒ|ἓ|ἕ|ℯ|e)(?:ꮟ|Ꮟ|𝐛|𝘣|𝒷|𝔟|𝓫|𝖇|𝖻|𝑏|𝙗|𝕓|𝒃|𝗯|𝚋|♭|ᑳ|ᒈ|b|ᖚ|ᕹ|ᕺ|ⓑ|ḃ|ḅ|ҍ|ъ|ḇ|ƃ|ɓ|ƅ|ᖯ|Ƅ|Ь|ᑲ|þ|Ƃ|⒝|Ъ|ᶀ|ᑿ|ᒀ|ᒂ|ᒁ|ᑾ|ь|ƀ|Ҍ|Ѣ|ѣ|ᔎ |b)(?:ո|ռ|ח|𝒏|𝓷|𝙣|𝑛|𝖓|𝔫|𝗇|𝚗|𝗻|ᥒ|ⓝ|ή|n|ǹ|ᴒ|ń|ñ|ᾗ|η|ṅ|ň|ṇ|ɲ|ņ|ṋ|ṉ|ղ|ຖ|Ռ|ƞ|ŋ|⒩|ภ|ก|ɳ|п|ʼn|л|ԉ|Ƞ|ἠ|ἡ|ῃ|դ|ᾐ|ᾑ|ᾒ|ᾓ|ᾔ|ᾕ|ᾖ|ῄ|ῆ|ῇ|ῂ|ἢ|ἣ|ἤ|ἥ|ἦ|ἧ|ὴ|ή|በ|ቡ|ቢ|ባ|ቤ|ብ|ቦ|ȵ|𝛈|𝜂|𝜼|𝝶|𝞰|𝕟|𝘯|𝐧|𝓃|ᶇ|ᵰ|ᥥ|∩|n)(?:ం|ಂ|ം|ං|૦|௦|۵|ℴ|𝑜|𝒐|𝖔|ꬽ|𝝄|𝛔|𝜎|𝝈|𝞂|ჿ|𝚘|০|୦|ዐ|𝛐|𝗈|𝞼|ဝ|ⲟ|𝙤|၀|𐐬|𝔬|𐓪|𝓸|🇴|⍤|○|ϙ|🅾|𝒪|𝖮|𝟢|𝟶|𝙾|𝘰|𝗼|𝕠|𝜊|𝐨|𝝾|𝞸|ᐤ|ⓞ|ѳ|᧐|ᥲ|ð|o|ఠ|ᦞ|Փ|ò|ө|ӧ|ó|º|ō|ô|ǒ|ȏ|ŏ|ồ|ȭ|ṏ|ὄ|ṑ|ṓ|ȯ|ȫ|๏|ᴏ|ő|ö|ѻ|о|ዐ|ǭ|ȱ|০|୦|٥|౦|೦|൦|๐|໐|ο|օ|ᴑ|०|੦|ỏ|ơ|ờ|ớ|ỡ|ở|ợ|ọ|ộ|ǫ|ø|ǿ|ɵ|ծ|ὀ|ὁ|ό|ὸ|ό|ὂ|ὃ|ὅ|o)(?:∨|⌄|⋁|ⅴ|𝐯|𝑣|𝒗|𝓋|𝔳|𝕧|𝖛|𝗏|ꮩ|ሀ|ⓥ|v|𝜐|𝝊|ṽ|ṿ|౮|ง|ѵ|ע|ᴠ|ν|ט|ᵥ|ѷ|៴|ᘁ|𝙫|𝚟|𝛎|𝜈|𝝂|𝝼|𝞶|𝘷|𝘃|𝓿|v)(?:ə|ә|ⅇ|ꬲ|ꞓ|⋴|𝛆|𝛜|𝜀|𝜖|𝜺|𝝐|𝝴|𝞊|𝞮|𝟄|ⲉ|ꮛ|𐐩|Ꞓ|Ⲉ|⍷|𝑒|𝓮|𝕖|𝖊|𝘦|𝗲|𝚎|𝙚|𝒆|𝔢|𝖾|𝐞|Ҿ|ҿ|ⓔ|e|⒠|è|ᧉ|é|ᶒ|ê|ɘ|ἔ|ề|ế|ễ|૯|ǝ|є|ε|ē|ҽ|ɛ|ể|ẽ|ḕ|ḗ|ĕ|ė|ë|ẻ|ě|ȅ|ȇ|ẹ|ệ|ȩ|ɇ|ₑ|ę|ḝ|ḙ|ḛ|℮|е|ԑ|ѐ|ӗ|ᥱ|ё|ἐ|ἑ|ἒ|ἓ|ἕ|ℯ|e)(?:ⓛ|l|ŀ|ĺ|ľ|ḷ|ḹ|ļ|Ӏ|ℓ|ḽ|ḻ|ł|レ|ɭ|ƚ|ɫ|ⱡ|\\||Ɩ|⒧|ʅ|ǀ|ו|ן|Ι|І|||ᶩ|ӏ|𝓘|𝕀|𝖨|𝗜|𝘐|𝐥|𝑙|𝒍|𝓁|𝔩|𝕝|𝖑|𝗅|𝗹|𝘭|𝚕|𝜤|𝝞|ı|𝚤|ɩ|ι|𝛊|𝜄|𝜾|𝞲|I|l)(?:.?(?:🝌|c|ⅽ|𝐜|𝑐|𝒄|𝒸|𝓬|𝔠|𝕔|𝖈|𝖼|𝗰|𝘤|𝙘|𝚌|ᴄ|ϲ|ⲥ|с|ꮯ|𐐽|ⲥ|𐐽|ꮯ|ĉ|c|ⓒ|ć|č|ċ|ç|ҁ|ƈ|ḉ|ȼ|ↄ|с|ር|ᴄ|ϲ|ҫ|꒝|ς|ɽ|ϛ|𝙲|ᑦ|᧚|𝐜|𝑐|𝒄|𝒸|𝓬|𝔠|𝕔|𝖈|𝖼|𝗰|𝘤|𝙘|𝚌|₵|🇨|ᥴ|ᒼ|ⅽ|c)(?:ం|ಂ|ം|ං|૦|௦|۵|ℴ|𝑜|𝒐|𝖔|ꬽ|𝝄|𝛔|𝜎|𝝈|𝞂|ჿ|𝚘|০|୦|ዐ|𝛐|𝗈|𝞼|ဝ|ⲟ|𝙤|၀|𐐬|𝔬|𐓪|𝓸|🇴|⍤|○|ϙ|🅾|𝒪|𝖮|𝟢|𝟶|𝙾|𝘰|𝗼|𝕠|𝜊|𝐨|𝝾|𝞸|ᐤ|ⓞ|ѳ|᧐|ᥲ|ð|o|ఠ|ᦞ|Փ|ò|ө|ӧ|ó|º|ō|ô|ǒ|ȏ|ŏ|ồ|ȭ|ṏ|ὄ|ṑ|ṓ|ȯ|ȫ|๏|ᴏ|ő|ö|ѻ|о|ዐ|ǭ|ȱ|০|୦|٥|౦|೦|൦|๐|໐|ο|օ|ᴑ|०|੦|ỏ|ơ|ờ|ớ|ỡ|ở|ợ|ọ|ộ|ǫ|ø|ǿ|ɵ|ծ|ὀ|ὁ|ό|ὸ|ό|ὂ|ὃ|ὅ|o)(?:₥|ᵯ|𝖒|𝐦|𝗆|𝔪|𝕞|𝓂|ⓜ|m|ന|ᙢ|൩|ḿ|ṁ|ⅿ|ϻ|ṃ|ጠ|ɱ|៳|ᶆ|𝒎|𝙢|𝓶|𝚖|𝑚|𝗺|᧕|᧗|m))?/g, ''); });"
}
}
Expand Down
170 changes: 159 additions & 11 deletions plugins/multisrc/readnovelfull/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type ReadNovelFullOptions = {
noPages?: string[];
pageAsPath?: boolean;
customJs?: string;
multiPageChapters?: boolean;
};

export type ReadNovelFullMetadata = {
Expand Down Expand Up @@ -163,13 +164,22 @@ export class ReadNovelFullPlugin implements Plugin.PluginBase {
pageAsPath = false,
} = this.options;

const genresValue = (filters?.genres?.value ??
this.filters?.genres?.value) as string[] | string | undefined;
const typeValue = String(
filters?.type?.value ?? this.filters?.type?.value ?? '',
);
const hasGenres = Array.isArray(genresValue)
? genresValue.length > 0
: !!genresValue;

// Skip Pagination for FWN & LR
if (
pageNo !== 1 &&
!showLatestNovels &&
!filters.genres.value.length &&
!hasGenres &&
noPages.length > 0 &&
noPages.includes(filters.type.value)
noPages.includes(typeValue)
) {
return [];
}
Expand All @@ -182,11 +192,14 @@ export class ReadNovelFullPlugin implements Plugin.PluginBase {

if (showLatestNovels) {
params.append(typeParam, latestPage);
} else if (filters.genres.value.length) {
} else if (hasGenres) {
params.append(typeParam, genreParam);
params.append(genreKey, filters.genres.value);
params.append(
genreKey,
Array.isArray(genresValue) ? genresValue.join(',') : genresValue!,
);
} else {
params.append(typeParam, filters.type.value);
params.append(typeParam, typeValue);
}

// Add language parameter if specified
Expand All @@ -200,13 +213,19 @@ export class ReadNovelFullPlugin implements Plugin.PluginBase {
// URL structure with path segments
const basePage = showLatestNovels
? latestPage
: filters.genres.value.length
? filters.genres.value
: filters.type.value;
: hasGenres
? Array.isArray(genresValue)
? genresValue.join(',')
: genresValue!
: typeValue;

if (pageAsPath) {
if (pageNo > 1) {
url = `${this.site}${basePage}/${pageNo.toString()}`;
if (this.options.multiPageChapters) {
url = `${this.site}${basePage}/${pageParam}/${pageNo.toString()}`;
} else {
url = `${this.site}${basePage}/${pageNo.toString()}`;
}
} else {
url = `${this.site}${basePage}`;
}
Expand Down Expand Up @@ -239,12 +258,15 @@ export class ReadNovelFullPlugin implements Plugin.PluginBase {
const authorParts: string[] = [];
const genreArray: string[] = [];
const infoParts: string[] = [];
const chapters: Plugin.ChapterItem[] = [];
let novelId: string | null = null;
let tempChapter: Partial<Plugin.ChapterItem> = {};
let i = 0;
let depth: number;

let isMultiPageSelect = false;
let multiPageOptionCount = 0;
const chapters: Plugin.ChapterItem[] = [];

const stateStack: ParsingState[] = [ParsingState.Idle];
const currentState = () => stateStack[stateStack.length - 1];
const pushState = (state: ParsingState) => stateStack.push(state);
Expand Down Expand Up @@ -335,6 +357,19 @@ export class ReadNovelFullPlugin implements Plugin.PluginBase {
novelPath.replace('.html', `/chapter-${i}.html`);
}
break;
case 'select':
if (
this.options.multiPageChapters &&
attribs.id === 'indexselect'
) {
isMultiPageSelect = true;
}
break;
case 'option':
if (isMultiPageSelect && attribs.value) {
multiPageOptionCount++;
}
break;
}
},

Expand Down Expand Up @@ -411,6 +446,9 @@ export class ReadNovelFullPlugin implements Plugin.PluginBase {
break;
}
break;
case 'select':
isMultiPageSelect = false;
break;
default:
return;
}
Expand Down Expand Up @@ -475,7 +513,117 @@ export class ReadNovelFullPlugin implements Plugin.PluginBase {
parser.write(body);
parser.end();

if (this.options.noAjax && chapters.length > 0) {
const multiPageMaxPage = Math.max(1, multiPageOptionCount);

if (this.options.multiPageChapters && multiPageMaxPage > 1) {
// Re-fetch page 1 with max pageSize (200) since default page size is usually 40,
// minimizing total requests required to fetch all chapters.
chapters.length = 0;
const cleanNovelPath = novelPath
.replace(/\.html$/, '')
.replace(/\/$/, '');
const newPageSize = 200;

const fetchAndParse = async (p: number) => {
const ajaxUrl = `${this.site}${cleanNovelPath}?ajax=chapters&page=${p}&pageSize=${newPageSize}`;
try {
const res = await fetchApi(ajaxUrl, {
headers: {
'Referer': this.site + novelPath,
'X-Requested-With': 'XMLHttpRequest',
'Accept': '*/*',
},
});
if (res.ok) {
const data = await res.json();
const html = data.html || '';
const pageChapters: Plugin.ChapterItem[] = [];
let isParsingChapter = false;
let tempAjaxChapter: Partial<Plugin.ChapterItem> = {};

const pageParser = new Parser({
onopentag: (name, attribs) => {
if (name === 'a' && attribs.href) {
isParsingChapter = true;
tempAjaxChapter.name = attribs.title || '';
tempAjaxChapter.path = attribs.href.replace(/^\//, '');
}
},
ontext: data => {
const text = data.trim();
if (isParsingChapter && text) {
tempAjaxChapter.name = tempAjaxChapter.name
? tempAjaxChapter.name + text
: text;
}
},
onclosetag: name => {
if (name === 'a' && isParsingChapter) {
if (tempAjaxChapter.path) {
pageChapters.push({
name: tempAjaxChapter.name?.trim() || `Chapter`,
path: tempAjaxChapter.path,
releaseTime: null,
});
}
tempAjaxChapter = {};
isParsingChapter = false;
}
},
});
pageParser.write(html);
pageParser.end();
return { pageChapters, totalPage: data.totalPage };
} else {
throw new Error(`HTTP Error ${res.status}`);
}
} catch (e) {
console.error(
`Failed to fetch chapters page ${p} for ${novelPath}`,
e,
);
throw e;
}
};

const firstPageData = await fetchAndParse(1);
const allChapters = [...firstPageData.pageChapters];
const newMaxPage = firstPageData.totalPage || 1;

if (newMaxPage > 1) {
for (let i = 2; i <= newMaxPage; i++) {
let chunkSuccess = false;
let retries = 0;

while (!chunkSuccess && retries < 3) {
try {
const result = await fetchAndParse(i);
allChapters.push(...result.pageChapters);
chunkSuccess = true;
} catch (err) {
retries++;
await this.sleep(1000);
}
}

if (i < newMaxPage) {
const randomDelay = Math.floor(Math.random() * 250) + 250; // Random delay between 250ms and 499ms
await this.sleep(randomDelay);
}
}
}

// Assign sequential chapter numbers
for (let i = 0; i < allChapters.length; i++) {
allChapters[i].chapterNumber = i + 1;
if (allChapters[i].name === 'Chapter') {
allChapters[i].name = `Chapter ${i + 1}`;
}
chapters.push(allChapters[i]);
}

novel.chapters = chapters;
} else if (this.options.noAjax && chapters.length > 0) {
novel.chapters = chapters;
} else if (novelId !== null) {
const chapterListing =
Expand Down
Loading