fix: linter issues

This commit is contained in:
K1ngfish3r
2026-04-23 01:04:42 +05:00
parent 5e195b7f53
commit 2396df38bb
111 changed files with 2738 additions and 2160 deletions
+76 -51
View File
@@ -11,7 +11,7 @@ class INovelTranslation implements Plugin.PluginBase {
name = 'iNovelTranslation';
icon = 'src/en/inoveltranslation/icon.png';
site = 'https://inoveltranslation.com';
version = '1.0.1';
version = '1.0.2';
filters: Filters | undefined = undefined;
pluginSettings = {
@@ -22,7 +22,6 @@ class INovelTranslation implements Plugin.PluginBase {
},
};
// Optimized stealth headers to mirror a real browser environment
private readonly HEADERS = {
'Accept':
'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
@@ -33,20 +32,16 @@ class INovelTranslation implements Plugin.PluginBase {
'Sec-Fetch-Site': 'same-origin',
};
async popularNovels(
pageNo: number,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
options: Plugin.PopularNovelsOptions<typeof this.filters>,
): Promise<Plugin.NovelItem[]> {
async popularNovels(pageNo: number): Promise<Plugin.NovelItem[]> {
const url = `${this.site}/api/novels?limit=50&page=${pageNo}`;
const result = await fetchApi(url, { headers: this.HEADERS }).then(r =>
r.json(),
);
const result: ApiResponse<NovelData> = await fetchApi(url, {
headers: this.HEADERS,
}).then(r => r.json());
const novels: Plugin.NovelItem[] = [];
if (result.docs) {
result.docs.forEach((doc: any) => {
result.docs.forEach(doc => {
novels.push({
name: doc.title,
path: `/novels/${doc.id}`,
@@ -61,27 +56,24 @@ class INovelTranslation implements Plugin.PluginBase {
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
const id = novelPath.split('/').pop();
const novelUrl = `${this.site}/api/novels/${id}?depth=1`;
const novelData = await fetchApi(novelUrl, { headers: this.HEADERS }).then(
r => r.json(),
);
const chaptersUrl = `${this.site}/api/chapters?where[novel][equals]=${id}&limit=999&depth=0`;
const chaptersData = await fetchApi(chaptersUrl, {
const novelData: NovelData = await fetchApi(novelUrl, {
headers: this.HEADERS,
}).then(r => r.json());
const chaptersUrl = `${this.site}/api/chapters?where[novel][equals]=${id}&limit=999&depth=0`;
const chaptersData: ApiResponse<ChapterData> = await fetchApi(chaptersUrl, {
headers: this.HEADERS,
}).then(r => r.json());
// Extract status
const status =
novelData.publication === 'completed'
? NovelStatus.Completed
: NovelStatus.Ongoing;
// Extract genres (tags)
const genres = novelData.tags
? novelData.tags.map((tag: any) => tag.name).join(', ')
? novelData.tags.map(tag => tag.name).join(', ')
: '';
// Process summary (Lexical JSON from API 'sypnosis') - use Plain Text for App Synopsis
let summary = '';
if (novelData.sypnosis && novelData.sypnosis.root) {
summary = this.lexicalToText(novelData.sypnosis.root);
@@ -103,7 +95,7 @@ class INovelTranslation implements Plugin.PluginBase {
const hideLocked = storage.get('hideLocked');
if (chaptersData.docs) {
chaptersData.docs.forEach((doc: any) => {
chaptersData.docs.forEach(doc => {
const isLocked = doc.tier !== null;
if (isLocked && hideLocked) {
return;
@@ -121,7 +113,6 @@ class INovelTranslation implements Plugin.PluginBase {
});
}
// Ensure chapters are sorted numerically
novel.chapters = chapters.sort(
(a, b) => (a.chapterNumber || 0) - (b.chapterNumber || 0),
);
@@ -129,7 +120,6 @@ class INovelTranslation implements Plugin.PluginBase {
}
async parseChapter(chapterPath: string): Promise<string> {
// Artificial delay to prevent aggressive rate limiting
await new Promise(res => setTimeout(res, 1500));
const rscHeader = { ...this.HEADERS, rsc: '1' };
@@ -139,8 +129,8 @@ class INovelTranslation implements Plugin.PluginBase {
response = await fetchApi(this.site + chapterPath, {
headers: rscHeader,
});
} catch (e: any) {
throw new Error(`Network error: ${e.message}`);
} catch (e) {
throw new Error(`Network error: ${(e as Error).message}`);
}
if (response.status !== 200) {
@@ -155,7 +145,6 @@ class INovelTranslation implements Plugin.PluginBase {
throw new Error('Server returned empty data.');
}
// 1. Proactive Cloudflare Detection
if (
rscText.includes('cf-browser-verification') ||
rscText.includes('cf-challenge') ||
@@ -192,7 +181,12 @@ class INovelTranslation implements Plugin.PluginBase {
let startIndex = rscText.lastIndexOf('{', sigIndex);
// Check for "content" or "root" before to find the start of the relevant object
const contextKeys = ['"content"', '\\"content\\"', '"root"', '\\"root\\"'];
const contextKeys = [
'"content"',
'\\"content\\"',
'"root"',
'\\"root\\"',
];
for (const key of contextKeys) {
const keyIndex = rscText.lastIndexOf(key, sigIndex);
if (keyIndex !== -1 && keyIndex > startIndex - 50) {
@@ -236,8 +230,8 @@ class INovelTranslation implements Plugin.PluginBase {
if (jsonStr) {
try {
// Attempt to parse directly (if it's unescaped RSC)
let safeJson = jsonStr.replace(/[\x00-\x1F\x7F-\x9F]/g, '');
// eslint-disable-next-line no-control-regex
const safeJson = jsonStr.replace(/[\x00-\x1F\x7F-\x9F]/g, '');
let parsedData;
try {
parsedData = JSON.parse(safeJson);
@@ -246,18 +240,22 @@ class INovelTranslation implements Plugin.PluginBase {
const cleanJson = jsonStr
.replace(/\\"/g, '"')
.replace(/\\\\/g, '\\')
// eslint-disable-next-line no-control-regex
.replace(/[\x00-\x1F\x7F-\x9F]/g, '');
parsedData = JSON.parse(cleanJson);
}
let lexicalRoot = parsedData.root || parsedData.content?.root || parsedData;
const lexicalRoot =
parsedData.root || parsedData.content?.root || parsedData;
if (lexicalRoot && lexicalRoot.children) {
return this.lexicalToHtml(lexicalRoot);
}
} catch (e: any) {
} catch (e: unknown) {
// Fallback to regex text extraction if JSON parsing fails
let fallbackHtml = '';
const textMatches = jsonStr.match(/\\?"text\\?"\s*:\s*\\?"(.*?)\\?"/g);
const textMatches = jsonStr.match(
/\\?"text\\?"\s*:\s*\\?"(.*?)\\?"/g,
);
if (textMatches && textMatches.length > 0) {
textMatches.forEach(m => {
let text = m.match(/: ?"?(.*?)"?$/)?.[1] || '';
@@ -296,11 +294,7 @@ class INovelTranslation implements Plugin.PluginBase {
);
}
/**
* Recursively converts Lexical JSON nodes to HTML strings.
* Suitable for Chapter Content.
*/
private lexicalToHtml(node: any): string {
private lexicalToHtml(node: LexicalNode): string {
let html = '';
if (node.children) {
for (const child of node.children) {
@@ -308,8 +302,8 @@ class INovelTranslation implements Plugin.PluginBase {
html += `<p>${this.lexicalToHtml(child)}</p>`;
} else if (child.type === 'text') {
let text = child.text || '';
if (child.format & 1) text = `<b>${text}</b>`;
if (child.format & 2) text = `<i>${text}</i>`;
if (child.format && child.format & 1) text = `<b>${text}</b>`;
if (child.format && child.format & 2) text = `<i>${text}</i>`;
html += text;
} else if (child.type === 'list') {
const tag = child.listType === 'number' ? 'ol' : 'ul';
@@ -327,11 +321,7 @@ class INovelTranslation implements Plugin.PluginBase {
return html;
}
/**
* Recursively converts Lexical JSON nodes to Plain Text with newlines.
* Suitable for Novel Synopsis in the app.
*/
private lexicalToText(node: any): string {
private lexicalToText(node: LexicalNode): string {
let textOut = '';
if (node.children) {
for (const child of node.children) {
@@ -356,14 +346,14 @@ class INovelTranslation implements Plugin.PluginBase {
const url = `${this.site}/api/novels?where[title][contains]=${encodeURIComponent(
searchTerm,
)}&limit=50&page=${pageNo}`;
const result = await fetchApi(url, { headers: this.HEADERS }).then(r =>
r.json(),
);
const result: ApiResponse<NovelData> = await fetchApi(url, {
headers: this.HEADERS,
}).then(r => r.json());
const novels: Plugin.NovelItem[] = [];
if (result.docs) {
result.docs.forEach((doc: any) => {
result.docs.forEach(doc => {
novels.push({
name: doc.title,
path: `/novels/${doc.id}`,
@@ -374,8 +364,43 @@ class INovelTranslation implements Plugin.PluginBase {
return novels;
}
resolveUrl = (path: string, isNovel?: boolean) => this.site + path;
}
export default new INovelTranslation();
type LexicalNode = {
type: string;
text?: string;
children?: LexicalNode[];
format?: number;
listType?: string;
tag?: string;
};
type NovelData = {
id: string;
title: string;
cover?: {
url: string;
};
author?: {
name: string;
};
publication?: string;
tags?: { name: string }[];
sypnosis?: {
root: LexicalNode;
};
};
type ChapterData = {
id: string;
title?: string;
chapter: number;
tier: string | null;
updatedAt: string;
};
type ApiResponse<T> = {
docs: T[];
};