feat: notificacion automatica en Discord via webhook (nuevos mangas)
This commit is contained in:
@@ -11,6 +11,10 @@ WEB_PASSWORD=
|
||||
# Webhook validation
|
||||
WEBHOOK_SECRET=
|
||||
|
||||
# Discord notification (opcional: avisa en canal cuando llega un manga nuevo)
|
||||
# Crear en Discord: canal -> Configuración -> Integraciones -> Webhooks
|
||||
DISCORD_WEBHOOK_URL=
|
||||
|
||||
# DB path
|
||||
DB_PATH=/app/data/worst-scan.db
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import crypto from "crypto"
|
||||
import { createPost, getPostByGid, updatePost } from "@/lib/db"
|
||||
import { cleanTitle, slugify } from "@/lib/slug"
|
||||
import { cacheCover } from "@/lib/cover-cache"
|
||||
import { notifyNewPost } from "@/lib/discord"
|
||||
|
||||
function verifySignature(bodyText: string, signature: string, secret: string): boolean {
|
||||
try {
|
||||
@@ -44,6 +45,7 @@ export async function POST(req: NextRequest) {
|
||||
const slug = slugify(title, gid)
|
||||
|
||||
let post = getPostByGid(gid)
|
||||
let isNew = false
|
||||
if (!post) {
|
||||
post = createPost({
|
||||
gid,
|
||||
@@ -60,6 +62,7 @@ export async function POST(req: NextRequest) {
|
||||
slug,
|
||||
published: 1,
|
||||
})
|
||||
isNew = true
|
||||
} else {
|
||||
post = updatePost(post.id, {
|
||||
title,
|
||||
@@ -77,6 +80,11 @@ export async function POST(req: NextRequest) {
|
||||
|
||||
await cacheCover(gid)
|
||||
|
||||
// Notificar en Discord si es un manga nuevo.
|
||||
if (isNew && post) {
|
||||
await notifyNewPost(post).catch(() => {})
|
||||
}
|
||||
|
||||
return Response.json({
|
||||
success: true,
|
||||
gid,
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { Post } from "./db"
|
||||
|
||||
// Notificación a Discord vía Webhook.
|
||||
// Si DISCORD_WEBHOOK_URL no está configurada, no hace nada (silencioso).
|
||||
const WEBHOOK_URL = process.env.DISCORD_WEBHOOK_URL || ""
|
||||
|
||||
export async function notifyNewPost(post: Post): Promise<boolean> {
|
||||
if (!WEBHOOK_URL) return false
|
||||
|
||||
const description = post.summary && !post.summary.startsWith("**") && post.summary.length > 40
|
||||
? post.summary.slice(0, 200)
|
||||
: `${post.num_pages} páginas${post.artist ? ` por ${post.artist}` : ""}`
|
||||
|
||||
const payload = {
|
||||
content: `📚 **Nuevo manga disponible:** ${post.title}`,
|
||||
embeds: [
|
||||
{
|
||||
title: post.title,
|
||||
url: `https://worstscan.xyz/p/${post.slug}`,
|
||||
description,
|
||||
color: 0x6366f1, // indigo, acorde al tema
|
||||
thumbnail: { url: `https://worstscan.xyz/api/cover/${post.gid}` },
|
||||
footer: { text: "worst-scan fansub" },
|
||||
timestamp: new Date().toISOString(),
|
||||
fields: [
|
||||
...(post.artist ? [{ name: "Artista", value: post.artist, inline: true }] : []),
|
||||
...(post.parody ? [{ name: "Franquicia", value: post.parody, inline: true }] : []),
|
||||
...(post.num_pages > 0 ? [{ name: "Páginas", value: String(post.num_pages), inline: true }] : []),
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(WEBHOOK_URL, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
signal: AbortSignal.timeout(8000),
|
||||
})
|
||||
return res.ok
|
||||
} catch {
|
||||
return false // nunca romper el poller por un webhook caído
|
||||
}
|
||||
}
|
||||
+6
-1
@@ -3,6 +3,7 @@ import { api } from "./api"
|
||||
import { cleanTitle, slugify } from "./slug"
|
||||
import { createPost, getPostByGid, updatePost } from "./db"
|
||||
import { cacheCover } from "./cover-cache"
|
||||
import { notifyNewPost } from "./discord"
|
||||
|
||||
let intervalId: ReturnType<typeof setInterval> | null = null
|
||||
const POLL_INTERVAL_MS = 60_000
|
||||
@@ -74,6 +75,11 @@ export async function pollOnce(): Promise<{ newPosts: number }> {
|
||||
published: 1,
|
||||
})
|
||||
newPosts++
|
||||
// Notificar en Discord cuando aparece un manga nuevo.
|
||||
const created = getPostByGid(gid)
|
||||
if (created) {
|
||||
await notifyNewPost(created).catch(() => {})
|
||||
}
|
||||
} else {
|
||||
updatePost(existing.id, {
|
||||
title: cleanT,
|
||||
@@ -88,7 +94,6 @@ export async function pollOnce(): Promise<{ newPosts: number }> {
|
||||
})
|
||||
}
|
||||
|
||||
const port = process.env.PORT || "3000"
|
||||
await cacheCover(gid)
|
||||
} catch {
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user