diff --git a/apps/backend/src/lib/pixiv.ts b/apps/backend/src/lib/pixiv.ts index b0b4b78..d300da5 100644 --- a/apps/backend/src/lib/pixiv.ts +++ b/apps/backend/src/lib/pixiv.ts @@ -1,148 +1,44 @@ import { MediaUpload } from "@/models/media"; -type PixivTag = { - tag?: unknown; - translation?: { en?: unknown } | null; -}; +function fetchPixivData(url: string): Promise { + // https://www.pixiv.net/artworks/143552616 + const match = url.match(/\/artworks\/(\d+)/); + if (!match) { + throw new Error("Invalid Pixiv URL"); + } + const artworkId = match[1]; -type PixivIllustBody = { - illustId?: unknown; - id?: unknown; - illustTitle?: unknown; - title?: unknown; - illustComment?: unknown; - description?: unknown; - urls?: Record; - tags?: { tags?: PixivTag[] } | PixivTag[] | string[]; - userId?: unknown; - userName?: unknown; - createDate?: unknown; - uploadDate?: unknown; - pageCount?: unknown; - xRestrict?: unknown; - url?: unknown; -}; -type PixivAjaxResponse = { - error?: boolean; - message?: string; - body?: T; -}; + return fetch(`https://www.phixiv.net/api/info?id=${artworkId}&language=ko`) + .then((response) => { + if (!response.ok) { + throw new Error(`Failed to fetch Pixiv data: ${response.status} ${response.statusText}`); + } + return response.json(); + }) + .then((data) => { + // if #R-18 in tags, throw error + if (data.tags && data.tags.some((tag: string) => tag.includes("R-18"))) { + throw new Error("Pixiv artwork is marked as R-18"); + } -type PixivPage = { - urls?: Record; -}; + return data; + }) + .then((data) => { + if (data.error) { + throw new Error(`Pixiv API error: ${data.message}`); + } + return data; + }); +} -function extractArtworkId(url: string): string { - const match = url.match(/(?:\/artworks\/|\/ajax\/illust\/)(\d+)/); +async function checkPixivData(url: string, selected: Array) { + const match = url.match(/\/artworks\/(\d+)/); if (!match) { throw new Error("Invalid Pixiv URL"); } - return match[1]; -} - -function pixivAjaxUrl(path: string): string { - return `https://www.pixiv.net/ajax/illust/${path}`; -} - -function getPreferredImageUrl(urls: Record | undefined): string | null { - if (!urls) return null; - - for (const key of ["original", "regular", "small", "thumb"]) { - const value = urls[key]; - if (typeof value === "string" && value.length > 0) return value; - } - - return null; -} - -function normalizeTags(tags: PixivIllustBody["tags"]): string[] { - const tagList = Array.isArray(tags) - ? tags - : tags && typeof tags === "object" && "tags" in tags - ? tags.tags - : []; - if (!Array.isArray(tagList)) return []; - - return tagList - .map((entry) => { - if (typeof entry === "string") return entry; - if (typeof entry.tag === "string") return entry.tag; - return null; - }) - .filter((tag): tag is string => tag !== null && tag.length > 0); -} - -function makePixivRequest(url: string) { - return fetch(url, { - headers: { - "Accept": "application/json", - "Referer": "https://www.pixiv.net/", - "User-Agent": "Mozilla/5.0 (compatible; akiyama.mizuki.guru/1.0)", - }, - }); -} - -async function readPixivJson(url: string): Promise { - const response = await makePixivRequest(url); - if (!response.ok) { - throw new Error(`Failed to fetch Pixiv data: ${response.status} ${response.statusText}`); - } - - const data = (await response.json()) as PixivAjaxResponse; - if (data.error) { - throw new Error(`Pixiv API error: ${data.message || "unknown error"}`); - } - if (data.body === undefined) { - throw new Error("Pixiv API response did not include a body"); - } - - return data.body; -} - -async function fetchPixivData(url: string): Promise { - const artworkId = extractArtworkId(url); - const body = await readPixivJson(pixivAjaxUrl(`${artworkId}?lang=ko`)); - const pageCount = typeof body.pageCount === "number" ? body.pageCount : 1; - const tags = normalizeTags(body.tags); - - if (tags.some((tag) => tag.includes("R-18"))) { - throw new Error("Pixiv artwork is marked as R-18"); - } - if (body.xRestrict === 1 || body.xRestrict === 2) { - throw new Error("Pixiv artwork is restricted"); - } - - let imageUrls: string[] = []; - if (pageCount > 1) { - const pages = await readPixivJson(pixivAjaxUrl(`${artworkId}/pages?lang=ko`)); - imageUrls = pages - .map((page) => getPreferredImageUrl(page.urls)) - .filter((imageUrl): imageUrl is string => imageUrl !== null); - } else { - const imageUrl = getPreferredImageUrl(body.urls); - if (imageUrl) imageUrls = [imageUrl]; - } - - return { - url: `https://www.pixiv.net/artworks/${artworkId}`, - illust_id: String(body.illustId || body.id || artworkId), - title: String(body.illustTitle || body.title || "Pixiv Artwork"), - description: String(body.illustComment || body.description || ""), - author_id: String(body.userId || "unknown"), - author_name: String(body.userName || "unknown"), - create_date: typeof body.createDate === "string" ? body.createDate : body.uploadDate, - tags, - image_proxy_urls: imageUrls, - image_urls: imageUrls, - page_count: pageCount, - raw: body, - }; -} - -async function checkPixivData(url: string, selected: Array) { - const artworkId = extractArtworkId(url); + const artworkId = match[1]; const selectedIndices = selected .map((isSelected, index) => (isSelected ? index : -1)) .filter((index) => index >= 0); @@ -159,4 +55,4 @@ async function checkPixivData(url: string, selected: Array) { return existing !== null; } -export { checkPixivData, fetchPixivData }; +export { checkPixivData, fetchPixivData }; \ No newline at end of file diff --git a/apps/backend/src/lib/s3.ts b/apps/backend/src/lib/s3.ts index c98afd3..16e368f 100644 --- a/apps/backend/src/lib/s3.ts +++ b/apps/backend/src/lib/s3.ts @@ -31,16 +31,7 @@ async function uploadToS3(fileName: string, mediaUrl: string, maxRetry = 3) { for (let attempt = 1; attempt <= maxRetry; attempt++) { try { - const parsedUrl = new URL(mediaUrl); - const isPixivImage = parsedUrl.hostname === "i.pximg.net"; - const response = await fetch(mediaUrl, { - headers: isPixivImage - ? { - "Referer": "https://www.pixiv.net/", - "User-Agent": "Mozilla/5.0 (compatible; akiyama.mizuki.guru/1.0)", - } - : undefined, - }); + const response = await fetch(mediaUrl); if (!response.ok) { throw new Error(`Fetch failed: ${response.status}`); } @@ -83,4 +74,4 @@ async function writeToS3(fileName: string, data: ArrayBuffer | Blob, contentType }); } -export { makeS3FileName, uploadToS3, writeToS3, client as s3Client }; +export { makeS3FileName, uploadToS3, writeToS3, client as s3Client }; \ No newline at end of file diff --git a/apps/backend/src/routes/proxy.ts b/apps/backend/src/routes/proxy.ts index 0a01901..bd16510 100644 --- a/apps/backend/src/routes/proxy.ts +++ b/apps/backend/src/routes/proxy.ts @@ -5,7 +5,6 @@ const ALLOWED_HOSTS = [ "video.twimg.com", "ton.twimg.com", "abs.twimg.com", - "i.pximg.net", ]; export default new Elysia({ prefix: "/proxy" }) @@ -26,7 +25,7 @@ export default new Elysia({ prefix: "/proxy" }) const response = await fetch(targetUrl, { headers: { "User-Agent": "Mozilla/5.0 (compatible; akiyama.mizuki.guru/1.0)", - "Referer": parsed.hostname === "i.pximg.net" ? "https://www.pixiv.net/" : "https://twitter.com/", + "Referer": "https://twitter.com/", }, }); diff --git a/apps/frontend/src/app/add/page.tsx b/apps/frontend/src/app/add/page.tsx index 978ab50..77617e4 100644 --- a/apps/frontend/src/app/add/page.tsx +++ b/apps/frontend/src/app/add/page.tsx @@ -250,7 +250,7 @@ export default function AddPage() { const data = (await response.json()) as PixivApiResponse; const items = (data.image_proxy_urls ?? []) .filter((imageUrl): imageUrl is string => typeof imageUrl === "string" && imageUrl.length > 0) - .map((imageUrl) => ({ url: proxyMediaUrl(imageUrl) })); + .map((imageUrl) => ({ url: imageUrl })); if (items.length === 0) throw new Error("이미지를 찾지 못했습니다."); diff --git a/apps/frontend/src/lib/media.ts b/apps/frontend/src/lib/media.ts index 11abad8..56f43a6 100644 --- a/apps/frontend/src/lib/media.ts +++ b/apps/frontend/src/lib/media.ts @@ -3,7 +3,6 @@ const PROXIED_HOSTS = [ "video.twimg.com", "ton.twimg.com", "abs.twimg.com", - "i.pximg.net", ]; export function proxyMediaUrl(url: string | undefined | null): string { diff --git a/bun.lock b/bun.lock index 51a1899..2f1921f 100644 --- a/bun.lock +++ b/bun.lock @@ -9,11 +9,11 @@ "name": "elysia", "version": "1.0.50", "dependencies": { - "@aws-sdk/client-s3": "^3.1075.0", + "@aws-sdk/client-s3": "^3.1053.0", "@elysiajs/jwt": "^1.4.2", "@elysiajs/openapi": "^1.4.15", "elysia": "latest", - "mongoose": "^9.7.2", + "mongoose": "^9.6.2", "ulid": "^3.0.2", }, "devDependencies": {