Files
worst-scan-web/src/app/api/webhooks/manga/route.ts
T

89 lines
2.9 KiB
TypeScript

import { NextRequest } from "next/server"
import crypto from "crypto"
import { createPost, getPostByGid, updatePost } from "@/lib/db"
import { cleanTitle, slugify } from "@/lib/slug"
import { cacheCover } from "@/lib/cover-cache"
function verifySignature(bodyText: string, signature: string, secret: string): boolean {
try {
const hmac = crypto.createHmac("sha256", secret)
hmac.update(bodyText)
const expected = hmac.digest("hex")
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))
} catch {
return false
}
}
export async function POST(req: NextRequest) {
try {
const rawBody = await req.text()
const secret = process.env.WEBHOOK_SECRET || process.env.FANSUB_WEBHOOK_SECRET || ""
if (secret) {
const signature = req.headers.get("x-signature-256") || req.headers.get("X-Signature-256") || ""
if (!signature || !verifySignature(rawBody, signature, secret)) {
return Response.json({ error: "Unauthorized: invalid signature" }, { status: 401 })
}
}
const payload = JSON.parse(rawBody)
const gid = payload.gid
const rawTitle = payload.title
if (!gid || !rawTitle) {
return Response.json({ error: "Invalid payload: gid and title required" }, { status: 400 })
}
const title = cleanTitle(rawTitle)
const tags = Array.isArray(payload.tags) ? payload.tags : []
const numPages = payload.num_pages || payload.pages || 0
const synopsis = payload.synopsis || ""
const sourceUrl = payload.url || (payload.source === "nhentai" ? `https://nhentai.net/g/${gid}/` : (payload.source === "ehentai" ? `https://e-hentai.org/g/${gid}/` : undefined))
const slug = slugify(title, gid)
let post = getPostByGid(gid)
if (!post) {
post = createPost({
gid,
title,
title_jpn: payload.title_jpn || undefined,
artist: payload.artist || undefined,
parody: payload.parody || undefined,
tags,
num_pages: numPages,
source: payload.source || undefined,
cover_url: payload.cover_url || undefined,
url: sourceUrl,
summary: synopsis || undefined,
slug,
published: 1,
})
} else {
post = updatePost(post.id, {
title,
title_jpn: payload.title_jpn || post.title_jpn || undefined,
artist: payload.artist || post.artist || undefined,
parody: payload.parody || post.parody || undefined,
tags: tags.length > 0 ? tags : post.tags,
num_pages: numPages || post.num_pages,
source: payload.source || post.source || undefined,
cover_url: payload.cover_url || post.cover_url || undefined,
url: sourceUrl || post.url || undefined,
summary: synopsis || post.summary || undefined,
})!
}
await cacheCover(gid)
return Response.json({
success: true,
gid,
post,
})
} catch (err) {
return Response.json({ error: String(err) }, { status: 500 })
}
}