= {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
};
const escapeHtml = (text: string): string =>
escapeRegex.test(text)
? ((escapeRegex.lastIndex = 0),
text.replace(escapeRegex, char => escapeMap[char as EscapeChar]))
: text;
const parser = new Parser({
onopentag(name, attribs) {
if (name === 'sentence' && attribs['class']?.includes('translated')) {
state = ParsingState.Chapter;
chapterTextParts.push('');
}
},
onopentagname(name) {
if (name === 'nav') {
state = ParsingState.Idle;
}
},
ontext(data) {
if (state === ParsingState.Chapter) {
chapterTextParts.push(escapeHtml(data));
}
},
onclosetag(name) {
if (name === 'sentence' && state === ParsingState.Chapter) {
chapterTextParts.push('
');
state = ParsingState.Idle;
}
},
});
parser.write(html);
parser.end();
const chapterText = chapterTextParts.join('');
return chapterText.replace(/„/g, '“');
}
async searchNovels(searchTerm: string): Promise {
const html = await fetchApi(this.site)
.then(b => b.text())
.then(r => r.replace(/>\s+<'));
let state = ParsingState.Idle;
const listPart = new Set();
const parser = new Parser({
onopentag(name, attribs) {
if (
state === ParsingState.InFooter &&
name === 'script' &&
attribs['type']?.includes('application/javascript')
) {
state = ParsingState.InScript;
}
},
ontext(data) {
if (state === ParsingState.InScript) {
const match = data.match(/prefetch: '\/(.*?\.json)/);
if (match) {
listPart.add(match[1]);
}
}
},
onclosetag(name) {
switch (name) {
case 'footer':
state = ParsingState.InFooter;
break;
case 'script':
if (state === ParsingState.InScript) {
state = ParsingState.Idle;
}
break;
}
},
});
parser.write(html);
parser.end();
const search = await fetchApi(
`${this.site}${Array.from(listPart).join('')}`,
);
const data = await search.json();
const nov = data.filter((res: { name: string }) =>
res.name.toLowerCase().includes(searchTerm.toLowerCase()),
);
const novels: Plugin.NovelItem[] = [];
nov.map((res: { name: string; slug: string; image: string }) => {
const novelName = res.name;
const novelUrl = `novel/${res.slug}`;
const novelCover = res.image;
const novel = {
path: novelUrl,
name: novelName,
cover: novelCover,
};
novels.push(novel);
});
return novels;
}
filters = {
order: {
value: 'favourites',
label: 'Order by',
options: [
{ label: 'Favourites', value: 'favourites' },
{ label: 'Name', value: 'name' },
{ label: 'Addition Date', value: 'date' },
],
type: FilterTypes.Picker,
},
sort: {
value: 'desc',
label: 'Sort by',
options: [
{ label: 'Descending', value: 'desc' },
{ label: 'Ascending', value: 'asc' },
],
type: FilterTypes.Picker,
},
storyStatus: {
value: 'all',
label: 'Status',
options: [
{ label: 'All', value: 'all' },
{ label: 'Ongoing', value: 'ongoing' },
{ label: 'Finished', value: 'finished' },
],
type: FilterTypes.Picker,
},
} satisfies Filters;
}
export default new LnMTLPlugin();
type ChapterEntry = {
number: number;
title: string;
slug: string;
created_at: string;
};
type VolumeEntry = {
id: string;
title: string;
};
enum ParsingState {
Idle,
InFooter,
InScript,
InDescription,
InSource,
InPanelKey,
InPanelValue,
InGenres,
Novel,
Chapter,
}