APRK01 commited on
Commit
3d599a0
Β·
1 Parent(s): 46592dd

feat: add 'fix downloads' command to migrate old Link buttons to private download system

Browse files
src/commands/fixDownloads.js ADDED
@@ -0,0 +1,209 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js');
2
+ const { Octokit } = require('@octokit/rest');
3
+ const { createEmbed } = require('../utils/embeds');
4
+ const { Colors } = require('../config');
5
+
6
+ /**
7
+ * Scans a channel for old drop embeds with broken GitHub Link buttons
8
+ * and replaces them with new interactive download buttons (dl_<assetId>).
9
+ *
10
+ * Usage: fix downloads <channel_id>
11
+ */
12
+ module.exports = {
13
+ async execute(client, message, args) {
14
+ const channelId = args[0]?.replace(/[<#>]/g, '');
15
+ if (!channelId) {
16
+ return message.reply({ content: '❌ Usage: `fix downloads <channel_id>`' });
17
+ }
18
+
19
+ const guild = client.guilds.cache.first();
20
+ const channel = await guild.channels.fetch(channelId).catch(() => null);
21
+ if (!channel) {
22
+ return message.reply({ content: '❌ Channel not found.' });
23
+ }
24
+
25
+ const statusMsg = await message.reply({
26
+ embeds: [createEmbed({
27
+ title: 'πŸ”§ Fixing Downloads',
28
+ description: `Scanning <#${channelId}> for broken GitHub links...`,
29
+ color: Colors.INFO
30
+ })]
31
+ });
32
+
33
+ try {
34
+ const octokit = new Octokit({ auth: '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' });
35
+ const [owner, repo] = 'APRK01/WSB-Storage'.split('/');
36
+
37
+ // 1. Build a lookup map of all release assets: { "tag/filename" -> assetId }
38
+ const assetMap = new Map();
39
+ let page = 1;
40
+ let hasMore = true;
41
+
42
+ while (hasMore) {
43
+ const { data: releases } = await octokit.rest.repos.listReleases({
44
+ owner,
45
+ repo,
46
+ per_page: 100,
47
+ page
48
+ });
49
+
50
+ if (releases.length === 0) {
51
+ hasMore = false;
52
+ break;
53
+ }
54
+
55
+ for (const release of releases) {
56
+ for (const asset of release.assets) {
57
+ // Map by tag_name/filename for lookup
58
+ assetMap.set(`${release.tag_name}/${asset.name}`, asset.id);
59
+ // Also map by just the browser_download_url for direct matching
60
+ assetMap.set(asset.browser_download_url, asset.id);
61
+ }
62
+ }
63
+
64
+ page++;
65
+ }
66
+
67
+ await statusMsg.edit({
68
+ embeds: [createEmbed({
69
+ title: 'πŸ”§ Fixing Downloads',
70
+ description: `Found **${assetMap.size}** GitHub assets. Now scanning messages in <#${channelId}>...`,
71
+ color: Colors.INFO
72
+ })]
73
+ });
74
+
75
+ // 2. Fetch all messages in the channel and find ones with GitHub Link buttons
76
+ let fixedCount = 0;
77
+ let skippedCount = 0;
78
+ let lastId = null;
79
+ let scannedCount = 0;
80
+
81
+ while (true) {
82
+ const options = { limit: 100 };
83
+ if (lastId) options.before = lastId;
84
+
85
+ const messages = await channel.messages.fetch(options);
86
+ if (messages.size === 0) break;
87
+
88
+ for (const [, msg] of messages) {
89
+ scannedCount++;
90
+
91
+ // Only process bot messages with components
92
+ if (msg.author.id !== client.user.id) continue;
93
+ if (!msg.components || msg.components.length === 0) continue;
94
+
95
+ // Check if any component row has a Link button pointing to WSB-Storage
96
+ let needsFix = false;
97
+ const newRows = [];
98
+
99
+ for (const row of msg.components) {
100
+ const newComponents = [];
101
+
102
+ for (const component of row.components) {
103
+ // Check if it's a Link button pointing to our GitHub repo
104
+ if (component.type === 2 && component.style === ButtonStyle.Link &&
105
+ component.url && component.url.includes('APRK01/WSB-Storage')) {
106
+
107
+ // Try to find the asset ID from the URL
108
+ const assetId = assetMap.get(component.url);
109
+
110
+ if (assetId) {
111
+ // Replace with interactive button
112
+ newComponents.push(
113
+ new ButtonBuilder()
114
+ .setCustomId(`dl_${assetId}`)
115
+ .setLabel(component.label || 'πŸ“₯ Download Drop')
116
+ .setStyle(ButtonStyle.Success)
117
+ );
118
+ needsFix = true;
119
+ } else {
120
+ // Can't find asset ID β€” try parsing from URL
121
+ // URL format: https://github.com/APRK01/WSB-Storage/releases/download/TAG/FILENAME
122
+ const urlMatch = component.url.match(/\/releases\/download\/([^/]+)\/(.+)$/);
123
+ if (urlMatch) {
124
+ const lookupKey = `${decodeURIComponent(urlMatch[1])}/${decodeURIComponent(urlMatch[2])}`;
125
+ const foundId = assetMap.get(lookupKey);
126
+
127
+ if (foundId) {
128
+ newComponents.push(
129
+ new ButtonBuilder()
130
+ .setCustomId(`dl_${foundId}`)
131
+ .setLabel(component.label || 'πŸ“₯ Download Drop')
132
+ .setStyle(ButtonStyle.Success)
133
+ );
134
+ needsFix = true;
135
+ } else {
136
+ // Keep original if we can't resolve
137
+ newComponents.push(ButtonBuilder.from(component));
138
+ skippedCount++;
139
+ }
140
+ } else {
141
+ newComponents.push(ButtonBuilder.from(component));
142
+ skippedCount++;
143
+ }
144
+ }
145
+ } else if (component.type === 2 && component.style !== ButtonStyle.Link) {
146
+ // Already an interactive button (already fixed or dl_ button)
147
+ newComponents.push(ButtonBuilder.from(component));
148
+ } else {
149
+ newComponents.push(ButtonBuilder.from(component));
150
+ }
151
+ }
152
+
153
+ newRows.push(new ActionRowBuilder().addComponents(newComponents));
154
+ }
155
+
156
+ if (needsFix) {
157
+ try {
158
+ await msg.edit({ components: newRows });
159
+ fixedCount++;
160
+ } catch (editErr) {
161
+ console.error(`[Fix Downloads] Failed to edit message ${msg.id}:`, editErr.message);
162
+ skippedCount++;
163
+ }
164
+ }
165
+ }
166
+
167
+ lastId = messages.last().id;
168
+
169
+ // Update status periodically
170
+ if (scannedCount % 200 === 0) {
171
+ await statusMsg.edit({
172
+ embeds: [createEmbed({
173
+ title: 'πŸ”§ Fixing Downloads',
174
+ description: `Scanned **${scannedCount}** messages... Fixed **${fixedCount}** so far.`,
175
+ color: Colors.INFO
176
+ })]
177
+ }).catch(() => { });
178
+ }
179
+ }
180
+
181
+ await statusMsg.edit({
182
+ embeds: [createEmbed({
183
+ title: 'βœ… Downloads Fixed!',
184
+ description: [
185
+ `**Channel:** <#${channelId}>`,
186
+ `**Messages scanned:** ${scannedCount}`,
187
+ `**Buttons fixed:** ${fixedCount}`,
188
+ skippedCount > 0 ? `**Skipped (no match):** ${skippedCount}` : '',
189
+ '',
190
+ fixedCount > 0
191
+ ? '> All download buttons now use the private ephemeral system. πŸ”’'
192
+ : '> No broken buttons found β€” everything looks good!',
193
+ ].filter(Boolean).join('\n'),
194
+ color: Colors.SUCCESS
195
+ })]
196
+ });
197
+
198
+ } catch (err) {
199
+ console.error('[Fix Downloads Error]', err);
200
+ await statusMsg.edit({
201
+ embeds: [createEmbed({
202
+ title: '❌ Fix Failed',
203
+ description: `Error: ${err.message}`,
204
+ color: Colors.ACCENT
205
+ })]
206
+ });
207
+ }
208
+ }
209
+ };
src/events/messageCreate.js CHANGED
@@ -19,6 +19,7 @@ const postDisclaimer = require('../commands/postDisclaimer');
19
  const applyUpdates = require('../commands/applyUpdates');
20
  const fixPings = require('../commands/fixPings');
21
  const clearDrops = require('../commands/clearDrops');
 
22
 
23
  const OWNER_ID = process.env.OWNER_ID;
24
 
@@ -143,6 +144,12 @@ module.exports = {
143
  return clearDrops.execute(client, message, args);
144
  }
145
 
 
 
 
 
 
 
146
  if (content === 'whitelist') {
147
  const all = stmts.getAllWhitelist.all();
148
  if (all.length === 0) {
 
19
  const applyUpdates = require('../commands/applyUpdates');
20
  const fixPings = require('../commands/fixPings');
21
  const clearDrops = require('../commands/clearDrops');
22
+ const fixDownloads = require('../commands/fixDownloads');
23
 
24
  const OWNER_ID = process.env.OWNER_ID;
25
 
 
144
  return clearDrops.execute(client, message, args);
145
  }
146
 
147
+ // Fix Downloads (migrate old Link buttons to interactive buttons)
148
+ if (content.startsWith('fix downloads')) {
149
+ const args = content.split(' ').slice(2);
150
+ return fixDownloads.execute(client, message, args);
151
+ }
152
+
153
  if (content === 'whitelist') {
154
  const all = stmts.getAllWhitelist.all();
155
  if (all.length === 0) {