Skip to content

Commit

Permalink
Merge branch 'LNReader:master' into progress-tracking
Browse files Browse the repository at this point in the history
  • Loading branch information
Batorian authored Dec 1, 2024
2 parents e230595 + 256bb18 commit bdd5066
Show file tree
Hide file tree
Showing 11 changed files with 690 additions and 53 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/prettier.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
node-version: '20'

- name: Install Dependencies
run: |
rm package.json
rm package-lock.json
npm init -y
npm pkg set type="module"
npm i prettier
npm i prettier@3.2.5
- name: Prettier
run: npx prettier --check "./src/**/*.{ts,tsx,js,css}"
5 changes: 5 additions & 0 deletions docs/komga-plugin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
1. Install Komga plugin;
2. In the installed plugins page press the cog icon to open the plugin settings;
3. Fill in the required information (email, password and your komga server url);
4. Press save and restart the app;
5. The komga plugin will now work like the other plugins.
Binary file added public/static/src/jp/kakuyomu/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/static/src/multi/komga/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions scripts/languages.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ export default {
Turkish: 'Türkçe',
Ukrainian: 'Українська',
Vietnamese: 'Tiếng Việt',
Multi: 'Multi',
};
9 changes: 3 additions & 6 deletions src/plugins/english/freewebnovel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,10 @@ class FreeWebNovel implements Plugin.PluginBase {
const chapterText = loadedCheerio('div.txt').html() || '';
return chapterText
.replace(
/(?:(?<=<p>\s*)(?:This (?:chapter is updated by|content is taken from)|Follow current novels on) )?[ƒfF][Rrɾг][Eēeё][Eēёe][Wwω][Eёēe][Bbɓ][Nnɳη][Oøѳσo][Vѵv][Eёeē][Llɭ]\.\s?[Cƈcç][Oσøoѳ][Mɱm]\.?/g,
'',
/<p>\s*(?:(?:This (?:chapter is updated by|content is taken from)|Follow current novels on|Updated from) )?(?:[ƒfF][Rrɾг][Eēeё][Eēёe][Wwω][Eёēe][Bbɓ][Nnɳη][Oøѳσo][Vѵv][Eёeē][LlɭI\|]\.\s?[Cƈcç][Oσøoѳ][Mɱm]|ʀʙɴʟ)\.?/g,
'<p>',
)
.replace(
/(?<=<p>\s*)Visit for the best novel reading experience\.?/g,
'',
);
.replace(/<p>\s*Visit for the best novel reading experience\.?/g, '<p>');
}

