diff --git a/app/api/webhooks/tiktok/route.ts b/app/api/webhooks/tiktok/route.ts new file mode 100644 index 0000000..c9e094b --- /dev/null +++ b/app/api/webhooks/tiktok/route.ts @@ -0,0 +1,162 @@ +import { NextResponse } from "next/server"; +import { getTiktokProfile } from "@/lib/followers/tiktok"; + +export async function POST(req: Request) { + try { + if (process.env.TIKTOK_WEBHOOK_SECRET) { + const { searchParams } = new URL(req.url); + if (searchParams.get("secret") !== process.env.TIKTOK_WEBHOOK_SECRET) { + console.error("[TikTok Webhook] Unauthorized request: invalid or missing secret query parameter."); + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + } + + // Read the body as text first in case it's not JSON or for signature verification if needed + const rawBody = await req.text(); + let body: any = {}; + + if (rawBody) { + try { + body = JSON.parse(rawBody); + } catch (e) { + console.error("[TikTok Webhook] Failed to parse JSON:", rawBody); + } + } + + console.log("[TikTok Webhook] Received payload:", JSON.stringify(body, null, 2)); + + const event = body.event || body.type || body.Event; + const data = body.data || body.Data || body; + + let message = ""; + let embed: any = null; + + // Fetch the user's official TikTok profile to use for author name & avatar + let officialAuthorName = "TikTok"; + let officialAvatarUrl: string | undefined = undefined; + try { + const profile = await getTiktokProfile(); + if (profile.displayName) officialAuthorName = profile.displayName; + if (profile.avatarUrl) officialAvatarUrl = profile.avatarUrl; + } catch (e) { + console.error("[TikTok Webhook] Failed to fetch profile:", e); + } + + // Check if it's a video upload or live event + if (typeof event === "string") { + const eventLower = event.toLowerCase(); + if (eventLower.includes("video") && (eventLower.includes("upload") || eventLower.includes("publish"))) { + const link = data.share_url || data.url || data.video_url || data.link || ""; + + let title = data.title || data.video_title || data.desc || "คลิปใหม่จาก TikTok"; + let thumbnailUrl = data.cover_url || data.thumbnail_url || ""; + + // Default to official profile info + let authorName = officialAuthorName; + let authorAvatar = officialAvatarUrl || ""; + + if (link && link.includes("tiktok.com")) { + try { + // Using tikwm API for video thumbnail and title + const oembedRes = await fetch(`https://www.tikwm.com/api/?url=${encodeURIComponent(link)}`); + if (oembedRes.ok) { + const oembedData = await oembedRes.json(); + if (oembedData.data) { + if (oembedData.data.title) title = oembedData.data.title; + if (oembedData.data.cover) thumbnailUrl = oembedData.data.cover; + // If the official API failed for some reason, fallback to tikwm author info + if (officialAuthorName === "TikTok" && oembedData.data.author?.nickname) authorName = oembedData.data.author.nickname; + if (!officialAvatarUrl && oembedData.data.author?.avatar) authorAvatar = oembedData.data.author.avatar; + } + } + } catch (e) { + console.error("[TikTok Webhook] Failed to fetch video info:", e); + } + } + + message = `ลงคลิปใหม่แล้วววสสสสส ${link}`; + embed = { + title: title, + url: link || undefined, + color: 0xff0050, + author: { + name: authorName, + url: link || "https://www.tiktok.com/", + icon_url: authorAvatar || undefined + }, + image: thumbnailUrl ? { url: thumbnailUrl } : undefined + }; + } else if (eventLower.includes("live")) { + const link = data.share_url || data.url || data.room_url || data.link || ""; + const title = data.title || data.live_title || data.desc || ""; + + message = `${link}`; + embed = { + title: title, + url: link || undefined, + color: 0xff0050, + author: { + name: officialAuthorName, + url: "https://www.tiktok.com/", + icon_url: officialAvatarUrl || undefined + }, + thumbnail: officialAvatarUrl ? { url: officialAvatarUrl } : undefined + }; + } + } else { + // Fallback: search payload keys for indicators + const payloadStr = JSON.stringify(body).toLowerCase(); + if (payloadStr.includes("video") && (payloadStr.includes("upload") || payloadStr.includes("publish"))) { + message = `คลิปใหม่!`; + } else if (payloadStr.includes("live")) { + message = `Live!`; + } + } + + if (message) { + await sendDiscordNotification(message, embed); + } + + return NextResponse.json({ success: true }, { status: 200 }); + } catch (error) { + console.error("[TikTok Webhook] Error processing request:", error); + return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); + } +} + +async function sendDiscordNotification(content: string, embed?: any) { + const token = process.env.DISCORD_BOT_TOKEN; + const channelId = process.env.DISCORD_CHANNEL_ID; + + if (!token) { + console.error("[TikTok Webhook] Missing DISCORD_BOT_TOKEN"); + return; + } + + if (!channelId) { + console.error("[TikTok Webhook] Missing DISCORD_CHANNEL_ID environment variable. Please add it to your .env file."); + return; + } + + try { + const res = await fetch(`https://discord.com/api/v10/channels/${channelId}/messages`, { + method: "POST", + headers: { + "Authorization": `Bot ${token}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + content, + embeds: embed ? [embed] : undefined + }) + }); + + if (!res.ok) { + console.error("[TikTok Webhook] Failed to send Discord message:", await res.text()); + } else { + console.log("[TikTok Webhook] Successfully sent Discord message"); + } + } catch (error) { + console.error("[TikTok Webhook] Error sending Discord message:", error); + } +}