APRK01 commited on
Commit
6ec7384
Β·
1 Parent(s): 90fc18c

feat: limit whitelisted drops to 3/day and export drop methods

Browse files
src/database.js CHANGED
@@ -34,6 +34,20 @@ db.exec(`
34
  key TEXT PRIMARY KEY,
35
  value TEXT NOT NULL
36
  );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  `);
38
 
39
  // ── Prepared Statements ───────────────────────────────────────
@@ -60,6 +74,17 @@ const stmts = {
60
  setState: db.prepare('INSERT OR REPLACE INTO bot_state (key, value) VALUES (?, ?)'),
61
  getState: db.prepare('SELECT value FROM bot_state WHERE key = ?'),
62
  delState: db.prepare('DELETE FROM bot_state WHERE key = ?'),
 
 
 
 
 
 
 
 
 
 
 
63
  };
64
 
65
  module.exports = { db, stmts };
 
34
  key TEXT PRIMARY KEY,
35
  value TEXT NOT NULL
36
  );
37
+
38
+ CREATE TABLE IF NOT EXISTS whitelist (
39
+ user_id TEXT PRIMARY KEY,
40
+ added_by TEXT NOT NULL,
41
+ added_at DATETIME DEFAULT CURRENT_TIMESTAMP
42
+ );
43
+
44
+ CREATE TABLE IF NOT EXISTS drop_log (
45
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
46
+ user_id TEXT NOT NULL,
47
+ title TEXT NOT NULL,
48
+ channel_id TEXT NOT NULL,
49
+ dropped_at DATETIME DEFAULT CURRENT_TIMESTAMP
50
+ );
51
  `);
52
 
53
  // ── Prepared Statements ───────────────────────────────────────
 
74
  setState: db.prepare('INSERT OR REPLACE INTO bot_state (key, value) VALUES (?, ?)'),
75
  getState: db.prepare('SELECT value FROM bot_state WHERE key = ?'),
76
  delState: db.prepare('DELETE FROM bot_state WHERE key = ?'),
77
+
78
+ // Whitelist
79
+ addWhitelist: db.prepare('INSERT OR REPLACE INTO whitelist (user_id, added_by) VALUES (?, ?)'),
80
+ removeWhitelist: db.prepare('DELETE FROM whitelist WHERE user_id = ?'),
81
+ getWhitelist: db.prepare('SELECT * FROM whitelist WHERE user_id = ?'),
82
+ getAllWhitelist: db.prepare('SELECT * FROM whitelist'),
83
+
84
+ // Drop log
85
+ logDrop: db.prepare('INSERT INTO drop_log (user_id, title, channel_id) VALUES (?, ?, ?)'),
86
+ getDropCount24h: db.prepare(`SELECT COUNT(*) as count FROM drop_log WHERE user_id = ? AND dropped_at > datetime('now', '-24 hours')`),
87
+ getLastDrop: db.prepare(`SELECT dropped_at FROM drop_log WHERE user_id = ? ORDER BY dropped_at DESC LIMIT 1`),
88
  };
89
 
90
  module.exports = { db, stmts };
src/events/messageCreate.js CHANGED
@@ -1,7 +1,9 @@
1
  const { ChannelType } = require('discord.js');
2
- const { errorEmbed, infoEmbed } = require('../utils/embeds');
3
  const { logCommand } = require('../systems/logger');
4
- const { startDropSession, hasSession, getPrompt, handleDropMessage } = require('../systems/drops');
 
 
5
 
6
  // Import command handlers
7
  const setup = require('../commands/setup');
@@ -17,7 +19,7 @@ const applyUpdates = require('../commands/applyUpdates');
17
 
18
  const OWNER_ID = process.env.OWNER_ID;
19
 
20
- // Command registry
21
  const commands = {
22
  'setup server': setup,
23
  'create roles': createRoles,
@@ -34,15 +36,14 @@ const commands = {
34
  module.exports = {
35
  name: 'messageCreate',
36
  async execute(client, message) {
37
- // ── SECURITY: Only accept DMs from the Owner ──
38
  if (message.author.bot) return;
39
  if (message.channel.type !== ChannelType.DM) return;
40
- if (message.author.id !== OWNER_ID) return;
41
 
 
42
  const content = message.content.trim().toLowerCase();
43
 
44
- // ── Handle active drop session first ──
45
- if (hasSession(message.author.id)) {
46
  try {
47
  const handled = await handleDropMessage(message);
48
  if (handled) return;
@@ -53,17 +54,80 @@ module.exports = {
53
  }
54
  }
55
 
56
- // ── Start a new drop ──
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  if (content === 'drop') {
58
  await logCommand(client, 'drop');
59
- const session = startDropSession(message.author.id);
60
  await message.reply(getPrompt(session));
61
  return;
62
  }
63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  // Show help
65
  if (content === 'help') {
66
- const allCommands = [...Object.keys(commands), 'drop'];
67
  const embed = infoEmbed('WSB Commands', [
68
  'All commands are **DM-only** and **Owner-only**.\n',
69
  ...allCommands.map(cmd => `\`${cmd}\``),