async searchNovels(searchTerm: string): Promise<Plugin.NovelItem[]> {
Expand Down
4 changes: 2 additions & 2 deletions src/plugins/english/mvlempyr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class MVLEMPYRPlugin implements Plugin.PluginBase {
name = 'MVLEMPYR';
icon = 'src/en/mvlempyr/icon.png';
site = 'https://www.mvlempyr.com/';
version = '1.0.2';
version = '1.0.3';

_chapSite = 'https://chp.mvlempyr.net/';
_allNovels: (Plugin.NovelItem & ExtraNovelData)[] | undefined;
Expand Down Expand Up @@ -348,7 +348,7 @@ class MVLEMPYRPlugin implements Plugin.PluginBase {
chapters: posts.map(chap => ({
name: chap.acf.ch_name,
path: 'chapter/' + chap.acf.novel_code + '-' + chap.acf.chapter_number,
date: chap.date,
releaseTime: chap.date,
chapterNumber: chap.acf.chapter_number,
})),
status: loadedCheerio('div.novelstatustextmedium').text(),
Expand Down
157 changes: 119 additions & 38 deletions src/plugins/japanese/Syosetu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { fetchApi } from '@libs/fetch';
import { Plugin } from '@typings/plugin';
import { defaultCover } from '@libs/defaultCover';
import { FilterTypes, Filters } from '@libs/filterInputs';
import { NovelStatus } from '@libs/novelStatus';
// const novelStatus = require('@libs/novelStatus');
// const isUrlAbsolute = require('@libs/isAbsoluteUrl');
// const parseDate = require('@libs/parseDate');
Expand All @@ -13,7 +14,7 @@ class Syosetu implements Plugin.PluginBase {
icon = 'src/jp/syosetu/icon.png';
site = 'https://yomou.syosetu.com/';
novelPrefix = 'https://ncode.syosetu.com';
version = '1.1.0';
version = '1.1.2';
headers = {
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
Expand Down Expand Up @@ -69,25 +70,57 @@ class Syosetu implements Plugin.PluginBase {
const novels = await getNovelsFromPage(pageNo);
return novels;
}
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
private async parseChaptersFromPage(
loadedCheerio: cheerio.CheerioAPI,
): Promise<Plugin.ChapterItem[]> {
const chapters: Plugin.ChapterItem[] = [];

loadedCheerio('.p-eplist__sublist').each((_, element) => {
const chapterLink = loadedCheerio(element).find('a');
const chapterUrl = chapterLink.attr('href');
const chapterName = chapterLink.text().trim();
const releaseDate = loadedCheerio(element)
.find('.p-eplist__update')
.text()
.trim()
.split(' ')[0]
.replace(/\//g, '-');

if (chapterUrl) {
chapters.push({
name: chapterName,
releaseTime: releaseDate,
path: chapterUrl.replace(this.novelPrefix, ''),
});
}
});

return chapters;
}
async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> {
// First fetch main page
const result = await fetchApi(this.novelPrefix + novelPath, {
headers: this.headers,
});
const body = await result.text();
const loadedCheerio = loadCheerio(body, { decodeEntities: false });

// Parse novel status
// Parse status
let status = 'Unknown';
if (loadedCheerio('.c-announce').text().includes('連載中')) {
status = 'Ongoing';
if (
loadedCheerio('.c-announce').text().includes('連載中') ||
loadedCheerio('.c-announce').text().includes('未完結')
) {
status = NovelStatus.Ongoing;
} else if (
loadedCheerio('.c-announce').text().includes('更新されていません')
) {
status = NovelStatus.OnHiatus;
} else if (loadedCheerio('.c-announce').text().includes('完結')) {
status = 'Completed';
} else if (loadedCheerio('.c-announce').text().includes('約2ヶ月以上')) {
status = 'On Hiatus';
status = NovelStatus.Completed;
}

// Create novel object with basic metadata
// Create novel object with metadata
const novel: Plugin.SourceNovel = {
path: novelPath,
name: loadedCheerio('.p-novel__title').text(),
Expand All @@ -96,32 +129,81 @@ class Syosetu implements Plugin.PluginBase {
.replace('作者:', '')
.trim(),
status: status,
artist: '', // Not available on syosetu
artist: '',
cover: defaultCover,
chapters: [],
genres: loadedCheerio('meta[property="og:description"]')
.attr('content')
?.split(' ')
.join(','), // Get genres from meta tag
};

// Get summary if available
novel.summary = loadedCheerio('#novel_ex').text().trim();
novel.summary = loadedCheerio('#novel_ex').html() || '';

// Parse chapters using the correct selectors
loadedCheerio('.p-eplist__sublist').each((_, element) => {
const chapterLink = loadedCheerio(element).find('a');
const chapterUrl = chapterLink.attr('href');
const chapterName = chapterLink.text().trim();
const releaseDate = loadedCheerio(element)
.find('.p-eplist__update')
.text()
.trim()
.split(' ')[0] // Get just the date part
.replace(/\//g, '-'); // Format date as YYYY-MM-DD
if (chapterUrl) {
chapters.push({
name: chapterName,
releaseTime: releaseDate,
path: chapterUrl.replace(this.novelPrefix, ''),
const chapters: Plugin.ChapterItem[] = [];

// Get last page URL first
const lastPageLink = loadedCheerio('.c-pager__item--last').attr('href');

if (!lastPageLink) {
// If no pagination, just parse chapters from the current page
loadedCheerio('.p-eplist__sublist').each((_, element) => {
const chapterLink = loadedCheerio(element).find('a');
const chapterUrl = chapterLink.attr('href');
const chapterName = chapterLink.text().trim();
const releaseDate = loadedCheerio(element)
.find('.p-eplist__update')
.text()
.trim()
.split(' ')[0]
.replace(/\//g, '-');

if (chapterUrl) {
chapters.push({
name: chapterName,
releaseTime: releaseDate,
path: chapterUrl.replace(this.novelPrefix, ''),
});
}
});
} else {
const lastPageMatch = lastPageLink.match(/\?p=(\d+)/);
const totalPages = lastPageMatch ? parseInt(lastPageMatch[1]) : 1;

// Fetch all pages in parallel for better performance
const pagePromises = Array.from({ length: totalPages }, (_, i) =>
fetchApi(`${this.novelPrefix}${novelPath}?p=${i + 1}`).then(r =>
r.text(),
),
);

const pageResults = await Promise.all(pagePromises);

// Process each page's chapters
pageResults.forEach(pageBody => {
const pageCheerio = loadCheerio(pageBody, { decodeEntities: false });
pageCheerio('.p-eplist__sublist').each((_, element) => {
const chapterLink = pageCheerio(element).find('a');
const chapterUrl = chapterLink.attr('href');
const chapterName = chapterLink.text().trim();
const releaseDate = pageCheerio(element)
.find('.p-eplist__update')
.text()
.trim()
.split(' ')[0]
.replace(/\//g, '-');

if (chapterUrl) {
chapters.push({
name: chapterName,
releaseTime: releaseDate,
path: chapterUrl.replace(this.novelPrefix, ''),
});
}
});
}
});
});
}

novel.chapters = chapters;
return novel;
Expand All @@ -136,16 +218,15 @@ class Syosetu implements Plugin.PluginBase {
decodeEntities: false,
});

// Get the novel text content and process it
const chapterContent = cheerioQuery('.p-novel__body .js-novel-text')
.find('p')
.map((_, element) => {
return cheerioQuery(element).text();
})
.get()
.join('\n');
// Get the chapter title
const chapterTitle = cheerioQuery('.p-novel__title').html() || '';

// Get the chapter content
const chapterContent =
cheerioQuery('.p-novel__body .js-novel-text').html() || '';

return chapterContent || '';
// Combine title and content with proper HTML structure
return `<h1>${chapterTitle}</h1>${chapterContent}`;
}
async searchNovels(
searchTerm: string,
Expand Down
Loading

0 comments on commit bdd5066

Please sign in to comment.