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 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
- const finalRow = new ActionRowBuilder().addComponents(
394
- new ButtonBuilder()
395
- .setLabel('📥 Download Drop')
396
- .setStyle(ButtonStyle.Link)
397
- .setURL(permanentUrl)
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 permanentUrl = uploadRes.data.browser_download_url;
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.Link)
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 = [];