@@ -77,10 +141,11 @@ module.exports = {
77
  if (content.startsWith('setup') || content.startsWith('create') ||
78
  content.startsWith('backup') || content.startsWith('shutdown') ||
79
  content.startsWith('ticket') || content.startsWith('permission') ||
80
- content.startsWith('post') || content.startsWith('drop')) {
 
81
  return message.reply({ embeds: [errorEmbed('Unknown Command', `Did you mean one of these?\n${[...Object.keys(commands), 'drop'].map(c => `\`${c}\``).join('\n')}`)] });
82
  }
83
- return; // Silently ignore non-command messages
84
  }
85
 
86
  try {
 
1
  const { ChannelType } = require('discord.js');
2
+ const { errorEmbed, infoEmbed, successEmbed, createEmbed } = require('../utils/embeds');
3
  const { logCommand } = require('../systems/logger');
4
+ const { startDropSession, hasSession, getPrompt, handleDropMessage, canDrop } = require('../systems/drops');
5
+ const { stmts } = require('../database');
6
+ const { Colors } = require('../config');
7
 
8
  // Import command handlers
9
  const setup = require('../commands/setup');
 
19
 
20
  const OWNER_ID = process.env.OWNER_ID;
21
 
22
+ // Owner-only command registry
23
  const commands = {
24
  'setup server': setup,
25
  'create roles': createRoles,
 
36
  module.exports = {
37
  name: 'messageCreate',
38
  async execute(client, message) {
 
39
  if (message.author.bot) return;
40
  if (message.channel.type !== ChannelType.DM) return;
 
41
 
42
+ const userId = message.author.id;
43
  const content = message.content.trim().toLowerCase();
44
 
45
+ // ── Handle active drop session (owner OR whitelisted) ──
46
+ if (hasSession(userId)) {
47
  try {
48
  const handled = await handleDropMessage(message);
49
  if (handled) return;
 
54
  }
55
  }
56
 
57
+ // ── Whitelisted user: only allow "drop" ──
58
+ if (userId !== OWNER_ID) {
59
+ if (content === 'drop') {
60
+ const check = canDrop(userId);
61
+ if (!check.allowed) {
62
+ if (check.reason === 'not_whitelisted') {
63
+ return; // Silently ignore non-whitelisted users
64
+ }
65
+ // Rate limited
66
+ return message.reply({
67
+ embeds: [createEmbed({
68
+ title: '⏳ Drop Cooldown',
69
+ description: `You've used all **3 drops** in the last 24 hours.\n\n> Resets in **${check.resetIn}**`,
70
+ color: Colors.WARNING,
71
+ })],
72
+ });
73
+ }
74
+ const session = startDropSession(userId);
75
+ await message.reply({
76
+ ...getPrompt(session),
77
+ content: `πŸ“¦ **Drop ${check.remaining}/3 remaining today**`,
78
+ });
79
+ return;
80
+ }
81
+ return; // Whitelisted users can only use "drop"
82
+ }
83
+
84
+ // ── Owner-only commands below ──────────────────────────
85
+
86
+ // Drop (no rate limit for owner)
87
  if (content === 'drop') {
88
  await logCommand(client, 'drop');
89
+ const session = startDropSession(userId);
90
  await message.reply(getPrompt(session));
91
  return;
92
  }
93
 
94
+ // Whitelist management
95
+ if (content.startsWith('whitelist ')) {
96
+ const targetId = content.split(' ')[1]?.trim();
97
+ if (!targetId || !/^\d{17,20}$/.test(targetId)) {
98
+ return message.reply({ embeds: [errorEmbed('Invalid ID', 'Usage: `whitelist <user_id>`')] });
99
+ }
100
+ stmts.addWhitelist.run(targetId, userId);
101
+ const user = await client.users.fetch(targetId).catch(() => null);
102
+ const name = user ? `**${user.tag}**` : `ID \`${targetId}\``;
103
+ return message.reply({ embeds: [successEmbed('βœ… Whitelisted', `${name} can now use \`drop\` (3/day)`)] });
104
+ }
105
+
106
+ if (content.startsWith('unwhitelist ')) {
107
+ const targetId = content.split(' ')[1]?.trim();
108
+ if (!targetId) return message.reply({ embeds: [errorEmbed('Invalid ID', 'Usage: `unwhitelist <user_id>`')] });
109
+ stmts.removeWhitelist.run(targetId);
110
+ return message.reply({ embeds: [successEmbed('βœ… Removed', `User \`${targetId}\` removed from whitelist`)] });
111
+ }
112
+
113
+ if (content === 'whitelist') {
114
+ const all = stmts.getAllWhitelist.all();
115
+ if (all.length === 0) {
116
+ return message.reply({ embeds: [infoEmbed('Whitelist', 'No users whitelisted.\n\nUse `whitelist <user_id>` to add.')] });
117
+ }
118
+ const lines = [];
119
+ for (const w of all) {
120
+ const user = await client.users.fetch(w.user_id).catch(() => null);
121
+ const name = user ? user.tag : 'Unknown';
122
+ const drops = stmts.getDropCount24h.get(w.user_id);
123
+ lines.push(`β€’ **${name}** (\`${w.user_id}\`) β€” ${drops.count}/3 drops today`);
124
+ }
125
+ return message.reply({ embeds: [infoEmbed('πŸ“‹ Whitelist', lines.join('\n'))] });
126
+ }
127
+
128
  // Show help
