From f66aa36890abbefff040a0cf7610c72e2bbdf1df Mon Sep 17 00:00:00 2001
From: ld3zian <42660576+ld3z@users.noreply.github.com>
Date: Tue, 28 Jan 2025 17:40:58 -0700
Subject: [PATCH 1/4] update custom feed validatity
---
package-lock.json | 28 ++++++++++++++++++++++++++++
package.json | 1 +
src/pages/rss/[feedId].xml.ts | 26 ++++++++++++++++++++------
src/pages/rss/custom/[slugs].xml.ts | 26 ++++++++++++++++++++------
4 files changed, 69 insertions(+), 12 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index ced729e..9b39710 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,6 +15,7 @@
"@vercel/analytics": "^1.4.1",
"@vercel/kv": "^1.0.1",
"astro": "^5.1.6",
+ "feed": "^4.2.2",
"ioredis": "^5.4.2",
"tailwindcss": "^3.4.1"
}
@@ -2970,6 +2971,17 @@
"reusify": "^1.0.4"
}
},
+ "node_modules/feed": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/feed/-/feed-4.2.2.tgz",
+ "integrity": "sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==",
+ "dependencies": {
+ "xml-js": "^1.6.11"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
@@ -5558,6 +5570,11 @@
"queue-microtask": "^1.2.2"
}
},
+ "node_modules/sax": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
+ "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg=="
+ },
"node_modules/semver": {
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
@@ -7038,6 +7055,17 @@
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
+ "node_modules/xml-js": {
+ "version": "1.6.11",
+ "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz",
+ "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==",
+ "dependencies": {
+ "sax": "^1.2.4"
+ },
+ "bin": {
+ "xml-js": "bin/cli.js"
+ }
+ },
"node_modules/xxhash-wasm": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz",
diff --git a/package.json b/package.json
index 5e529dc..cc44e93 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,7 @@
"@vercel/analytics": "^1.4.1",
"@vercel/kv": "^1.0.1",
"astro": "^5.1.6",
+ "feed": "^4.2.2",
"ioredis": "^5.4.2",
"tailwindcss": "^3.4.1"
}
diff --git a/src/pages/rss/[feedId].xml.ts b/src/pages/rss/[feedId].xml.ts
index d589c17..37c1c49 100644
--- a/src/pages/rss/[feedId].xml.ts
+++ b/src/pages/rss/[feedId].xml.ts
@@ -1,4 +1,4 @@
-import rss from "@astrojs/rss";
+import { Feed } from 'feed';
import { getChaptersForSlugs, isValidLanguage } from "../../lib/api";
import { getFeedMapping } from "../../lib/db";
import type { APIRoute } from "astro";
@@ -28,14 +28,21 @@ export const GET: APIRoute = async ({ params }) => {
return new Response("No chapters found for the provided comics", { status: 404 });
}
- return rss({
+ const feed = new Feed({
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) => ({
+ id: "https://github.com/ld3z/manga-rss",
+ link: "https://github.com/ld3z/manga-rss",
+ language: lang,
+ copyright: "All rights reserved",
+ updated: new Date(),
+ });
+
+ chapters.forEach((chapter) => {
+ feed.addItem({
title: `${chapter.md_comics.title} - Chapter ${chapter.chap}`,
+ id: `${chapter.md_comics.slug}-${chapter.chap}`,
link: `https://comick.io/comic/${chapter.md_comics.slug}`,
- pubDate: new Date(chapter.updated_at),
description: `
${
@@ -47,6 +54,13 @@ export const GET: APIRoute = async ({ params }) => {
New chapter available: Chapter ${chapter.chap}
`,
- })),
+ date: new Date(chapter.updated_at),
+ });
+ });
+
+ return new Response(feed.rss2(), {
+ headers: {
+ 'Content-Type': 'application/xml; charset=utf-8',
+ },
});
};
\ No newline at end of file
diff --git a/src/pages/rss/custom/[slugs].xml.ts b/src/pages/rss/custom/[slugs].xml.ts
index f5bb501..7aafc15 100644
--- a/src/pages/rss/custom/[slugs].xml.ts
+++ b/src/pages/rss/custom/[slugs].xml.ts
@@ -1,4 +1,4 @@
-import rss from "@astrojs/rss";
+import { Feed } from 'feed';
import { getChaptersForSlugs, isValidLanguage } from "../../../lib/api";
import type { APIRoute } from "astro";
@@ -22,15 +22,29 @@ export const GET: APIRoute = async ({ params, url }) => {
return new Response("No chapters found for the provided comics", { status: 404 });
}
- return rss({
+ const feed = new Feed({
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) => ({
+ id: "https://github.com/ld3z/manga-rss",
+ link: "https://github.com/ld3z/manga-rss",
+ language: lang,
+ copyright: "All rights reserved",
+ updated: new Date(),
+ });
+
+ chapters.forEach((chapter) => {
+ feed.addItem({
title: `${chapter.md_comics.title} - Chapter ${chapter.chap}`,
+ id: `${chapter.md_comics.slug}-${chapter.chap}`,
link: `https://comick.io/comic/${chapter.md_comics.slug}`,
- pubDate: new Date(chapter.updated_at),
description: `New chapter available: Chapter ${chapter.chap}`,
- })),
+ date: new Date(chapter.updated_at),
+ });
+ });
+
+ return new Response(feed.rss2(), {
+ headers: {
+ 'Content-Type': 'application/xml; charset=utf-8',
+ },
});
};
\ No newline at end of file
From 40552e471c3b6dc3a577dcfe8833a5169577a3f2 Mon Sep 17 00:00:00 2001
From: ld3zian <42660576+ld3z@users.noreply.github.com>
Date: Tue, 28 Jan 2025 17:48:05 -0700
Subject: [PATCH 2/4] fix feed?
---
src/pages/rss/[feedId].xml.ts | 17 ++++++++++++-----
src/pages/rss/custom/[slugs].xml.ts | 15 +++++++++++----
2 files changed, 23 insertions(+), 9 deletions(-)
diff --git a/src/pages/rss/[feedId].xml.ts b/src/pages/rss/[feedId].xml.ts
index 37c1c49..4fcb502 100644
--- a/src/pages/rss/[feedId].xml.ts
+++ b/src/pages/rss/[feedId].xml.ts
@@ -3,7 +3,7 @@ import { getChaptersForSlugs, isValidLanguage } from "../../lib/api";
import { getFeedMapping } from "../../lib/db";
import type { APIRoute } from "astro";
-export const GET: APIRoute = async ({ params }) => {
+export const GET: APIRoute = async ({ params, request }) => {
const feedId = params.feedId?.replace('.xml', '');
if (!feedId) {
@@ -28,21 +28,28 @@ export const GET: APIRoute = async ({ params }) => {
return new Response("No chapters found for the provided comics", { status: 404 });
}
+ const siteUrl = new URL(request.url).origin;
+ const feedUrl = new URL(request.url).href;
+
const feed = new Feed({
title: `ComicK - Custom Feed (${lang.toUpperCase()})`,
description: `Custom RSS feed for selected comics: ${slugs.join(', ')}`,
- id: "https://github.com/ld3z/manga-rss",
- link: "https://github.com/ld3z/manga-rss",
+ id: feedUrl,
+ link: siteUrl,
+ feedLinks: {
+ rss2: feedUrl
+ },
language: lang,
copyright: "All rights reserved",
updated: new Date(),
});
chapters.forEach((chapter) => {
+ const chapterUrl = `https://comick.io/comic/${chapter.md_comics.slug}`;
feed.addItem({
title: `${chapter.md_comics.title} - Chapter ${chapter.chap}`,
- id: `${chapter.md_comics.slug}-${chapter.chap}`,
- link: `https://comick.io/comic/${chapter.md_comics.slug}`,
+ id: `urn:comick:${chapter.md_comics.slug}:${chapter.chap}`,
+ link: chapterUrl,
description: `
${
diff --git a/src/pages/rss/custom/[slugs].xml.ts b/src/pages/rss/custom/[slugs].xml.ts
index 7aafc15..dd94d9f 100644
--- a/src/pages/rss/custom/[slugs].xml.ts
+++ b/src/pages/rss/custom/[slugs].xml.ts
@@ -22,21 +22,28 @@ export const GET: APIRoute = async ({ params, url }) => {
return new Response("No chapters found for the provided comics", { status: 404 });
}
+ const siteUrl = new URL(url).origin;
+ const feedUrl = new URL(url).href;
+
const feed = new Feed({
title: `ComicK - Custom Feed (${lang.toUpperCase()})`,
description: `Custom RSS feed for selected comics: ${slugs.join(', ')}`,
- id: "https://github.com/ld3z/manga-rss",
- link: "https://github.com/ld3z/manga-rss",
+ id: feedUrl,
+ link: siteUrl,
+ feedLinks: {
+ rss2: feedUrl
+ },
language: lang,
copyright: "All rights reserved",
updated: new Date(),
});
chapters.forEach((chapter) => {
+ const chapterUrl = `https://comick.io/comic/${chapter.md_comics.slug}`;
feed.addItem({
title: `${chapter.md_comics.title} - Chapter ${chapter.chap}`,
- id: `${chapter.md_comics.slug}-${chapter.chap}`,
- link: `https://comick.io/comic/${chapter.md_comics.slug}`,
+ id: `urn:comick:${chapter.md_comics.slug}:${chapter.chap}`,
+ link: chapterUrl,
description: `New chapter available: Chapter ${chapter.chap}`,
date: new Date(chapter.updated_at),
});
From 6ca77cf6afa2bdc9f9c4167759ff751c9163dd5a Mon Sep 17 00:00:00 2001
From: ld3zian <42660576+ld3z@users.noreply.github.com>
Date: Tue, 28 Jan 2025 18:34:35 -0700
Subject: [PATCH 3/4] fixed the feed
---
src/pages/rss/[feedId].xml.ts | 3 ++-
src/pages/rss/custom/[slugs].xml.ts | 3 ++-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/pages/rss/[feedId].xml.ts b/src/pages/rss/[feedId].xml.ts
index 4fcb502..9429878 100644
--- a/src/pages/rss/[feedId].xml.ts
+++ b/src/pages/rss/[feedId].xml.ts
@@ -42,13 +42,14 @@ export const GET: APIRoute = async ({ params, request }) => {
language: lang,
copyright: "All rights reserved",
updated: new Date(),
+ generator: 'ComicK RSS Generator',
});
chapters.forEach((chapter) => {
const chapterUrl = `https://comick.io/comic/${chapter.md_comics.slug}`;
feed.addItem({
title: `${chapter.md_comics.title} - Chapter ${chapter.chap}`,
- id: `urn:comick:${chapter.md_comics.slug}:${chapter.chap}`,
+ id: `${chapter.md_comics.slug}-${chapter.chap}`,
link: chapterUrl,
description: `
diff --git a/src/pages/rss/custom/[slugs].xml.ts b/src/pages/rss/custom/[slugs].xml.ts
index dd94d9f..d29f95c 100644
--- a/src/pages/rss/custom/[slugs].xml.ts
+++ b/src/pages/rss/custom/[slugs].xml.ts
@@ -36,13 +36,14 @@ export const GET: APIRoute = async ({ params, url }) => {
language: lang,
copyright: "All rights reserved",
updated: new Date(),
+ generator: 'ComicK RSS Generator',
});
chapters.forEach((chapter) => {
const chapterUrl = `https://comick.io/comic/${chapter.md_comics.slug}`;
feed.addItem({
title: `${chapter.md_comics.title} - Chapter ${chapter.chap}`,
- id: `urn:comick:${chapter.md_comics.slug}:${chapter.chap}`,
+ id: `${chapter.md_comics.slug}-${chapter.chap}`,
link: chapterUrl,
description: `New chapter available: Chapter ${chapter.chap}`,
date: new Date(chapter.updated_at),
From 17ceea25d97e34a43cb04044eb5a9a822c9ad7d0 Mon Sep 17 00:00:00 2001
From: ld3zian <42660576+ld3z@users.noreply.github.com>
Date: Tue, 28 Jan 2025 18:49:06 -0700
Subject: [PATCH 4/4] better error handling and performance improvements
---
src/components/ComicCard.astro | 4 ++-
src/config/backgrounds.ts | 7 ++++++
src/layouts/Layout.astro | 11 +++++----
src/lib/api.ts | 27 ++++++++++++++------
src/lib/db.ts | 45 +++++++++++++++++++++++++++++++---
src/lib/types.ts | 16 +++++++-----
6 files changed, 87 insertions(+), 23 deletions(-)
diff --git a/src/components/ComicCard.astro b/src/components/ComicCard.astro
index 37687b2..f7923b0 100644
--- a/src/components/ComicCard.astro
+++ b/src/components/ComicCard.astro
@@ -20,6 +20,8 @@ const isNSFW = comic.md_comics.content_rating === 'erotica';
@@ -50,7 +52,7 @@ const isNSFW = comic.md_comics.content_rating === 'erotica';
-
+
{comic.md_comics.title}
diff --git a/src/config/backgrounds.ts b/src/config/backgrounds.ts
index 329fce9..4179bc8 100644
--- a/src/config/backgrounds.ts
+++ b/src/config/backgrounds.ts
@@ -75,3 +75,10 @@ export function getRandomBackground(): BackgroundImage {
}
return backgrounds[Math.floor(Math.random() * backgrounds.length)];
}
+
+export function preloadBackgrounds() {
+ backgrounds.forEach(bg => {
+ const img = new Image();
+ img.src = bg.url;
+ });
+}
diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro
index 7c27522..7ce0919 100644
--- a/src/layouts/Layout.astro
+++ b/src/layouts/Layout.astro
@@ -147,11 +147,12 @@ const isCreditsPage = Astro.url.pathname === '/credits';
}
};
- // Initialize theme
- const savedTheme = localStorage.getItem('theme') as 'light' | 'dark' | null;
- const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
- const initialTheme = savedTheme || (prefersDark ? 'dark' : 'light');
- updateThemeToggle(initialTheme);
+ // Add theme initialization to prevent flash of wrong theme
+ const themeScript = `
+ const theme = localStorage.getItem('theme') ??
+ (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
+ document.documentElement.setAttribute('data-theme', theme);
+ `;
// Event listeners
settingsButton?.addEventListener('click', () => {
diff --git a/src/lib/api.ts b/src/lib/api.ts
index d566cbe..3d482f1 100644
--- a/src/lib/api.ts
+++ b/src/lib/api.ts
@@ -69,30 +69,43 @@ export function getGenreNames(genreIds: number[], genres: Genre[]): string[] {
.filter(name => name !== '');
}
+export class APIError extends Error {
+ constructor(
+ message: string,
+ public statusCode?: number,
+ public endpoint?: string
+ ) {
+ super(message);
+ this.name = 'APIError';
+ }
+}
+
export async function fetchComics(
language: string = 'en',
contentType: string = 'sfw',
comicType: ComicType = 'all',
page: number = 1
): Promise
{
+ const apiUrl = `${urlBase}?lang=${language}&page=${page}&order=new&accept_erotic_content=${contentType === 'nsfw'}${comicType === 'all' ? '' : `&type=${comicType}`}`;
+
try {
- const typeParam = comicType === 'all' ? '' : `&type=${comicType}`;
- const apiUrl = `${urlBase}?lang=${language}&page=${page}&order=new&accept_erotic_content=${contentType === 'nsfw'}${typeParam}`;
-
console.log('Fetching comics from:', apiUrl);
const response = await fetchWithRetry(apiUrl);
const data = await response.json();
if (!Array.isArray(data)) {
- console.error('Unexpected API response format:', data);
- return [];
+ throw new APIError('Invalid API response format', undefined, apiUrl);
}
return data;
} catch (error) {
- console.error('Error fetching comics:', error);
- return [];
+ if (error instanceof APIError) throw error;
+ throw new APIError(
+ 'Failed to fetch comics',
+ error instanceof Response ? error.status : undefined,
+ apiUrl
+ );
}
}
diff --git a/src/lib/db.ts b/src/lib/db.ts
index 84a8deb..ffcc509 100644
--- a/src/lib/db.ts
+++ b/src/lib/db.ts
@@ -3,6 +3,8 @@ import type { FeedMapping } from './types';
const REDIS_URL = import.meta.env.REDIS_URL || process.env.REDIS_URL;
let redis: Redis | null = null;
+let redisRetryCount = 0;
+const MAX_RETRIES = 3;
try {
console.log('Environment check:', {
@@ -23,11 +25,14 @@ try {
console.log(`Retry attempt ${times} with delay ${delay}ms`);
return delay;
},
- maxRetriesPerRequest: 3,
+ maxRetriesPerRequest: 2,
enableReadyCheck: true,
- reconnectOnError(err) {
- console.log('Reconnect on error:', err.message);
- return true;
+ reconnectOnError: (err) => {
+ const targetError = 'READONLY';
+ if (err.message.includes(targetError)) {
+ return true;
+ }
+ return false;
}
});
@@ -55,6 +60,38 @@ try {
}
}
+async function getRedisClient(): Promise {
+ if (redis?.status === 'ready') return redis;
+
+ if (redisRetryCount >= MAX_RETRIES) {
+ throw new Error('Failed to connect to Redis after multiple attempts');
+ }
+
+ try {
+ redis = new Redis(REDIS_URL, {
+ retryStrategy(times) {
+ const delay = Math.min(times * 50, 2000);
+ console.log(`Retry attempt ${times} with delay ${delay}ms`);
+ return delay;
+ },
+ maxRetriesPerRequest: 2,
+ enableReadyCheck: true,
+ reconnectOnError: (err) => {
+ const targetError = 'READONLY';
+ if (err.message.includes(targetError)) {
+ return true;
+ }
+ return false;
+ }
+ });
+ redisRetryCount = 0;
+ return redis;
+ } catch (error) {
+ redisRetryCount++;
+ throw error;
+ }
+}
+
export async function storeFeedMapping(feedId: string, slugs: string[], lang: string): Promise {
if (!redis) {
console.error('Redis client not initialized');
diff --git a/src/lib/types.ts b/src/lib/types.ts
index 69b3f03..2970743 100644
--- a/src/lib/types.ts
+++ b/src/lib/types.ts
@@ -6,11 +6,9 @@ export interface Comic {
title: string;
slug: string;
genres: number[];
- md_covers: {
- b2key: string;
- }[];
- content_rating: string;
- comic_type: string;
+ md_covers: ComicCover[];
+ content_rating: 'safe' | 'suggestive' | 'erotica';
+ comic_type: ComicType;
};
}
@@ -24,4 +22,10 @@ export interface FeedMapping {
lang: string;
}
-export type ComicType = 'manga' | 'manhwa' | 'manhua' | 'all';
\ No newline at end of file
+export type ComicType = 'manga' | 'manhwa' | 'manhua' | 'all';
+
+export interface ComicCover {
+ b2key: string;
+ width?: number;
+ height?: number;
+}
\ No newline at end of file