diff --git a/.gitignore b/.gitignore index 016b59e..d1745cb 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,4 @@ pnpm-debug.log* .DS_Store # jetbrains setting folder -.idea/ +.idea/ \ No newline at end of file diff --git a/src/components/SlugInput.astro b/src/components/SlugInput.astro new file mode 100644 index 0000000..c5a953b --- /dev/null +++ b/src/components/SlugInput.astro @@ -0,0 +1,163 @@ +--- +interface Props { + currentSlugs?: string[]; +} + +const { currentSlugs = [] } = Astro.props; + +import { availableLanguages, languageMap } from '../lib/api'; +--- + +
+
+ +
+ + +
+
+ +
+ + +
+ +
+ {currentSlugs.map(slug => ( +
+ {slug} + +
+ ))} +
+ +
+ + + + + + + Generate RSS Feed + +
+
+ + \ No newline at end of file diff --git a/src/lib/api.ts b/src/lib/api.ts index 7b6a50b..fed06a3 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -99,4 +99,157 @@ export function getLanguageName(code: string): string { export const availableLanguages = Array.from(languages); export const availableContentTypes = Array.from(contentTypes); -export { languageMap }; \ No newline at end of file +export { languageMap }; + +interface ComicDetail { + comic: { + hid: string; + title: string; + slug: string; + }; +} + +interface ChapterDetail { + id: string; + chap: string; + title: string; + updated_at: string; + md_comics: { + title: string; + slug: string; + }; +} + +interface ChapterParams { + limit?: number; + lang?: string; +} + +export async function getComicBySlug(slug: string): Promise { + try { + const response = await fetchWithRetry(`https://api.comick.fun/comic/${slug}`); + const data = await response.json(); + + if (!data?.comic?.hid) { + console.error(`Invalid comic data for slug ${slug}:`, data); + return null; + } + + return data; + } catch (error) { + console.error(`Error fetching comic ${slug}:`, error); + return null; + } +} + +export async function getChaptersByHid( + hid: string, + slug: string, + params: ChapterParams = { limit: 15, lang: 'en' } +): Promise { + try { + const queryParams = new URLSearchParams({ + limit: params.limit?.toString() || '15', + lang: params.lang || 'en', + ordering: '-created_at' + }); + + const comicResponse = await fetchWithRetry( + `https://api.comick.fun/comic/${hid}` + ); + const comicData = await comicResponse.json(); + const comicTitle = comicData?.comic?.title || 'Unknown Comic'; + + const response = await fetchWithRetry( + `https://api.comick.fun/comic/${hid}/chapters?${queryParams}` + ); + const data = await response.json(); + + if (!data?.chapters || !Array.isArray(data.chapters)) { + console.error(`Invalid chapters data for hid ${hid}:`, data); + return []; + } + + console.log('Processing chapters for comic:', comicTitle); + + const chapters = data.chapters + .filter(chapter => { + const isValid = chapter && + chapter.chap && + chapter.hid && + (chapter.publish_at || chapter.created_at || chapter.updated_at); + return isValid; + }) + .map(chapter => ({ + id: chapter.hid, + chap: chapter.chap, + title: chapter.title || `Chapter ${chapter.chap}`, + updated_at: chapter.publish_at || chapter.created_at || chapter.updated_at, + md_comics: { + title: comicTitle, + slug: slug + } + })); + + console.log(`Successfully processed ${chapters.length} chapters for ${comicTitle}`); + return chapters; + } catch (error) { + console.error(`Error fetching chapters for hid ${hid}:`, error); + return []; + } +} + +export async function getChaptersForSlugs( + slugs: string[], + lang: string = 'en' +): Promise { + console.log(`Fetching chapters for slugs:`, slugs, `in language: ${lang}`); + const chapters: ChapterDetail[] = []; + + for (const slug of slugs) { + console.log(`Processing slug: ${slug}`); + const comicDetail = await getComicBySlug(slug); + + if (!comicDetail) { + console.error(`Failed to get comic details for slug: ${slug}`); + continue; + } + + if (!comicDetail.comic?.hid) { + console.error(`No HID found for slug ${slug}`, comicDetail); + continue; + } + + console.log(`Found HID ${comicDetail.comic.hid} for slug ${slug}`); + const comicChapters = await getChaptersByHid( + comicDetail.comic.hid, + slug, + { + limit: 15, + lang: lang + } + ); + console.log(`Found ${comicChapters.length} chapters for ${slug}`); + + if (comicChapters.length > 0) { + chapters.push(...comicChapters); + } + } + + // Sort by chapter number (descending) first, then by date + const sortedChapters = chapters.sort((a, b) => { + // Convert chapter numbers to floats for proper numerical sorting + const chapA = parseFloat(a.chap); + const chapB = parseFloat(b.chap); + + if (chapB !== chapA) { + return chapB - chapA; // Sort by chapter number first + } + + // If chapters are the same, sort by date + return new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime(); + }); + + console.log(`Total chapters found: ${sortedChapters.length}`); + return sortedChapters; +} \ No newline at end of file diff --git a/src/pages/[lang]/[type].astro b/src/pages/[lang]/[type].astro index cba7108..33e115c 100644 --- a/src/pages/[lang]/[type].astro +++ b/src/pages/[lang]/[type].astro @@ -47,7 +47,27 @@ const description = `Latest ${type!.toUpperCase()} comics in ${getLanguageName(l diff --git a/src/pages/custom.astro b/src/pages/custom.astro new file mode 100644 index 0000000..3608115 --- /dev/null +++ b/src/pages/custom.astro @@ -0,0 +1,22 @@ +--- +import Layout from '../layouts/Layout.astro'; +import SlugInput from '../components/SlugInput.astro'; + +const title = "ComicK - Custom RSS Feed"; +const description = "Create a custom RSS feed for your favorite comics"; +--- + + +
+

Custom RSS Feed

+ +
+ +
+ +
+

Add the comic slugs from the URLs of your favorite comics on ComicK.

+

Example: For "https://comick.io/comic/01-one-piece", the slug is "01-one-piece"

+
+
+
\ No newline at end of file diff --git a/src/pages/rss/custom/[slugs].xml.ts b/src/pages/rss/custom/[slugs].xml.ts new file mode 100644 index 0000000..f5bb501 --- /dev/null +++ b/src/pages/rss/custom/[slugs].xml.ts @@ -0,0 +1,36 @@ +import rss from "@astrojs/rss"; +import { getChaptersForSlugs, isValidLanguage } from "../../../lib/api"; +import type { APIRoute } from "astro"; + +export const GET: APIRoute = async ({ params, url }) => { + const slugs = params.slugs?.split(',') || []; + const lang = url.searchParams.get('lang') || 'en'; + + if (slugs.length === 0) { + return new Response("No slugs provided", { status: 400 }); + } + + if (!isValidLanguage(lang)) { + return new Response("Invalid language", { status: 400 }); + } + + console.log('Processing RSS request for slugs:', slugs, 'in language:', lang); + const chapters = await getChaptersForSlugs(slugs, lang); + + if (chapters.length === 0) { + console.log('No chapters found for any of the provided slugs'); + return new Response("No chapters found for the provided comics", { status: 404 }); + } + + return rss({ + title: `ComicK - Custom Feed (${lang.toUpperCase()})`, + description: `Custom RSS feed for selected comics: ${slugs.join(', ')}`, + site: "https://github.com/ld3z/manga-rss", + items: chapters.map((chapter) => ({ + title: `${chapter.md_comics.title} - Chapter ${chapter.chap}`, + link: `https://comick.io/comic/${chapter.md_comics.slug}`, + pubDate: new Date(chapter.updated_at), + description: `New chapter available: Chapter ${chapter.chap}`, + })), + }); +}; \ No newline at end of file