From d34d3a5abd5a87365bc1ca9ec2fe2f1fa8f8e58f Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 22 Feb 2026 21:02:16 +0700 Subject: [PATCH 01/12] feat(bots/discord): auto migrate database schema --- bots/discord/.drizzle/0000_schema.sql | 18 +++ bots/discord/.drizzle/meta/0000_snapshot.json | 133 ++++++++++++++++++ bots/discord/.drizzle/meta/_journal.json | 13 ++ bots/discord/.gitignore | 1 - bots/discord/scripts/build.ts | 1 - bots/discord/src/context.ts | 32 +---- 6 files changed, 170 insertions(+), 28 deletions(-) create mode 100644 bots/discord/.drizzle/0000_schema.sql create mode 100644 bots/discord/.drizzle/meta/0000_snapshot.json create mode 100644 bots/discord/.drizzle/meta/_journal.json diff --git a/bots/discord/.drizzle/0000_schema.sql b/bots/discord/.drizzle/0000_schema.sql new file mode 100644 index 0000000..5b7a432 --- /dev/null +++ b/bots/discord/.drizzle/0000_schema.sql @@ -0,0 +1,18 @@ +CREATE TABLE `applied_presets` ( + `member` text NOT NULL, + `guild` text NOT NULL, + `roles` text DEFAULT '[]' NOT NULL, + `preset` text NOT NULL, + `until` integer +); +--> statement-breakpoint +CREATE UNIQUE INDEX `unique_composite` ON `applied_presets` (`member`,`preset`,`guild`);--> statement-breakpoint +CREATE TABLE `responses` ( + `reply` text PRIMARY KEY NOT NULL, + `channel` text NOT NULL, + `guild` text NOT NULL, + `ref` text NOT NULL, + `label` text NOT NULL, + `text` text NOT NULL, + `by` text +); diff --git a/bots/discord/.drizzle/meta/0000_snapshot.json b/bots/discord/.drizzle/meta/0000_snapshot.json new file mode 100644 index 0000000..ea6460f --- /dev/null +++ b/bots/discord/.drizzle/meta/0000_snapshot.json @@ -0,0 +1,133 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "81c6e9da-4d03-4d2f-9934-1a6cf376dd6e", + "prevId": "00000000-0000-0000-0000-000000000000", + "tables": { + "applied_presets": { + "name": "applied_presets", + "columns": { + "member": { + "name": "member", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "guild": { + "name": "guild", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "roles": { + "name": "roles", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "preset": { + "name": "preset", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "until": { + "name": "until", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "unique_composite": { + "name": "unique_composite", + "columns": [ + "member", + "preset", + "guild" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "responses": { + "name": "responses", + "columns": { + "reply": { + "name": "reply", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "guild": { + "name": "guild", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ref": { + "name": "ref", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "by": { + "name": "by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/bots/discord/.drizzle/meta/_journal.json b/bots/discord/.drizzle/meta/_journal.json new file mode 100644 index 0000000..376a43e --- /dev/null +++ b/bots/discord/.drizzle/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "sqlite", + "entries": [ + { + "idx": 0, + "version": "6", + "when": 1771768794695, + "tag": "0000_schema", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/bots/discord/.gitignore b/bots/discord/.gitignore index 130f357..2e8d818 100644 --- a/bots/discord/.gitignore +++ b/bots/discord/.gitignore @@ -178,7 +178,6 @@ dist *.db *.sqlite *.sqlite3 -.drizzle # Auto-generated files src/commands/index.ts diff --git a/bots/discord/scripts/build.ts b/bots/discord/scripts/build.ts index f382cfe..814575c 100644 --- a/bots/discord/scripts/build.ts +++ b/bots/discord/scripts/build.ts @@ -20,4 +20,3 @@ await cp('./config.js', './dist/config.js') logger.info('Copying database schema...') await cp('./.drizzle', './dist/.drizzle', { recursive: true }) -await rm('./.drizzle', { recursive: true }) diff --git a/bots/discord/src/context.ts b/bots/discord/src/context.ts index cfd7d07..445307d 100644 --- a/bots/discord/src/context.ts +++ b/bots/discord/src/context.ts @@ -3,7 +3,7 @@ import { Client as APIClient } from '@revanced/bot-api' import { createLogger } from '@revanced/bot-shared' import { Client as DiscordClient, type Message, Options, Partials } from 'discord.js' import { drizzle } from 'drizzle-orm/bun-sqlite' -import { existsSync, readdirSync, readFileSync } from 'fs' +import { migrate } from 'drizzle-orm/bun-sqlite/migrator' import { join } from 'path' import { __getConfig, config } from './config' import * as schemas from './database/schemas' @@ -30,37 +30,17 @@ export const api = { } const DatabasePath = process.env['DATABASE_PATH'] -const DatabaseSchemaDir = join(import.meta.dir, '..', '.drizzle') - -let dbSchemaFileName: string | undefined - -if (DatabasePath && !existsSync(DatabasePath)) { - logger.warn('Database file not found, trying to create from schema...') - - try { - const file = readdirSync(DatabaseSchemaDir, { withFileTypes: true }) - .filter(file => file.isFile() && file.name.endsWith('.sql')) - .sort() - .at(-1) - - if (!file) throw new Error('No schema file found') - - dbSchemaFileName = file.name - logger.debug(`Using schema file: ${dbSchemaFileName}`) - } catch (e) { - logger.fatal('Could not create database from schema, check if the schema file exists and is accessible') - logger.fatal(e) - process.exit(1) - } -} const db = new Database(DatabasePath, { readwrite: true, create: true }) -if (dbSchemaFileName) db.run(readFileSync(join(DatabaseSchemaDir, dbSchemaFileName)).toString()) -export const database = drizzle(db, { +const database = drizzle(db, { schema: schemas, }) +migrate(database, { migrationsFolder: join(import.meta.dir, '..', '.drizzle') }) + +export { database } + export const discord = { client: new DiscordClient({ intents: [ From 18a119fdad2af838e2d1fae5094ef9c5358e6d8c Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sun, 22 Feb 2026 16:42:04 +0100 Subject: [PATCH 02/12] feat(bots/discord): add remind/unremind command (#51) --- bots/discord/src/commands/utility/remind.ts | 147 ++++++++++++++++++ bots/discord/src/commands/utility/unremind.ts | 62 ++++++++ bots/discord/src/database/schemas.ts | 15 ++ .../events/discord/ready/checkReminders.ts | 75 +++++++++ 4 files changed, 299 insertions(+) create mode 100644 bots/discord/src/commands/utility/remind.ts create mode 100644 bots/discord/src/commands/utility/unremind.ts create mode 100644 bots/discord/src/events/discord/ready/checkReminders.ts diff --git a/bots/discord/src/commands/utility/remind.ts b/bots/discord/src/commands/utility/remind.ts new file mode 100644 index 0000000..df5f622 --- /dev/null +++ b/bots/discord/src/commands/utility/remind.ts @@ -0,0 +1,147 @@ +import { EmbedBuilder, MessageFlags } from 'discord.js' +import { eq } from 'drizzle-orm' +import Command from '$/classes/Command' +import { database } from '$/context' +import { reminders } from '$/database/schemas' +import { applyCommonEmbedStyles } from '$/utils/discord/embeds' +import { durationToString, parseDuration } from '$/utils/duration' + +export default new Command({ + name: 'remind', + description: 'Set a reminder or list your reminders', + type: Command.Type.ChatGuild, + requirements: { + defaultCondition: 'pass', + }, + options: { + message: { + description: 'The reminder message', + required: false, + type: Command.OptionType.String, + maxLength: 1000, + }, + interval: { + description: 'When to remind (e.g., 1d, 2h30m, 1w). Default: 1 day', + required: false, + type: Command.OptionType.String, + }, + user: { + description: 'The user to remind (defaults to yourself)', + required: false, + type: Command.OptionType.User, + }, + }, + async execute({ logger }, interaction, { message, interval, user }) { + // If no message is provided, list all reminders + if (!message) { + const userReminders = await database.query.reminders.findMany({ + where: eq(reminders.creatorId, interaction.user.id), + }) + + if (userReminders.length === 0) { + const embed = applyCommonEmbedStyles( + new EmbedBuilder().setTitle('No Reminders').setDescription('You have no active reminders.'), + false, + true, + true, + ) + + await interaction.reply({ + embeds: [embed], + flags: MessageFlags.Ephemeral, + }) + return + } + + const reminderList = userReminders + .map(r => { + const targetStr = r.targetId === r.creatorId ? 'yourself' : `<@${r.targetId}>` + return ( + `**${r.id}.** ${r.message.substring(0, 50)}${r.message.length > 50 ? '...' : ''}\n` + + `-# For ${targetStr} • • Reminded ${r.count}x` + ) + }) + .join('\n\n') + + const embed = applyCommonEmbedStyles( + new EmbedBuilder().setTitle('Your Reminders').setDescription(reminderList), + false, + true, + true, + ) + + await interaction.reply({ + embeds: [embed], + flags: MessageFlags.Ephemeral, + }) + return + } + + // Create a new reminder + const targetUser = user ?? interaction.user + const durationMs = parseDuration(interval ?? '1d', 'd') + + if (durationMs <= 0 || !Number.isFinite(durationMs)) { + const embed = applyCommonEmbedStyles( + new EmbedBuilder() + .setTitle('Invalid duration') + .setDescription('Please provide a valid duration (e.g., 1d, 2h30m, 1w).') + .setColor('Red'), + false, + false, + false, + ) + + await interaction.reply({ + embeds: [embed], + flags: MessageFlags.Ephemeral, + }) + return + } + + const now = Math.floor(Date.now() / 1000) + const remindAt = now + Math.floor(durationMs / 1000) + + const intervalSeconds = Math.floor(durationMs / 1000) + const [inserted] = await database + .insert(reminders) + .values({ + creatorId: interaction.user.id, + targetId: targetUser.id, + guildId: interaction.guildId!, + channelId: interaction.channelId, + message: message, + createdAt: now, + remindAt: remindAt, + intervalSeconds: intervalSeconds, + count: 0, + }) + .returning() + + const reminderId = inserted?.id ?? 'unknown' + + const targetStr = targetUser.id === interaction.user.id ? 'You' : targetUser.toString() + + const embed = applyCommonEmbedStyles( + new EmbedBuilder() + .setTitle('Reminder set') + .setDescription( + `${targetStr} will be reminded .\n\n` + + `**Message:** ${message}\n` + + `-# Reminder ID: ${reminderId}`, + ), + false, + true, + true, + ) + + await interaction.reply({ + embeds: [embed], + }) + + logger.info( + `User ${interaction.user.tag} (${interaction.user.id}) set reminder #${reminderId} ` + + `for ${targetUser.tag} (${targetUser.id}) in ${durationToString(durationMs)}`, + ) + }, +}) diff --git a/bots/discord/src/commands/utility/unremind.ts b/bots/discord/src/commands/utility/unremind.ts new file mode 100644 index 0000000..877b1d8 --- /dev/null +++ b/bots/discord/src/commands/utility/unremind.ts @@ -0,0 +1,62 @@ +import { EmbedBuilder, MessageFlags } from 'discord.js' +import { eq } from 'drizzle-orm' +import Command from '$/classes/Command' +import CommandError, { CommandErrorType } from '$/classes/CommandError' +import { database } from '$/context' +import { reminders } from '$/database/schemas' +import { applyCommonEmbedStyles } from '$/utils/discord/embeds' + +export default new Command({ + name: 'unremind', + description: 'Remove a reminder', + type: Command.Type.ChatGuild, + requirements: { + defaultCondition: 'pass', + }, + options: { + id: { + description: 'The reminder ID to remove', + required: true, + type: Command.OptionType.Integer, + min: 1, + }, + }, + async execute({ logger }, interaction, { id }) { + const reminder = await database.query.reminders.findFirst({ + where: eq(reminders.id, id), + }) + + if (!reminder) { + throw new CommandError(CommandErrorType.InvalidArgument, `Reminder with ID **${id}** was not found.`) + } + + // Only the creator can remove the reminder + if (reminder.creatorId !== interaction.user.id) { + throw new CommandError( + CommandErrorType.RequirementsNotMet, + 'You can only remove reminders that you created.', + ) + } + + await database.delete(reminders).where(eq(reminders.id, id)) + + const embed = applyCommonEmbedStyles( + new EmbedBuilder() + .setTitle('Reminder removed') + .setDescription( + `Removed reminder **#${id}**.\n\n` + + `-# Message: ${reminder.message.substring(0, 100)}${reminder.message.length > 100 ? '...' : ''}`, + ), + false, + true, + true, + ) + + await interaction.reply({ + embeds: [embed], + flags: MessageFlags.Ephemeral, + }) + + logger.info(`User ${interaction.user.tag} (${interaction.user.id}) removed reminder #${id}`) + }, +}) diff --git a/bots/discord/src/database/schemas.ts b/bots/discord/src/database/schemas.ts index 409c1f8..eb38ac4 100644 --- a/bots/discord/src/database/schemas.ts +++ b/bots/discord/src/database/schemas.ts @@ -1,6 +1,21 @@ import { integer, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core' import type { InferSelectModel } from 'drizzle-orm' +export const reminders = sqliteTable('reminders', { + id: integer('id').primaryKey({ autoIncrement: true }), + creatorId: text('creator').notNull(), + targetId: text('target').notNull(), + guildId: text('guild').notNull(), + channelId: text('channel').notNull(), + message: text('message').notNull(), + createdAt: integer('created_at').notNull(), + remindAt: integer('remind_at').notNull(), + intervalSeconds: integer('interval_seconds').notNull(), + count: integer('count').notNull().default(0), +}) + +export type Reminder = InferSelectModel + export const responses = sqliteTable('responses', { replyId: text('reply').primaryKey().notNull(), channelId: text('channel').notNull(), diff --git a/bots/discord/src/events/discord/ready/checkReminders.ts b/bots/discord/src/events/discord/ready/checkReminders.ts new file mode 100644 index 0000000..841410d --- /dev/null +++ b/bots/discord/src/events/discord/ready/checkReminders.ts @@ -0,0 +1,75 @@ +import { type Client, EmbedBuilder } from 'discord.js' +import { eq, lt } from 'drizzle-orm' +import { database, logger } from '$/context' +import { reminders } from '$/database/schemas' +import { applyCommonEmbedStyles } from '$/utils/discord/embeds' +import { on, withContext } from '$/utils/discord/events' + +const REMINDER_CHECK_INTERVAL = 30_000 // Check every 30 seconds + +export default withContext(on, 'ready', async (_, client) => { + checkReminders(client) + setInterval(() => checkReminders(client), REMINDER_CHECK_INTERVAL) +}) + +async function checkReminders(client: Client) { + logger.debug('Checking for due reminders...') + + const now = Math.floor(Date.now() / 1000) + const dueReminders = await database.query.reminders.findMany({ + where: lt(reminders.remindAt, now), + }) + + for (const reminder of dueReminders) { + try { + logger.debug(`Processing reminder #${reminder.id} for ${reminder.targetId}`) + + const guild = await client.guilds.fetch(reminder.guildId) + const channel = await guild.channels.fetch(reminder.channelId) + + if (!channel?.isTextBased()) { + logger.warn( + `Channel ${reminder.channelId} for reminder #${reminder.id} is not text-based or doesn't exist`, + ) + await database.delete(reminders).where(eq(reminders.id, reminder.id)) + continue + } + + const newCount = reminder.count + 1 + const creatorMention = reminder.creatorId === reminder.targetId ? '' : ` (set by <@${reminder.creatorId}>)` + + const embed = applyCommonEmbedStyles( + new EmbedBuilder() + .setTitle(`Reminder (#${newCount})`) + .setDescription(reminder.message) + .setFooter({ + text: `Set on ${new Date(reminder.createdAt * 1000).toLocaleDateString()}`, + }), + false, + false, + true, + ) + + await channel.send({ + content: `<@${reminder.targetId}>${creatorMention}`, + embeds: [embed], + }) + + logger.info( + `Sent reminder #${reminder.id} (count: ${newCount}) to ${reminder.targetId} in channel ${reminder.channelId}`, + ) + + // Update count and schedule next reminder + const nextRemindAt = now + reminder.intervalSeconds + await database + .update(reminders) + .set({ + count: newCount, + remindAt: nextRemindAt, + }) + .where(eq(reminders.id, reminder.id)) + } catch (e) { + logger.error(`Error while processing reminder #${reminder.id}:`, e) + } + } +} From d9754f2869134a1c91acc3e4738da29d28308afb Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 22 Feb 2026 15:42:48 +0000 Subject: [PATCH 03/12] chore(release): 1.6.0-dev.1 [skip ci] # @revanced/discord-bot [1.6.0-dev.1](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.5.3...@revanced/discord-bot@1.6.0-dev.1) (2026-02-22) ### Features * **bots/discord:** add remind/unremind command ([#51](https://github.com/revanced/revanced-bots/issues/51)) ([18a119f](https://github.com/revanced/revanced-bots/commit/18a119fdad2af838e2d1fae5094ef9c5358e6d8c)) * **bots/discord:** auto migrate database schema ([d34d3a5](https://github.com/revanced/revanced-bots/commit/d34d3a5abd5a87365bc1ca9ec2fe2f1fa8f8e58f)) --- bots/discord/CHANGELOG.md | 8 ++++++++ bots/discord/package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index 8ec7888..6ed010a 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,11 @@ +# @revanced/discord-bot [1.6.0-dev.1](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.5.3...@revanced/discord-bot@1.6.0-dev.1) (2026-02-22) + + +### Features + +* **bots/discord:** add remind/unremind command ([#51](https://github.com/revanced/revanced-bots/issues/51)) ([18a119f](https://github.com/revanced/revanced-bots/commit/18a119fdad2af838e2d1fae5094ef9c5358e6d8c)) +* **bots/discord:** auto migrate database schema ([d34d3a5](https://github.com/revanced/revanced-bots/commit/d34d3a5abd5a87365bc1ca9ec2fe2f1fa8f8e58f)) + ## @revanced/discord-bot [1.5.3](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.5.2...@revanced/discord-bot@1.5.3) (2026-01-17) diff --git a/bots/discord/package.json b/bots/discord/package.json index db9f61e..7fbf2ff 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.5.3", + "version": "1.6.0-dev.1", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { From f24a8fbfdc968971c73715d0bf9768dec3341954 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 22 Feb 2026 22:49:30 +0700 Subject: [PATCH 04/12] fix(bots/discord): add missing database migration file --- bots/discord/.drizzle/0001_schema.sql | 12 + bots/discord/.drizzle/meta/0001_snapshot.json | 214 ++++++++++++++++++ bots/discord/.drizzle/meta/_journal.json | 7 + 3 files changed, 233 insertions(+) create mode 100644 bots/discord/.drizzle/0001_schema.sql create mode 100644 bots/discord/.drizzle/meta/0001_snapshot.json diff --git a/bots/discord/.drizzle/0001_schema.sql b/bots/discord/.drizzle/0001_schema.sql new file mode 100644 index 0000000..b9d9b1f --- /dev/null +++ b/bots/discord/.drizzle/0001_schema.sql @@ -0,0 +1,12 @@ +CREATE TABLE `reminders` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `creator` text NOT NULL, + `target` text NOT NULL, + `guild` text NOT NULL, + `channel` text NOT NULL, + `message` text NOT NULL, + `created_at` integer NOT NULL, + `remind_at` integer NOT NULL, + `interval_seconds` integer NOT NULL, + `count` integer DEFAULT 0 NOT NULL +); diff --git a/bots/discord/.drizzle/meta/0001_snapshot.json b/bots/discord/.drizzle/meta/0001_snapshot.json new file mode 100644 index 0000000..bc1a1e7 --- /dev/null +++ b/bots/discord/.drizzle/meta/0001_snapshot.json @@ -0,0 +1,214 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "ea0c4e69-ffe0-45f8-81cc-aefc1c696751", + "prevId": "81c6e9da-4d03-4d2f-9934-1a6cf376dd6e", + "tables": { + "applied_presets": { + "name": "applied_presets", + "columns": { + "member": { + "name": "member", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "guild": { + "name": "guild", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "roles": { + "name": "roles", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "preset": { + "name": "preset", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "until": { + "name": "until", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "unique_composite": { + "name": "unique_composite", + "columns": [ + "member", + "preset", + "guild" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "reminders": { + "name": "reminders", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "creator": { + "name": "creator", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target": { + "name": "target", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "guild": { + "name": "guild", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "remind_at": { + "name": "remind_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "interval_seconds": { + "name": "interval_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "responses": { + "name": "responses", + "columns": { + "reply": { + "name": "reply", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "guild": { + "name": "guild", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ref": { + "name": "ref", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "by": { + "name": "by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/bots/discord/.drizzle/meta/_journal.json b/bots/discord/.drizzle/meta/_journal.json index 376a43e..bb3c2b4 100644 --- a/bots/discord/.drizzle/meta/_journal.json +++ b/bots/discord/.drizzle/meta/_journal.json @@ -8,6 +8,13 @@ "when": 1771768794695, "tag": "0000_schema", "breakpoints": true + }, + { + "idx": 1, + "version": "6", + "when": 1771775341832, + "tag": "0001_schema", + "breakpoints": true } ] } \ No newline at end of file From 99c74227c4185fa4d6731dac03230312d9b3106a Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 22 Feb 2026 22:54:30 +0700 Subject: [PATCH 05/12] feat(bots/discord/database/schemas): add indexes on reminders --- bots/discord/.drizzle/0002_schema.sql | 2 + bots/discord/.drizzle/meta/0002_snapshot.json | 230 ++++++++++++++++++ bots/discord/.drizzle/meta/_journal.json | 7 + bots/discord/src/database/schemas.ts | 31 ++- 4 files changed, 258 insertions(+), 12 deletions(-) create mode 100644 bots/discord/.drizzle/0002_schema.sql create mode 100644 bots/discord/.drizzle/meta/0002_snapshot.json diff --git a/bots/discord/.drizzle/0002_schema.sql b/bots/discord/.drizzle/0002_schema.sql new file mode 100644 index 0000000..1e3e8be --- /dev/null +++ b/bots/discord/.drizzle/0002_schema.sql @@ -0,0 +1,2 @@ +CREATE UNIQUE INDEX `reminders_remind_at_idx` ON `reminders` (`remind_at`);--> statement-breakpoint +CREATE UNIQUE INDEX `reminders_creator_guild_idx` ON `reminders` (`creator`,`guild`); \ No newline at end of file diff --git a/bots/discord/.drizzle/meta/0002_snapshot.json b/bots/discord/.drizzle/meta/0002_snapshot.json new file mode 100644 index 0000000..b83835d --- /dev/null +++ b/bots/discord/.drizzle/meta/0002_snapshot.json @@ -0,0 +1,230 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "05335016-e3f2-40ac-96ae-ce775205f005", + "prevId": "ea0c4e69-ffe0-45f8-81cc-aefc1c696751", + "tables": { + "applied_presets": { + "name": "applied_presets", + "columns": { + "member": { + "name": "member", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "guild": { + "name": "guild", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "roles": { + "name": "roles", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "preset": { + "name": "preset", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "until": { + "name": "until", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "unique_composite": { + "name": "unique_composite", + "columns": [ + "member", + "preset", + "guild" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "reminders": { + "name": "reminders", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "creator": { + "name": "creator", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target": { + "name": "target", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "guild": { + "name": "guild", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "remind_at": { + "name": "remind_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "interval_seconds": { + "name": "interval_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "reminders_remind_at_idx": { + "name": "reminders_remind_at_idx", + "columns": [ + "remind_at" + ], + "isUnique": true + }, + "reminders_creator_guild_idx": { + "name": "reminders_creator_guild_idx", + "columns": [ + "creator", + "guild" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "responses": { + "name": "responses", + "columns": { + "reply": { + "name": "reply", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "guild": { + "name": "guild", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ref": { + "name": "ref", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "by": { + "name": "by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/bots/discord/.drizzle/meta/_journal.json b/bots/discord/.drizzle/meta/_journal.json index bb3c2b4..c739f13 100644 --- a/bots/discord/.drizzle/meta/_journal.json +++ b/bots/discord/.drizzle/meta/_journal.json @@ -15,6 +15,13 @@ "when": 1771775341832, "tag": "0001_schema", "breakpoints": true + }, + { + "idx": 2, + "version": "6", + "when": 1771775646605, + "tag": "0002_schema", + "breakpoints": true } ] } \ No newline at end of file diff --git a/bots/discord/src/database/schemas.ts b/bots/discord/src/database/schemas.ts index eb38ac4..2659110 100644 --- a/bots/discord/src/database/schemas.ts +++ b/bots/discord/src/database/schemas.ts @@ -1,18 +1,25 @@ import { integer, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core' import type { InferSelectModel } from 'drizzle-orm' -export const reminders = sqliteTable('reminders', { - id: integer('id').primaryKey({ autoIncrement: true }), - creatorId: text('creator').notNull(), - targetId: text('target').notNull(), - guildId: text('guild').notNull(), - channelId: text('channel').notNull(), - message: text('message').notNull(), - createdAt: integer('created_at').notNull(), - remindAt: integer('remind_at').notNull(), - intervalSeconds: integer('interval_seconds').notNull(), - count: integer('count').notNull().default(0), -}) +export const reminders = sqliteTable( + 'reminders', + { + id: integer('id').primaryKey({ autoIncrement: true }), + creatorId: text('creator').notNull(), + targetId: text('target').notNull(), + guildId: text('guild').notNull(), + channelId: text('channel').notNull(), + message: text('message').notNull(), + createdAt: integer('created_at').notNull(), + remindAt: integer('remind_at').notNull(), + intervalSeconds: integer('interval_seconds').notNull(), + count: integer('count').notNull().default(0), + }, + table => [ + uniqueIndex('reminders_remind_at_idx').on(table.remindAt), + uniqueIndex('reminders_creator_guild_idx').on(table.creatorId, table.guildId), + ], +) export type Reminder = InferSelectModel From 26680a97fd15cc75250cd91a612132844567c54b Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 22 Feb 2026 22:57:09 +0700 Subject: [PATCH 06/12] fix(bots/discord): off-by-one reminders query + log if query fails --- .../src/events/discord/ready/checkReminders.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/bots/discord/src/events/discord/ready/checkReminders.ts b/bots/discord/src/events/discord/ready/checkReminders.ts index 841410d..b5a47dc 100644 --- a/bots/discord/src/events/discord/ready/checkReminders.ts +++ b/bots/discord/src/events/discord/ready/checkReminders.ts @@ -1,5 +1,5 @@ import { type Client, EmbedBuilder } from 'discord.js' -import { eq, lt } from 'drizzle-orm' +import { eq, lte } from 'drizzle-orm' import { database, logger } from '$/context' import { reminders } from '$/database/schemas' import { applyCommonEmbedStyles } from '$/utils/discord/embeds' @@ -8,8 +8,11 @@ import { on, withContext } from '$/utils/discord/events' const REMINDER_CHECK_INTERVAL = 30_000 // Check every 30 seconds export default withContext(on, 'ready', async (_, client) => { - checkReminders(client) - setInterval(() => checkReminders(client), REMINDER_CHECK_INTERVAL) + checkReminders(client).catch(e => logger.error('Error during initial reminder check:', e)) + setInterval( + () => checkReminders(client).catch(e => logger.error('Error in reminder check interval:', e)), + REMINDER_CHECK_INTERVAL, + ) }) async function checkReminders(client: Client) { @@ -17,7 +20,7 @@ async function checkReminders(client: Client) { const now = Math.floor(Date.now() / 1000) const dueReminders = await database.query.reminders.findMany({ - where: lt(reminders.remindAt, now), + where: lte(reminders.remindAt, now), }) for (const reminder of dueReminders) { From 13c48d5e6ec10a2d36f2d193f6865e0053b7cab0 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 22 Feb 2026 15:57:59 +0000 Subject: [PATCH 07/12] chore(release): 1.6.0-dev.2 [skip ci] # @revanced/discord-bot [1.6.0-dev.2](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.6.0-dev.1...@revanced/discord-bot@1.6.0-dev.2) (2026-02-22) ### Bug Fixes * **bots/discord:** add missing database migration file ([f24a8fb](https://github.com/revanced/revanced-bots/commit/f24a8fbfdc968971c73715d0bf9768dec3341954)) * **bots/discord:** off-by-one reminders query + log if query fails ([26680a9](https://github.com/revanced/revanced-bots/commit/26680a97fd15cc75250cd91a612132844567c54b)) ### Features * **bots/discord/database/schemas:** add indexes on reminders ([99c7422](https://github.com/revanced/revanced-bots/commit/99c74227c4185fa4d6731dac03230312d9b3106a)) --- bots/discord/CHANGELOG.md | 13 +++++++++++++ bots/discord/package.json | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index 6ed010a..eec15e5 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,16 @@ +# @revanced/discord-bot [1.6.0-dev.2](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.6.0-dev.1...@revanced/discord-bot@1.6.0-dev.2) (2026-02-22) + + +### Bug Fixes + +* **bots/discord:** add missing database migration file ([f24a8fb](https://github.com/revanced/revanced-bots/commit/f24a8fbfdc968971c73715d0bf9768dec3341954)) +* **bots/discord:** off-by-one reminders query + log if query fails ([26680a9](https://github.com/revanced/revanced-bots/commit/26680a97fd15cc75250cd91a612132844567c54b)) + + +### Features + +* **bots/discord/database/schemas:** add indexes on reminders ([99c7422](https://github.com/revanced/revanced-bots/commit/99c74227c4185fa4d6731dac03230312d9b3106a)) + # @revanced/discord-bot [1.6.0-dev.1](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.5.3...@revanced/discord-bot@1.6.0-dev.1) (2026-02-22) diff --git a/bots/discord/package.json b/bots/discord/package.json index 7fbf2ff..9e74eac 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.6.0-dev.1", + "version": "1.6.0-dev.2", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": { From 1f1dd7410204fb0b8ecddd655ee23fb107ec02cd Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 1 Mar 2026 17:52:20 +0700 Subject: [PATCH 08/12] fix(bots/discord): add min max intervals in `remind` command --- bots/discord/src/commands/utility/remind.ts | 25 ++++++--------------- 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/bots/discord/src/commands/utility/remind.ts b/bots/discord/src/commands/utility/remind.ts index df5f622..7da59ec 100644 --- a/bots/discord/src/commands/utility/remind.ts +++ b/bots/discord/src/commands/utility/remind.ts @@ -5,6 +5,10 @@ import { database } from '$/context' import { reminders } from '$/database/schemas' import { applyCommonEmbedStyles } from '$/utils/discord/embeds' import { durationToString, parseDuration } from '$/utils/duration' +import CommandError, { CommandErrorType } from '$/classes/CommandError' + +const MIN_DURATION = parseDuration('1m') +const MAX_DURATION = parseDuration('1y') export default new Command({ name: 'remind', @@ -21,7 +25,7 @@ export default new Command({ maxLength: 1000, }, interval: { - description: 'When to remind (e.g., 1d, 2h30m, 1w). Default: 1 day', + description: 'When to remind (e.g., 1d, 2h30m, 1w). Default: 1 day. Min: 1 minute. Max: 1 year.', required: false, type: Command.OptionType.String, }, @@ -81,23 +85,8 @@ export default new Command({ const targetUser = user ?? interaction.user const durationMs = parseDuration(interval ?? '1d', 'd') - if (durationMs <= 0 || !Number.isFinite(durationMs)) { - const embed = applyCommonEmbedStyles( - new EmbedBuilder() - .setTitle('Invalid duration') - .setDescription('Please provide a valid duration (e.g., 1d, 2h30m, 1w).') - .setColor('Red'), - false, - false, - false, - ) - - await interaction.reply({ - embeds: [embed], - flags: MessageFlags.Ephemeral, - }) - return - } + if (durationMs < MIN_DURATION || durationMs > MAX_DURATION) + throw new CommandError(CommandErrorType.InvalidArgument, 'Interval must be between 1 minute and 1 year.') const now = Math.floor(Date.now() / 1000) const remindAt = now + Math.floor(durationMs / 1000) From a94fd742a7f0b3cf8a7f31e716b88fe27f89f70b Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 1 Mar 2026 17:57:08 +0700 Subject: [PATCH 09/12] fix(bots/discord): throw error if nothing inserted during adding reminders --- bots/discord/src/commands/utility/remind.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bots/discord/src/commands/utility/remind.ts b/bots/discord/src/commands/utility/remind.ts index 7da59ec..7d8ed53 100644 --- a/bots/discord/src/commands/utility/remind.ts +++ b/bots/discord/src/commands/utility/remind.ts @@ -107,7 +107,8 @@ export default new Command({ }) .returning() - const reminderId = inserted?.id ?? 'unknown' + const reminderId = inserted?.id + if (!reminderId) throw new CommandError(CommandErrorType.Generic, 'Failed to create reminder.') const targetStr = targetUser.id === interaction.user.id ? 'You' : targetUser.toString() From cafdbc0c7bb43947afc7e7a262b2a0eb62791511 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 1 Mar 2026 17:57:57 +0700 Subject: [PATCH 10/12] fix(bots/discord/database/schemas): index instead of unique index on reminders --- bots/discord/.drizzle/0003_schema.sql | 4 + bots/discord/.drizzle/meta/0003_snapshot.json | 230 ++++++++++++++++++ bots/discord/.drizzle/meta/_journal.json | 7 + bots/discord/src/database/schemas.ts | 6 +- 4 files changed, 244 insertions(+), 3 deletions(-) create mode 100644 bots/discord/.drizzle/0003_schema.sql create mode 100644 bots/discord/.drizzle/meta/0003_snapshot.json diff --git a/bots/discord/.drizzle/0003_schema.sql b/bots/discord/.drizzle/0003_schema.sql new file mode 100644 index 0000000..a8cc001 --- /dev/null +++ b/bots/discord/.drizzle/0003_schema.sql @@ -0,0 +1,4 @@ +DROP INDEX `reminders_remind_at_idx`;--> statement-breakpoint +DROP INDEX `reminders_creator_guild_idx`;--> statement-breakpoint +CREATE INDEX `reminders_remind_at_idx` ON `reminders` (`remind_at`);--> statement-breakpoint +CREATE INDEX `reminders_creator_guild_idx` ON `reminders` (`creator`,`guild`); \ No newline at end of file diff --git a/bots/discord/.drizzle/meta/0003_snapshot.json b/bots/discord/.drizzle/meta/0003_snapshot.json new file mode 100644 index 0000000..af49c5d --- /dev/null +++ b/bots/discord/.drizzle/meta/0003_snapshot.json @@ -0,0 +1,230 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "22c5d88c-ccb5-499c-adaa-199c09a74ca6", + "prevId": "05335016-e3f2-40ac-96ae-ce775205f005", + "tables": { + "applied_presets": { + "name": "applied_presets", + "columns": { + "member": { + "name": "member", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "guild": { + "name": "guild", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "roles": { + "name": "roles", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "preset": { + "name": "preset", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "until": { + "name": "until", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "unique_composite": { + "name": "unique_composite", + "columns": [ + "member", + "preset", + "guild" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "reminders": { + "name": "reminders", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "creator": { + "name": "creator", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target": { + "name": "target", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "guild": { + "name": "guild", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "remind_at": { + "name": "remind_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "interval_seconds": { + "name": "interval_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "reminders_remind_at_idx": { + "name": "reminders_remind_at_idx", + "columns": [ + "remind_at" + ], + "isUnique": false + }, + "reminders_creator_guild_idx": { + "name": "reminders_creator_guild_idx", + "columns": [ + "creator", + "guild" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "responses": { + "name": "responses", + "columns": { + "reply": { + "name": "reply", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "guild": { + "name": "guild", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ref": { + "name": "ref", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "by": { + "name": "by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/bots/discord/.drizzle/meta/_journal.json b/bots/discord/.drizzle/meta/_journal.json index c739f13..49c5c98 100644 --- a/bots/discord/.drizzle/meta/_journal.json +++ b/bots/discord/.drizzle/meta/_journal.json @@ -22,6 +22,13 @@ "when": 1771775646605, "tag": "0002_schema", "breakpoints": true + }, + { + "idx": 3, + "version": "6", + "when": 1772362642052, + "tag": "0003_schema", + "breakpoints": true } ] } \ No newline at end of file diff --git a/bots/discord/src/database/schemas.ts b/bots/discord/src/database/schemas.ts index 2659110..c679385 100644 --- a/bots/discord/src/database/schemas.ts +++ b/bots/discord/src/database/schemas.ts @@ -1,4 +1,4 @@ -import { integer, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core' +import { index, integer, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core' import type { InferSelectModel } from 'drizzle-orm' export const reminders = sqliteTable( @@ -16,8 +16,8 @@ export const reminders = sqliteTable( count: integer('count').notNull().default(0), }, table => [ - uniqueIndex('reminders_remind_at_idx').on(table.remindAt), - uniqueIndex('reminders_creator_guild_idx').on(table.creatorId, table.guildId), + index('reminders_remind_at_idx').on(table.remindAt), + index('reminders_creator_guild_idx').on(table.creatorId, table.guildId), ], ) From b519b562f9bc1415bcd3a3cfa7bd73e18b2b21b2 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sun, 1 Mar 2026 18:01:11 +0700 Subject: [PATCH 11/12] fix(bots/discord): require roles for using utility commands --- bots/discord/config.js | 3 +++ bots/discord/config.schema.ts | 3 +++ bots/discord/src/commands/{utility => utilities}/remind.ts | 4 ++-- bots/discord/src/commands/{utility => utilities}/unremind.ts | 4 ++-- 4 files changed, 10 insertions(+), 4 deletions(-) rename bots/discord/src/commands/{utility => utilities}/remind.ts (98%) rename bots/discord/src/commands/{utility => utilities}/unremind.ts (95%) diff --git a/bots/discord/config.js b/bots/discord/config.js index 1b04f24..ff5b829 100644 --- a/bots/discord/config.js +++ b/bots/discord/config.js @@ -35,6 +35,9 @@ export default { thread: 'THREAD_ID_HERE', }, }, + utilities: { + roles: ['ROLE_ID_HERE'], + }, rolePresets: { guilds: { GUILD_ID_HERE: { diff --git a/bots/discord/config.schema.ts b/bots/discord/config.schema.ts index 156d695..0eb456b 100644 --- a/bots/discord/config.schema.ts +++ b/bots/discord/config.schema.ts @@ -19,6 +19,9 @@ export type Config = { thread?: string } } + utilities?: { + roles?: string[] + } rolePresets?: { checkExpiredEvery: number guilds: Record> diff --git a/bots/discord/src/commands/utility/remind.ts b/bots/discord/src/commands/utilities/remind.ts similarity index 98% rename from bots/discord/src/commands/utility/remind.ts rename to bots/discord/src/commands/utilities/remind.ts index 7d8ed53..bee3573 100644 --- a/bots/discord/src/commands/utility/remind.ts +++ b/bots/discord/src/commands/utilities/remind.ts @@ -1,7 +1,7 @@ import { EmbedBuilder, MessageFlags } from 'discord.js' import { eq } from 'drizzle-orm' import Command from '$/classes/Command' -import { database } from '$/context' +import { config, database } from '$/context' import { reminders } from '$/database/schemas' import { applyCommonEmbedStyles } from '$/utils/discord/embeds' import { durationToString, parseDuration } from '$/utils/duration' @@ -15,7 +15,7 @@ export default new Command({ description: 'Set a reminder or list your reminders', type: Command.Type.ChatGuild, requirements: { - defaultCondition: 'pass', + roles: config.utilities?.roles, }, options: { message: { diff --git a/bots/discord/src/commands/utility/unremind.ts b/bots/discord/src/commands/utilities/unremind.ts similarity index 95% rename from bots/discord/src/commands/utility/unremind.ts rename to bots/discord/src/commands/utilities/unremind.ts index 877b1d8..a30c022 100644 --- a/bots/discord/src/commands/utility/unremind.ts +++ b/bots/discord/src/commands/utilities/unremind.ts @@ -2,7 +2,7 @@ import { EmbedBuilder, MessageFlags } from 'discord.js' import { eq } from 'drizzle-orm' import Command from '$/classes/Command' import CommandError, { CommandErrorType } from '$/classes/CommandError' -import { database } from '$/context' +import { config, database } from '$/context' import { reminders } from '$/database/schemas' import { applyCommonEmbedStyles } from '$/utils/discord/embeds' @@ -11,7 +11,7 @@ export default new Command({ description: 'Remove a reminder', type: Command.Type.ChatGuild, requirements: { - defaultCondition: 'pass', + roles: config.utilities?.roles, }, options: { id: { From 9ba040f625c3d004f5114dc9814d7e4a59215b9b Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 1 Mar 2026 11:02:05 +0000 Subject: [PATCH 12/12] chore(release): 1.6.0-dev.3 [skip ci] # @revanced/discord-bot [1.6.0-dev.3](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.6.0-dev.2...@revanced/discord-bot@1.6.0-dev.3) (2026-03-01) ### Bug Fixes * **bots/discord/database/schemas:** index instead of unique index on reminders ([cafdbc0](https://github.com/revanced/revanced-bots/commit/cafdbc0c7bb43947afc7e7a262b2a0eb62791511)) * **bots/discord:** add min max intervals in `remind` command ([1f1dd74](https://github.com/revanced/revanced-bots/commit/1f1dd7410204fb0b8ecddd655ee23fb107ec02cd)) * **bots/discord:** require roles for using utility commands ([b519b56](https://github.com/revanced/revanced-bots/commit/b519b562f9bc1415bcd3a3cfa7bd73e18b2b21b2)) * **bots/discord:** throw error if nothing inserted during adding reminders ([a94fd74](https://github.com/revanced/revanced-bots/commit/a94fd742a7f0b3cf8a7f31e716b88fe27f89f70b)) --- bots/discord/CHANGELOG.md | 10 ++++++++++ bots/discord/package.json | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/bots/discord/CHANGELOG.md b/bots/discord/CHANGELOG.md index eec15e5..defa569 100644 --- a/bots/discord/CHANGELOG.md +++ b/bots/discord/CHANGELOG.md @@ -1,3 +1,13 @@ +# @revanced/discord-bot [1.6.0-dev.3](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.6.0-dev.2...@revanced/discord-bot@1.6.0-dev.3) (2026-03-01) + + +### Bug Fixes + +* **bots/discord/database/schemas:** index instead of unique index on reminders ([cafdbc0](https://github.com/revanced/revanced-bots/commit/cafdbc0c7bb43947afc7e7a262b2a0eb62791511)) +* **bots/discord:** add min max intervals in `remind` command ([1f1dd74](https://github.com/revanced/revanced-bots/commit/1f1dd7410204fb0b8ecddd655ee23fb107ec02cd)) +* **bots/discord:** require roles for using utility commands ([b519b56](https://github.com/revanced/revanced-bots/commit/b519b562f9bc1415bcd3a3cfa7bd73e18b2b21b2)) +* **bots/discord:** throw error if nothing inserted during adding reminders ([a94fd74](https://github.com/revanced/revanced-bots/commit/a94fd742a7f0b3cf8a7f31e716b88fe27f89f70b)) + # @revanced/discord-bot [1.6.0-dev.2](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.6.0-dev.1...@revanced/discord-bot@1.6.0-dev.2) (2026-02-22) diff --git a/bots/discord/package.json b/bots/discord/package.json index 9e74eac..ccde31f 100644 --- a/bots/discord/package.json +++ b/bots/discord/package.json @@ -2,7 +2,7 @@ "name": "@revanced/discord-bot", "type": "module", "private": true, - "version": "1.6.0-dev.2", + "version": "1.6.0-dev.3", "description": "🤖 Discord bot assisting ReVanced", "main": "src/index.ts", "scripts": {