Skip to content
Open
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
95 changes: 86 additions & 9 deletions plugins/arabic/galaxynovels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type ChapterJSON = {
label: string;
title: string;
url: string;
content_api: string;
content_api?: string;
date_iso: string;
};

Expand All @@ -21,10 +21,23 @@ type ChaptersIndex = {
chapters: ChapterJSON[];
};

type ChaptersManifest = {
novel_id: number;
total: number;
pack_url: string;
live_tail: ChapterJSON[];
};

type ChaptersPack = {
novel_id: number;
total: number;
chapters: ChapterJSON[];
};

class GalaxyNovels implements Plugin.PluginBase {
id = 'galaxynovels';
name = 'Galaxy Novels';
version = '1.1.1';
version = '1.1.2';
icon = 'src/ar/galaxynovels/icon.png';
site = 'https://galaxynovels.com/';

Expand Down Expand Up @@ -58,6 +71,66 @@ class GalaxyNovels implements Plugin.PluginBase {
return url.startsWith('http') ? new URL(url).pathname : url;
}

private toChapterItem(
ch: ChapterJSON,
novelPath: string,
): Plugin.ChapterItem {
return {
name: ch.label + (ch.title ? `: ${ch.title}` : ''),
path: this.toChapterPath(ch.url) || `${novelPath}chapter-${ch.id}/`,
chapterNumber: ch.position,
releaseTime: ch.date_iso?.split('T')[0] || '',
};
}

// The novel page only renders the latest 30 chapters (data-per-page="30")
// and the data-index-url endpoint is currently empty, so the full list is
// read from the static chapter manifest instead: the manifest points at a
// pack file holding every chapter, plus a live_tail with the newest ones.
// The tail is merged over the pack the way the site's own reader does, so
// chapters published after the pack was generated are included. Anything
// missing or malformed here returns null so parseNovel falls back to the
// page-derived list instead of an empty chapter list.
private async fetchManifestChapters(
manifestUrl: string | undefined,
novelPath: string,
): Promise<Plugin.ChapterItem[] | null> {
if (!manifestUrl) return null;
try {
const manifestEndpoint = manifestUrl.startsWith('http')
? manifestUrl
: `${this.baseUrl}${manifestUrl}`;
const manifest = await this.fetchJson<ChaptersManifest>(manifestEndpoint);
if (!manifest?.pack_url) return null;

const packEndpoint = manifest.pack_url.startsWith('http')
? manifest.pack_url
: `${this.baseUrl}${manifest.pack_url}`;
const pack = await this.fetchJson<ChaptersPack>(packEndpoint);
if (!pack || !Array.isArray(pack.chapters)) return null;

const byId = new Map<number, ChapterJSON>();
for (const ch of pack.chapters) {
if (ch && typeof ch.id === 'number' && ch.url) byId.set(ch.id, ch);
}
const liveTail = Array.isArray(manifest.live_tail)
? manifest.live_tail
: [];
for (const ch of liveTail) {
if (ch && typeof ch.id === 'number' && ch.url) byId.set(ch.id, ch);
}

const merged = Array.from(byId.values())
.filter(ch => typeof ch.position === 'number')
.sort((a, b) => a.position - b.position);
if (merged.length === 0) return null;

return merged.map(ch => this.toChapterItem(ch, novelPath));
} catch {
return null;
}
}

private async fetchHtml(url: string): Promise<string> {
const res = await fetchApi(url);
if (!res.ok) {
Expand Down Expand Up @@ -145,22 +218,26 @@ class GalaxyNovels implements Plugin.PluginBase {

const chaptersContainer = $('[data-wor-chapters-container]');
const chaptersIndexUrl = chaptersContainer.attr('data-index-url');
const chaptersManifestUrl = chaptersContainer.attr('data-manifest-url');

let chapters: Plugin.ChapterItem[] = [];

if (chaptersIndexUrl) {
const manifestChapters = await this.fetchManifestChapters(
chaptersManifestUrl,
novelPath,
);
if (manifestChapters && manifestChapters.length > 0) {
chapters = manifestChapters;
}

if (chapters.length === 0 && chaptersIndexUrl) {
try {
const indexUrl = chaptersIndexUrl.startsWith('http')
? chaptersIndexUrl
: `${this.baseUrl}${chaptersIndexUrl}`;
const index = await this.fetchJson<ChaptersIndex>(indexUrl);

chapters = index.chapters.map(ch => ({
name: ch.label + (ch.title ? `: ${ch.title}` : ''),
path: this.toChapterPath(ch.url) || `${novelPath}chapter-${ch.id}/`,
chapterNumber: ch.position,
releaseTime: ch.date_iso?.split('T')[0] || '',
}));
chapters = index.chapters.map(ch => this.toChapterItem(ch, novelPath));
} catch {
// fallback to HTML parsing
}
Expand Down