129
  if (content === 'help') {
130
+ const allCommands = [...Object.keys(commands), 'drop', 'whitelist', 'whitelist <id>', 'unwhitelist <id>'];
131
  const embed = infoEmbed('WSB Commands', [
132
  'All commands are **DM-only** and **Owner-only**.\n',
133
  ...allCommands.map(cmd => `\`${cmd}\``),
 
141
  if (content.startsWith('setup') || content.startsWith('create') ||
142
  content.startsWith('backup') || content.startsWith('shutdown') ||
143
  content.startsWith('ticket') || content.startsWith('permission') ||
144
+ content.startsWith('post') || content.startsWith('drop') ||
145
+ content.startsWith('apply')) {
146
  return message.reply({ embeds: [errorEmbed('Unknown Command', `Did you mean one of these?\n${[...Object.keys(commands), 'drop'].map(c => `\`${c}\``).join('\n')}`)] });
147
  }
148
+ return;
149
  }
150
 
151
  try {
src/systems/drops.js CHANGED
@@ -6,12 +6,40 @@ const {
6
  } = require('discord.js');
7
  const { createEmbed } = require('../utils/embeds');
8
  const { Colors } = require('../config');
 
 
 
 
9
 
10
  // Active drop sessions: Map<userId, session>
11
  const activeSessions = new Map();
12
 
13
  const STEPS = ['title', 'file', 'warnings', 'status', 'about', 'image', 'preview'];
14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  /**
16
  * Start a new drop session for the owner.
17
  */
@@ -268,6 +296,9 @@ async function handleDropMessage(message) {
268
  components: [downloadRow],
269
  });
270
 
 
 
 
271
  activeSessions.delete(userId);
272
  await message.reply({
273
  embeds: [createEmbed({
@@ -355,4 +386,5 @@ module.exports = {
355
  handleDropMessage,
356
  handleDropButton,
357
  buildDropEmbed,
 
358
  };
 
6
  } = require('discord.js');
7
  const { createEmbed } = require('../utils/embeds');
8
  const { Colors } = require('../config');
9
+ const { stmts } = require('../database');
10
+
11
+ const MAX_DROPS_PER_DAY = 3;
12
+ const OWNER_ID = process.env.OWNER_ID;
13
 
14
  // Active drop sessions: Map<userId, session>
15
  const activeSessions = new Map();
16
 
17
  const STEPS = ['title', 'file', 'warnings', 'status', 'about', 'image', 'preview'];
18
 
19
+ /**
20
+ * Check if a user can drop (owner = unlimited, whitelisted = 3/day).
21
+ * Returns { allowed: boolean, remaining?: number, resetIn?: string }
22
+ */
23
+ function canDrop(userId) {
24
+ if (userId === OWNER_ID) return { allowed: true, remaining: Infinity };
25
+
26
+ const wl = stmts.getWhitelist.get(userId);
27
+ if (!wl) return { allowed: false, reason: 'not_whitelisted' };
28
+
29
+ const { count } = stmts.getDropCount24h.get(userId);
30
+ if (count >= MAX_DROPS_PER_DAY) {
31
+ const last = stmts.getLastDrop.get(userId);
32
+ const resetTime = new Date(last.dropped_at + 'Z');
33
+ resetTime.setHours(resetTime.getHours() + 24);
34
+ const diff = resetTime - new Date();
35
+ const hours = Math.floor(diff / 3600000);
36
+ const mins = Math.floor((diff % 3600000) / 60000);
37
+ return { allowed: false, reason: 'rate_limited', resetIn: `${hours}h ${mins}m` };
38
+ }
39
+
40
+ return { allowed: true, remaining: MAX_DROPS_PER_DAY - count };
41
+ }
42
+
43
  /**
44
  * Start a new drop session for the owner.
45
  */
 
296
  components: [downloadRow],
297
  });
298
 
299
+ // Log the drop for rate limiting
300
+ stmts.logDrop.run(userId, session.title, channelId);
301
+
302
  activeSessions.delete(userId);
303
  await message.reply({
304
  embeds: [createEmbed({
 
386
  handleDropMessage,
387
  handleDropButton,
388
  buildDropEmbed,
389
+ canDrop,
390
  };