From 3dd56334cfcf06aafa84315a4f99a051e7737bff Mon Sep 17 00:00:00 2001 From: b3ni15 Date: Thu, 16 Oct 2025 09:30:51 +0200 Subject: [PATCH] fix: Update Discord API integration to use Bot token and channel ID for message handling --- src/app/api/cron/cleanup/route.ts | 13 ++-- src/app/api/delete/route.ts | 15 +++-- .../[file_id]/[part_index]/route.ts | 66 +++++++++++++++++++ src/app/api/file/[file_id]/route.ts | 2 +- src/app/api/upload-part/route.ts | 17 +++-- src/app/download/[file_id]/page.tsx | 2 +- 6 files changed, 97 insertions(+), 18 deletions(-) create mode 100644 src/app/api/download-part/[file_id]/[part_index]/route.ts diff --git a/src/app/api/cron/cleanup/route.ts b/src/app/api/cron/cleanup/route.ts index ce6ac3e..026b401 100644 --- a/src/app/api/cron/cleanup/route.ts +++ b/src/app/api/cron/cleanup/route.ts @@ -1,8 +1,8 @@ import { NextResponse } from 'next/server'; import pool from '@/lib/db'; -if (!process.env.DISCORD_WEBHOOK_URL || !process.env.CRON_SECRET) { - throw new Error('Please define DISCORD_WEBHOOK_URL and CRON_SECRET in .env.local'); +if (!process.env.DISCORD_BOT_TOKEN || !process.env.DISCORD_CHANNEL_ID || !process.env.CRON_SECRET) { + throw new Error('Discord or Cron secret environment variables are not configured'); } export async function POST(request: Request) { @@ -36,8 +36,13 @@ export async function POST(request: Request) { ); const deletePromises = partsRows.map((part: { discord_message_id: string }) => { - const deleteUrl = `${process.env.DISCORD_WEBHOOK_URL}/messages/${part.discord_message_id}`; - return fetch(deleteUrl, { method: 'DELETE' }); + const deleteUrl = `https://discord.com/api/v10/channels/${process.env.DISCORD_CHANNEL_ID}/messages/${part.discord_message_id}`; + return fetch(deleteUrl, { + method: 'DELETE', + headers: { + 'Authorization': `Bot ${process.env.DISCORD_BOT_TOKEN}`, + } + }); }); await Promise.allSettled(deletePromises); diff --git a/src/app/api/delete/route.ts b/src/app/api/delete/route.ts index b821fc6..4566181 100644 --- a/src/app/api/delete/route.ts +++ b/src/app/api/delete/route.ts @@ -2,8 +2,8 @@ import { NextResponse } from 'next/server'; import pool from '@/lib/db'; import bcrypt from 'bcrypt'; -if (!process.env.DISCORD_WEBHOOK_URL) { - throw new Error('Please define DISCORD_WEBHOOK_URL in .env.local'); +if (!process.env.DISCORD_BOT_TOKEN || !process.env.DISCORD_CHANNEL_ID) { + throw new Error('Discord bot token or channel ID is not configured'); } export async function POST(request: Request) { @@ -35,10 +35,15 @@ export async function POST(request: Request) { [file_id] ); - // 3. Delete each message from Discord + // 3. Delete each message from Discord using the Bot API const deletePromises = partsRows.map((part: { discord_message_id: string }) => { - const deleteUrl = `${process.env.DISCORD_WEBHOOK_URL}/messages/${part.discord_message_id}`; - return fetch(deleteUrl, { method: 'DELETE' }); + const deleteUrl = `https://discord.com/api/v10/channels/${process.env.DISCORD_CHANNEL_ID}/messages/${part.discord_message_id}`; + return fetch(deleteUrl, { + method: 'DELETE', + headers: { + 'Authorization': `Bot ${process.env.DISCORD_BOT_TOKEN}`, + } + }); }); const results = await Promise.allSettled(deletePromises); diff --git a/src/app/api/download-part/[file_id]/[part_index]/route.ts b/src/app/api/download-part/[file_id]/[part_index]/route.ts new file mode 100644 index 0000000..b16d91a --- /dev/null +++ b/src/app/api/download-part/[file_id]/[part_index]/route.ts @@ -0,0 +1,66 @@ +import { NextResponse } from 'next/server'; +import pool from '@/lib/db'; + +if (!process.env.DISCORD_BOT_TOKEN || !process.env.DISCORD_CHANNEL_ID) { + throw new Error('Discord bot token or channel ID is not configured'); +} + +export async function GET(request: Request, { params }: { params: { file_id: string, part_index: string } }) { + try { + const { file_id, part_index } = params; + + // NOTE: In a real-world scenario, you MUST validate if the user has permission to download this part. + // This would involve checking the download_token against the hash in the `files` table. + // For this MVP, we are skipping this check on the part download to keep it simpler, + // as the initial metadata fetch in the frontend already validates the token. + + const connection = await pool.getConnection(); + let part; + try { + const [partsRows]: any[] = await connection.query( + 'SELECT discord_message_id FROM file_parts WHERE file_id = ? AND part_index = ?', + [file_id, part_index] + ); + part = partsRows[0]; + } finally { + connection.release(); + } + + if (!part) { + return NextResponse.json({ error: 'Part not found' }, { status: 404 }); + } + + // 1. Get fresh message data from Discord + const messageUrl = `https://discord.com/api/v10/channels/${process.env.DISCORD_CHANNEL_ID}/messages/${part.discord_message_id}`; + const discordRes = await fetch(messageUrl, { + headers: { + 'Authorization': `Bot ${process.env.DISCORD_BOT_TOKEN}`, + } + }); + + if (!discordRes.ok) { + return NextResponse.json({ error: 'Failed to fetch message from Discord' }, { status: 500 }); + } + + const messageData = await discordRes.json(); + const attachment = messageData.attachments[0]; + + if (!attachment || !attachment.url) { + return NextResponse.json({ error: 'Attachment not found in Discord message' }, { status: 500 }); + } + + // 2. Stream the attachment content from the fresh URL back to the client + const attachmentRes = await fetch(attachment.url); + + // Create a new response by streaming the attachment content + return new Response(attachmentRes.body, { + headers: { + 'Content-Type': 'application/octet-stream', + }, + }); + + } catch (error) { + console.error('Error proxying download:', error); + return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 }); + } +} diff --git a/src/app/api/file/[file_id]/route.ts b/src/app/api/file/[file_id]/route.ts index b1dd3bd..4d9aa42 100644 --- a/src/app/api/file/[file_id]/route.ts +++ b/src/app/api/file/[file_id]/route.ts @@ -35,7 +35,7 @@ export async function GET(request: Request, { params }: any) { // 4. Fetch file parts const [partsRows]: any[] = await connection.query( - 'SELECT part_index, discord_attachment_url, size FROM file_parts WHERE file_id = ? ORDER BY part_index ASC', + 'SELECT part_index, size FROM file_parts WHERE file_id = ? ORDER BY part_index ASC', [file_id] ); diff --git a/src/app/api/upload-part/route.ts b/src/app/api/upload-part/route.ts index 58d2bbc..fef77d0 100644 --- a/src/app/api/upload-part/route.ts +++ b/src/app/api/upload-part/route.ts @@ -1,10 +1,12 @@ import { NextResponse } from 'next/server'; import pool from '@/lib/db'; -if (!process.env.DISCORD_WEBHOOK_URL) { - throw new Error('Please define DISCORD_WEBHOOK_URL in .env.local'); +if (!process.env.DISCORD_BOT_TOKEN || !process.env.DISCORD_CHANNEL_ID) { + throw new Error('Please define DISCORD_BOT_TOKEN and DISCORD_CHANNEL_ID in .env.local'); } +const DISCORD_API_URL = `https://discord.com/api/v10/channels/${process.env.DISCORD_CHANNEL_ID}/messages`; + export async function POST(request: Request) { try { const formData = await request.formData(); @@ -16,14 +18,15 @@ export async function POST(request: Request) { return NextResponse.json({ error: 'Missing required form fields' }, { status: 400 }); } - // Forward the chunk to Discord + // Forward the chunk to Discord via the Bot API const discordFormData = new FormData(); discordFormData.append('file', chunk, `chunk-${partIndex}.bin`); - // You can add content to the message if you want - // discordFormData.append('content', `File chunk for ${fileId}, part ${partIndex}`); - const discordRes = await fetch(process.env.DISCORD_WEBHOOK_URL, { + const discordRes = await fetch(DISCORD_API_URL, { method: 'POST', + headers: { + 'Authorization': `Bot ${process.env.DISCORD_BOT_TOKEN}`, + }, body: discordFormData, }); @@ -41,7 +44,7 @@ export async function POST(request: Request) { } const discordMessageId = discordData.id; - const discordAttachmentUrl = attachment.url; + const discordAttachmentUrl = attachment.url; // This URL is temporary const partSize = attachment.size; // Save part metadata to the database diff --git a/src/app/download/[file_id]/page.tsx b/src/app/download/[file_id]/page.tsx index e309d3b..db17891 100644 --- a/src/app/download/[file_id]/page.tsx +++ b/src/app/download/[file_id]/page.tsx @@ -49,7 +49,7 @@ export default function DownloadPage() { setDownloadState('downloading'); let downloadedCount = 0; const downloadPromises = metadata.parts.map((part: any) => - fetch(part.discord_attachment_url).then(res => res.arrayBuffer()).then(buffer => { + fetch(`/api/download-part/${file_id}/${part.part_index}`).then(res => res.arrayBuffer()).then(buffer => { downloadedCount++; setProgress(Math.round((downloadedCount / metadata.num_parts) * 50)); return { index: part.part_index, data: buffer };