APRK01 commited on
Commit ·
46592dd
1
Parent(s): 7822b5f
feat: private download system — interactive buttons with ephemeral temp URLs
Browse files- src/events/interactionCreate.js +7 -1
- src/systems/drops.js +87 -8
- src/systems/massdrop.js +4 -4
src/events/interactionCreate.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
const { handleTicketButton } = require('../systems/tickets');
|
| 2 |
-
const { handleDropButton } = require('../systems/drops');
|
| 3 |
const { handleMassDropInteraction } = require('../systems/massdrop');
|
| 4 |
|
| 5 |
module.exports = {
|
|
@@ -8,6 +8,12 @@ module.exports = {
|
|
| 8 |
// Handle select menus and modals as well
|
| 9 |
if (!interaction.isButton() && !interaction.isStringSelectMenu() && !interaction.isModalSubmit()) return;
|
| 10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
// Try mass drop interactions (buttons, dropdowns, modals)
|
| 12 |
const massDropHandled = await handleMassDropInteraction(interaction);
|
| 13 |
if (massDropHandled) return;
|
|
|
|
| 1 |
const { handleTicketButton } = require('../systems/tickets');
|
| 2 |
+
const { handleDropButton, handleDownloadButton } = require('../systems/drops');
|
| 3 |
const { handleMassDropInteraction } = require('../systems/massdrop');
|
| 4 |
|
| 5 |
module.exports = {
|
|
|
|
| 8 |
// Handle select menus and modals as well
|
| 9 |
if (!interaction.isButton() && !interaction.isStringSelectMenu() && !interaction.isModalSubmit()) return;
|
| 10 |
|
| 11 |
+
// Handle download buttons from server channels (dl_<assetId>)
|
| 12 |
+
if (interaction.isButton() && interaction.customId.startsWith('dl_')) {
|
| 13 |
+
await handleDownloadButton(interaction);
|
| 14 |
+
return;
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
// Try mass drop interactions (buttons, dropdowns, modals)
|
| 18 |
const massDropHandled = await handleMassDropInteraction(interaction);
|
| 19 |
if (massDropHandled) return;
|
src/systems/drops.js
CHANGED
|
@@ -341,6 +341,7 @@ async function handleDropMessage(message) {
|
|
| 341 |
// Update: Only use GitHub proxy for internal attachments.
|
| 342 |
// External links (Mega, MediaFire) are posted directly.
|
| 343 |
let permanentUrl = session.file.url;
|
|
|
|
| 344 |
|
| 345 |
if (!session.file.isExternal) {
|
| 346 |
const processingMsg = await message.reply({ content: '⏳ *Uploading file to permanent GitHub proxy storage...*' });
|
|
@@ -375,6 +376,7 @@ async function handleDropMessage(message) {
|
|
| 375 |
}
|
| 376 |
});
|
| 377 |
|
|
|
|
| 378 |
permanentUrl = uploadRes.data.browser_download_url;
|
| 379 |
await processingMsg.edit({ content: '✅ *Successfully proxied file to GitHub permanent storage.*' });
|
| 380 |
} catch (githubErr) {
|
|
@@ -390,12 +392,24 @@ async function handleDropMessage(message) {
|
|
| 390 |
image: imageUrl ? { url: imageUrl } : null
|
| 391 |
});
|
| 392 |
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 399 |
|
| 400 |
await channel.send({
|
| 401 |
embeds: [finalEmbed],
|
|
@@ -403,8 +417,6 @@ async function handleDropMessage(message) {
|
|
| 403 |
files: filesToUpload // Pass the preview image (if any)
|
| 404 |
});
|
| 405 |
|
| 406 |
-
await processingMsg.edit({ content: '✅ *Successfully proxied file to GitHub permanent storage.*' });
|
| 407 |
-
|
| 408 |
} catch (githubErr) {
|
| 409 |
console.error('[GitHub Upload Error]', githubErr);
|
| 410 |
await processingMsg.edit({ content: '❌ *Failed to proxy storage to GitHub. The drop was cancelled.*' });
|
|
@@ -483,6 +495,72 @@ async function handleDropButton(interaction) {
|
|
| 483 |
return false;
|
| 484 |
}
|
| 485 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 486 |
/**
|
| 487 |
* Format bytes to human readable string.
|
| 488 |
*/
|
|
@@ -500,6 +578,7 @@ module.exports = {
|
|
| 500 |
getPrompt,
|
| 501 |
handleDropMessage,
|
| 502 |
handleDropButton,
|
|
|
|
| 503 |
buildDropEmbed,
|
| 504 |
canDrop,
|
| 505 |
};
|
|
|
|
| 341 |
// Update: Only use GitHub proxy for internal attachments.
|
| 342 |
// External links (Mega, MediaFire) are posted directly.
|
| 343 |
let permanentUrl = session.file.url;
|
| 344 |
+
let assetId = null;
|
| 345 |
|
| 346 |
if (!session.file.isExternal) {
|
| 347 |
const processingMsg = await message.reply({ content: '⏳ *Uploading file to permanent GitHub proxy storage...*' });
|
|
|
|
| 376 |
}
|
| 377 |
});
|
| 378 |
|
| 379 |
+
assetId = uploadRes.data.id;
|
| 380 |
permanentUrl = uploadRes.data.browser_download_url;
|
| 381 |
await processingMsg.edit({ content: '✅ *Successfully proxied file to GitHub permanent storage.*' });
|
| 382 |
} catch (githubErr) {
|
|
|
|
| 392 |
image: imageUrl ? { url: imageUrl } : null
|
| 393 |
});
|
| 394 |
|
| 395 |
+
let finalRow;
|
| 396 |
+
if (session.file.isExternal) {
|
| 397 |
+
// External links use direct Link buttons (publicly accessible)
|
| 398 |
+
finalRow = new ActionRowBuilder().addComponents(
|
| 399 |
+
new ButtonBuilder()
|
| 400 |
+
.setLabel('📥 Download Drop')
|
| 401 |
+
.setStyle(ButtonStyle.Link)
|
| 402 |
+
.setURL(permanentUrl)
|
| 403 |
+
);
|
| 404 |
+
} else {
|
| 405 |
+
// Private GitHub assets use interactive buttons
|
| 406 |
+
finalRow = new ActionRowBuilder().addComponents(
|
| 407 |
+
new ButtonBuilder()
|
| 408 |
+
.setCustomId(`dl_${assetId}`)
|
| 409 |
+
.setLabel('📥 Download Drop')
|
| 410 |
+
.setStyle(ButtonStyle.Success)
|
| 411 |
+
);
|
| 412 |
+
}
|
| 413 |
|
| 414 |
await channel.send({
|
| 415 |
embeds: [finalEmbed],
|
|
|
|
| 417 |
files: filesToUpload // Pass the preview image (if any)
|
| 418 |
});
|
| 419 |
|
|
|
|
|
|
|
| 420 |
} catch (githubErr) {
|
| 421 |
console.error('[GitHub Upload Error]', githubErr);
|
| 422 |
await processingMsg.edit({ content: '❌ *Failed to proxy storage to GitHub. The drop was cancelled.*' });
|
|
|
|
| 495 |
return false;
|
| 496 |
}
|
| 497 |
|
| 498 |
+
/**
|
| 499 |
+
* Handle download button clicks — generates a temporary download URL from the private GitHub repo.
|
| 500 |
+
* Button customId format: dl_<assetId>
|
| 501 |
+
*/
|
| 502 |
+
async function handleDownloadButton(interaction) {
|
| 503 |
+
const { customId } = interaction;
|
| 504 |
+
if (!customId.startsWith('dl_')) return false;
|
| 505 |
+
|
| 506 |
+
const assetId = customId.split('_')[1];
|
| 507 |
+
if (!assetId) return false;
|
| 508 |
+
|
| 509 |
+
try {
|
| 510 |
+
await interaction.deferReply({ ephemeral: true });
|
| 511 |
+
|
| 512 |
+
const GITHUB_TOKEN = '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';
|
| 513 |
+
const [owner, repo] = 'APRK01/WSB-Storage'.split('/');
|
| 514 |
+
|
| 515 |
+
// Request the asset with octet-stream accept header — GitHub returns a 302 redirect
|
| 516 |
+
// to a temporary signed S3 URL that works without authentication
|
| 517 |
+
const apiUrl = `https://api.github.com/repos/${owner}/${repo}/releases/assets/${assetId}`;
|
| 518 |
+
const response = await fetch(apiUrl, {
|
| 519 |
+
headers: {
|
| 520 |
+
'Authorization': `Bearer ${GITHUB_TOKEN}`,
|
| 521 |
+
'Accept': 'application/octet-stream',
|
| 522 |
+
'User-Agent': 'WSB-Bot'
|
| 523 |
+
},
|
| 524 |
+
redirect: 'manual'
|
| 525 |
+
});
|
| 526 |
+
|
| 527 |
+
if (response.status === 302) {
|
| 528 |
+
const tempUrl = response.headers.get('location');
|
| 529 |
+
await interaction.editReply({
|
| 530 |
+
content: `📥 **Your private download link:**\n${tempUrl}\n\n> ⚠️ This link expires in a few minutes. Do **not** share it.`,
|
| 531 |
+
});
|
| 532 |
+
} else {
|
| 533 |
+
// Fallback: try following the redirect automatically
|
| 534 |
+
const directResponse = await fetch(apiUrl, {
|
| 535 |
+
headers: {
|
| 536 |
+
'Authorization': `Bearer ${GITHUB_TOKEN}`,
|
| 537 |
+
'Accept': 'application/octet-stream',
|
| 538 |
+
'User-Agent': 'WSB-Bot'
|
| 539 |
+
}
|
| 540 |
+
});
|
| 541 |
+
if (directResponse.ok) {
|
| 542 |
+
// If the URL resolved, the response URL is the temporary link
|
| 543 |
+
await interaction.editReply({
|
| 544 |
+
content: `📥 **Your private download link:**\n${directResponse.url}\n\n> ⚠️ This link expires in a few minutes. Do **not** share it.`,
|
| 545 |
+
});
|
| 546 |
+
} else {
|
| 547 |
+
await interaction.editReply({
|
| 548 |
+
content: '❌ Failed to generate download link. The file may have been deleted.',
|
| 549 |
+
});
|
| 550 |
+
}
|
| 551 |
+
}
|
| 552 |
+
return true;
|
| 553 |
+
} catch (err) {
|
| 554 |
+
console.error('[Download Button Error]', err);
|
| 555 |
+
try {
|
| 556 |
+
await interaction.editReply({
|
| 557 |
+
content: '❌ Something went wrong generating your download link. Please try again.',
|
| 558 |
+
});
|
| 559 |
+
} catch (_) { }
|
| 560 |
+
return true;
|
| 561 |
+
}
|
| 562 |
+
}
|
| 563 |
+
|
| 564 |
/**
|
| 565 |
* Format bytes to human readable string.
|
| 566 |
*/
|
|
|
|
| 578 |
getPrompt,
|
| 579 |
handleDropMessage,
|
| 580 |
handleDropButton,
|
| 581 |
+
handleDownloadButton,
|
| 582 |
buildDropEmbed,
|
| 583 |
canDrop,
|
| 584 |
};
|
src/systems/massdrop.js
CHANGED
|
@@ -304,8 +304,8 @@ async function handleMassDropMessage(message) {
|
|
| 304 |
}
|
| 305 |
});
|
| 306 |
|
| 307 |
-
// 4. Dispatch Embed to Channel
|
| 308 |
-
const
|
| 309 |
|
| 310 |
const finalEmbed = buildDropEmbed({
|
| 311 |
title: fileConf.title,
|
|
@@ -317,9 +317,9 @@ async function handleMassDropMessage(message) {
|
|
| 317 |
|
| 318 |
const finalRow = new ActionRowBuilder().addComponents(
|
| 319 |
new ButtonBuilder()
|
|
|
|
| 320 |
.setLabel('📥 Download Drop')
|
| 321 |
-
.setStyle(ButtonStyle.
|
| 322 |
-
.setURL(permanentUrl)
|
| 323 |
);
|
| 324 |
|
| 325 |
const filesToUpload = [];
|
|
|
|
| 304 |
}
|
| 305 |
});
|
| 306 |
|
| 307 |
+
// 4. Dispatch Embed to Channel — use interactive button for private downloads
|
| 308 |
+
const assetId = uploadRes.data.id;
|
| 309 |
|
| 310 |
const finalEmbed = buildDropEmbed({
|
| 311 |
title: fileConf.title,
|
|
|
|
| 317 |
|
| 318 |
const finalRow = new ActionRowBuilder().addComponents(
|
| 319 |
new ButtonBuilder()
|
| 320 |
+
.setCustomId(`dl_${assetId}`)
|
| 321 |
.setLabel('📥 Download Drop')
|
| 322 |
+
.setStyle(ButtonStyle.Success)
|
|
|
|
| 323 |
);
|
| 324 |
|
| 325 |
const filesToUpload = [];
|