-
Notifications
You must be signed in to change notification settings - Fork 305
feat(ar/markazriwayat): convert from madara to standalone plugin #2304
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
hamedhani1998
wants to merge
22
commits into
lnreader:master
Choose a base branch
from
hamedhani1998:feat-markazriwayat
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
f81b16a
feat(ar/markazriwayat): convert from madara to standalone plugin
e551923
feat(ar/markazriwayat): standalone plugin with full chapter loading
3673b89
fix(ar/markazriwayat): use REST API for search instead of broken HTML…
c486b8e
fix(ar/markazriwayat): simplify search with error handling and fallback
5580c17
fix(markazriwayat): version 1.0.0 and clean hidden HTML
cbb78a3
fix(markazriwayat): set version to 2.2.1 (1 increment from madara 2.2.0)
a2d3e47
Update plugins/arabic/Markazriwayat.ts
hamedhani1998 d191b8b
Apply suggestion from @K1ngfish3r
hamedhani1998 e902070
Update plugins/arabic/Markazriwayat.ts
hamedhani1998 4833a5d
Update plugins/arabic/Markazriwayat.ts
hamedhani1998 0b5fbdb
fix(ar/markazriwayat): reset version to 1.0.0
ef60862
fix(ar/markazriwayat): update version to 2.2.2
76ecd1f
fix(ar/markazriwayat): set version to 1.0.0
b51eac3
Apply suggestion from @K1ngfish3r
hamedhani1998 9dc89dc
Apply suggestion from @K1ngfish3r
hamedhani1998 35cbfeb
Apply suggestion from @K1ngfish3r
hamedhani1998 abd96d3
Apply suggestion from @K1ngfish3r
hamedhani1998 3d54994
Apply suggestion from @K1ngfish3r
hamedhani1998 e76ed06
Apply suggestion from @K1ngfish3r
hamedhani1998 2c40f4a
Apply suggestion from @K1ngfish3r
hamedhani1998 6138a70
Apply suggestion from @K1ngfish3r
hamedhani1998 9eaaa8e
fix(ar/markazriwayat): remove markazriwayat from madara sources and o…
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,225 @@ | ||
| import { load as parseHTML } from 'cheerio'; | ||
| import { fetchApi } from '@libs/fetch'; | ||
| import { Plugin } from '@/types/plugin'; | ||
| import { defaultCover } from '@libs/defaultCover'; | ||
| import { NovelStatus } from '@libs/novelStatus'; | ||
| import { FilterTypes, Filters } from '@libs/filterInputs'; | ||
|
|
||
| class Markazriwayat implements Plugin.PluginBase { | ||
| id = 'markazriwayat'; | ||
| name = 'مركز الروايات'; | ||
| version = '1.0.0'; | ||
| icon = 'src/ar/markazriwayat/icon.png'; | ||
| site = 'https://markazriwayat.com/'; | ||
|
|
||
|
|
||
| filters = { | ||
| order: { | ||
| type: FilterTypes.Picker, | ||
| label: 'الترتيب', | ||
| value: 'popular', | ||
| options: [ | ||
| { label: 'الأكثر شعبية', value: 'popular' }, | ||
| { label: 'الأحدث', value: 'new' }, | ||
| { label: 'الأعلى تقييماً', value: 'rating' }, | ||
| ], | ||
| }, | ||
| } satisfies Filters; | ||
|
|
||
| private async fetchHtml(url: string): Promise<string> { | ||
| const res = await fetchApi(url); | ||
| if (!res.ok) throw new Error(`Request failed: ${res.status}`); | ||
| return res.text(); | ||
| } | ||
|
|
||
| private parseNovelCards(html: string): Plugin.NovelItem[] { | ||
| const $ = parseHTML(html); | ||
| const novels: Plugin.NovelItem[] = []; | ||
| const seen = new Set<string>(); | ||
|
|
||
| $('a.lib-card').each((_, el) => { | ||
| const $el = $(el); | ||
| const href = $el.attr('href') || ''; | ||
| const path = href.replace(this.site, '').replace(/\/$/, ''); | ||
| const name = $el.find('.lib-card__title').text().trim(); | ||
| const cover = | ||
| $el.find('img').attr('data-src') || | ||
| $el.find('img').attr('data-defer-src') || | ||
| defaultCover; | ||
|
|
||
| if (name && path && !seen.has(path)) { | ||
| seen.add(path); | ||
| novels.push({ name, path, cover }); | ||
| } | ||
| }); | ||
|
|
||
| return novels; | ||
| } | ||
|
|
||
| async popularNovels( | ||
| page: number, | ||
| { | ||
| filters, | ||
| showLatestNovels, | ||
| }: Plugin.PopularNovelsOptions<typeof this.filters>, | ||
| ): Promise<Plugin.NovelItem[]> { | ||
| let url = `${this.site}`; | ||
| if (showLatestNovels) { | ||
| url += 'new/'; | ||
| } else { | ||
| url += `${filters.order.value}/`; | ||
| } | ||
| if (page > 1) url += `page/${page}/`; | ||
|
|
||
| const html = await this.fetchHtml(url); | ||
| return this.parseNovelCards(html); | ||
| } | ||
|
|
||
| async searchNovels( | ||
| searchTerm: string, | ||
| page: number, | ||
|
hamedhani1998 marked this conversation as resolved.
|
||
| ): Promise<Plugin.NovelItem[]> { | ||
| try { | ||
| if (page > 1) return []; | ||
| const apiUrl = `${this.site}wp-json/theam/v1/novel-search?term=${encodeURIComponent(searchTerm)}&per_page=20`; | ||
|
hamedhani1998 marked this conversation as resolved.
|
||
| const res = await fetchApi(apiUrl) | ||
| if (!res.ok) return []; | ||
| const data = await res.json(); | ||
| return (data.items || []).map( | ||
| (item: { title: string; link: string; cover?: string }) => ({ | ||
| name: item.title, | ||
| path: item.link.replace(this.site, ''), | ||
| cover: item.cover || defaultCover, | ||
| })); | ||
| } catch { | ||
| // Fallback: use library search HTML | ||
| try { | ||
| const url = `${this.site}library/?search=${encodeURIComponent(searchTerm)}`; | ||
| const html = await this.fetchHtml(url); | ||
| return this.parseNovelCards(html); | ||
| } catch { | ||
| return []; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| async parseNovel(novelPath: string): Promise<Plugin.SourceNovel> { | ||
| const html = await this.fetchHtml(`${this.site}${novelPath}`); | ||
| const $ = parseHTML(html); | ||
|
|
||
| const novel: Plugin.SourceNovel = { | ||
| path: novelPath, | ||
| name: $('h1').first().text().trim() || 'Untitled', | ||
| cover: defaultCover, | ||
| summary: '', | ||
| author: '', | ||
| status: NovelStatus.Unknown, | ||
| genres: '', | ||
| chapters: [], | ||
| }; | ||
|
|
||
| // Cover | ||
| const coverImg = $('img') | ||
| .filter(function () { | ||
| const src = $(this).attr('data-src') || $(this).attr('src') || ''; | ||
| return src.includes('wp-content/uploads') && !src.includes('cropped-'); | ||
| }) | ||
| .first(); | ||
| novel.cover = | ||
| coverImg.attr('data-src') || | ||
| coverImg.attr('data-defer-src') || | ||
| defaultCover; | ||
|
|
||
|
hamedhani1998 marked this conversation as resolved.
|
||
| // Status | ||
| const statusEl = $('.status-pill').first(); | ||
| const statusClass = statusEl.attr('class') || ''; | ||
| if (statusClass.includes('is-ongoing')) novel.status = NovelStatus.Ongoing; | ||
| else if (statusClass.includes('is-complete')) | ||
| novel.status = NovelStatus.Completed; | ||
| else if (statusClass.includes('is-stopped')) | ||
| novel.status = NovelStatus.OnHiatus; | ||
|
|
||
| // Author | ||
| const authorLink = $('a[href*="/author/"]').first(); | ||
| if (authorLink.length) novel.author = authorLink.text().trim(); | ||
|
|
||
| // Summary | ||
| novel.summary = $('#manga-summary').text().trim(); | ||
|
|
||
| // Genres | ||
| const genreParts: string[] = []; | ||
| $('a.pill, a[href*="/genre/"], a[href*="/tasnif/"]').each((_, el) => { | ||
| const t = $(el).text().trim(); | ||
| if (t) genreParts.push(t); | ||
| }); | ||
| novel.genres = genreParts.join(', '); | ||
|
|
||
| // Chapters: read total from HTML, generate paths directly (fast, no API) | ||
| const chapters: Plugin.ChapterItem[] = []; | ||
|
|
||
| const totalText = $('.manga-stat__value').last().text().trim(); | ||
| const totalMatch = totalText.match(/(\d+)/); | ||
| const totalChapters = totalMatch ? parseInt(totalMatch[1], 10) : 0; | ||
|
|
||
| const firstRow = $('div.ch-row').first(); | ||
| const firstLink = firstRow.find('a').first().attr('href') || ''; | ||
| const firstNum = firstRow.attr('data-ch-num') || ''; | ||
|
|
||
| if (totalChapters > 0 && firstLink && firstNum) { | ||
| const escapedNum = firstNum.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); | ||
| const basePath = firstLink.replace(new RegExp(`${escapedNum}/?$`), ''); | ||
| const basePathRelative = basePath.replace(this.site, ''); | ||
|
|
||
| for (let i = 1; i <= totalChapters; i++) { | ||
| chapters.push({ | ||
| name: `الفصل ${i}`, | ||
| path: basePathRelative + i + '/', | ||
| chapterNumber: i, | ||
| }); | ||
| } | ||
| } else { | ||
| $('div.ch-row').each((_, el) => { | ||
| const a = $(el).find('a').first(); | ||
| const name = | ||
| $(el).find('.ch-title').text().trim() || a.attr('aria-label') || ''; | ||
| const href = a.attr('href') || ''; | ||
| const date = $(el).find('.ch-date').text().trim(); | ||
| const chNum = $(el).attr('data-ch-num') || ''; | ||
| if (name && href) { | ||
| chapters.push({ | ||
| name, | ||
| path: href.replace(this.site, ''), | ||
| releaseTime: date || null, | ||
| chapterNumber: chNum ? parseInt(chNum, 10) : chapters.length + 1, | ||
| }); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| novel.chapters = chapters; | ||
|
|
||
| return novel; | ||
| } | ||
|
|
||
| async parseChapter(chapterPath: string): Promise<string> { | ||
| const html = await this.fetchHtml(`${this.site}${chapterPath}`); | ||
| const $ = parseHTML(html); | ||
|
|
||
| $( | ||
| 'script, style, .sharedaddy, .jp-relatedposts, .wp-block-spacer, .reading-nav, .ads, .advertisement, .nav-links, .comments-area', | ||
| ).remove(); | ||
|
|
||
| $( | ||
| '[style*="display:none"], [style*="display: none"], [hidden], .hidden', | ||
| ).remove(); | ||
|
|
||
| const content = | ||
| $('.reading-content, .entry-content, .chapter-content, .text-left') | ||
| .first() | ||
| .html() || ''; | ||
|
|
||
| return content || '<p>المحتوى غير متاح.</p>'; | ||
| } | ||
| } | ||
|
|
||
| export default new Markazriwayat(); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.