Compare commits

...

9 Commits

Author SHA1 Message Date
Anthony Berg
32776a2311 feat: add log on Discord when bot has started
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
ESLint / Run eslint scanning (push) Waiting to run
Prettier / Run prettier scanning (push) Waiting to run
2025-01-15 17:42:54 +01:00
Anthony Berg
d72b66f988 refactor: update activist role to new snowflake 2025-01-15 16:47:48 +01:00
Anthony Berg
e03bd6e85e refactor: update roles to new snowflake 2025-01-15 16:22:31 +01:00
Anthony Berg
a400cf9507 refactor: update roles to new snowflake 2025-01-15 16:13:19 +01:00
Anthony Berg
2fbb6c9265 feat: update deps and breaking changes 2025-01-15 16:07:21 +01:00
Anthony Berg
fc8c12b346
Merge pull request #212 from veganhacktivists/coolify
Coolify support
2025-01-15 15:25:03 +01:00
Joaquín Triñanes
9ebf8a6938
Allow redis to use password auth 2024-10-25 10:58:10 +02:00
Joaquín Triñanes
bc7f2ffcfd
Add nixpacks config 2024-10-25 10:30:56 +02:00
Joaquín Triñanes
86f391e131
Enable corepack 2024-10-25 10:23:20 +02:00
13 changed files with 432 additions and 378 deletions

View File

@ -3,15 +3,18 @@ DISCORD_TOKEN= # Bot token from: https://discord.com/developers/
# Configuration # Configuration
DEFAULT_PREFIX= # Prefix used to run commands in Discord DEFAULT_PREFIX= # Prefix used to run commands in Discord
DEVELOPMENT= # (true/false) Enables developer mode DEVELOPMENT= # (true/false) Enables developer mode
# Docker # Docker
POSTGRES_USER=USERNAME POSTGRES_USER=USERNAME
POSTGRES_PASSWORD=PASSWORD POSTGRES_PASSWORD=PASSWORD
POSTGRES_DB=DB POSTGRES_DB=DB
# Redis # Redis (if running everything within docker compose, use "redis" for the host and leave the rest empty)
REDIS_URL= # URL to redis database (if running everything within docker compose, use "redis") REDIS_HOST= # URL to redis database
REDIS_USER= # redis database user
REDIS_PASSWORD= # redis database password
REDIS_PORT= # redis database port
# Database URL (designed for Postgres, but designed on Prisma) # Database URL (designed for Postgres, but designed on Prisma)
DATABASE_URL= # "postgresql://USERNAME:PASSWORD@postgres:5432/DB?schema=ara&sslmode=prefer" DATABASE_URL= # "postgresql://USERNAME:PASSWORD@postgres:5432/DB?schema=ara&sslmode=prefer"

5
nixpacks.toml Normal file
View File

@ -0,0 +1,5 @@
[phases.build]
cmds = ["pnpm prisma generate", "..."]
[start]
cmd = 'pnpm run start:migrate'

View File

@ -33,31 +33,31 @@
"node": ">=20", "node": ">=20",
"pnpm": ">=9" "pnpm": ">=9"
}, },
"packageManager": "pnpm@9.6.0",
"dependencies": { "dependencies": {
"@prisma/client": "^5.18.0", "@prisma/client": "^5.22.0",
"@sapphire/discord.js-utilities": "^7.3.0", "@sapphire/discord.js-utilities": "^7.3.2",
"@sapphire/framework": "^5.2.1", "@sapphire/framework": "^5.3.2",
"@sapphire/plugin-logger": "^4.0.2", "@sapphire/plugin-logger": "^4.0.2",
"@sapphire/plugin-scheduled-tasks": "^10.0.1", "@sapphire/plugin-scheduled-tasks": "^10.0.2",
"@sapphire/plugin-subcommands": "^6.0.3", "@sapphire/plugin-subcommands": "^6.0.3",
"@sapphire/stopwatch": "^1.5.2", "@sapphire/stopwatch": "^1.5.4",
"@sapphire/time-utilities": "^1.7.12", "@sapphire/time-utilities": "^1.7.14",
"@sapphire/ts-config": "^5.0.1", "@sapphire/ts-config": "^5.0.1",
"@sapphire/utilities": "^3.17.0", "@sapphire/utilities": "^3.18.1",
"bullmq": "^5.12.10", "bullmq": "^5.34.10",
"discord.js": "^14.15.3", "discord.js": "^14.17.3",
"ioredis": "^5.4.1", "ioredis": "^5.4.2",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "~5.4.5" "typescript": "~5.4.5"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20.16.1", "@types/node": "^20.17.13",
"@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0", "@typescript-eslint/parser": "^6.21.0",
"eslint": "8.56.0", "eslint": "8.56.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"prettier": "3.2.4", "prettier": "3.2.4",
"prisma": "^5.18.0" "prisma": "^5.22.0"
} },
"packageManager": "pnpm@9.12.2+sha512.22721b3a11f81661ae1ec68ce1a7b879425a1ca5b991c975b074ac220b187ce56c708fe5db69f4c962c989452eee76c82877f4ee80f474cebd61ee13461b6228"
} }

618
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -18,7 +18,7 @@
*/ */
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { Message } from 'discord.js'; import { Message, MessageFlagsBitField } from 'discord.js';
import { ChannelType, TextChannel } from 'discord.js'; import { ChannelType, TextChannel } from 'discord.js';
export class AnonymousCommand extends Command { export class AnonymousCommand extends Command {
@ -67,8 +67,8 @@ export class AnonymousCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
@ -77,8 +77,17 @@ export class AnonymousCommand extends Command {
if (interaction.channel === null) { if (interaction.channel === null) {
await interaction.reply({ await interaction.reply({
content: 'Error getting the channel!', content: 'Error getting the channel!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
});
return;
}
if (!interaction.channel.isSendable()) {
await interaction.reply({
content: `I do not have sufficient permissions to send a message in ${interaction.channel}!`,
flags: MessageFlagsBitField.Flags.Ephemeral,
withResponse: true,
}); });
return; return;
} }
@ -86,8 +95,8 @@ export class AnonymousCommand extends Command {
await interaction.channel.send(message); await interaction.channel.send(message);
await interaction.reply({ await interaction.reply({
content: 'Sent the message', content: 'Sent the message',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
@ -95,8 +104,8 @@ export class AnonymousCommand extends Command {
if (channel.type !== ChannelType.GuildText) { if (channel.type !== ChannelType.GuildText) {
await interaction.reply({ await interaction.reply({
content: 'Could not send, unsupported text channel!', content: 'Could not send, unsupported text channel!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
} }
@ -105,8 +114,8 @@ export class AnonymousCommand extends Command {
await interaction.reply({ await interaction.reply({
content: 'Sent the message', content: 'Sent the message',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
} }
@ -121,7 +130,7 @@ export class AnonymousCommand extends Command {
return; return;
} }
if (channel.isTextBased()) { if (channel.isSendable()) {
await channel.send(text); await channel.send(text);
} else { } else {
await message.react('❌'); await message.react('❌');

View File

@ -30,9 +30,9 @@ import {
TextChannel, TextChannel,
GuildMember, GuildMember,
Snowflake, Snowflake,
MessageFlagsBitField,
} from 'discord.js'; } from 'discord.js';
import type { Message } from 'discord.js'; import type { Message } from 'discord.js';
import { isMessageInstance } from '@sapphire/discord.js-utilities';
import { import {
addSusNoteDB, addSusNoteDB,
findNotes, findNotes,
@ -407,16 +407,21 @@ export class SusCommand extends Subcommand {
const message = await interaction.reply({ const message = await interaction.reply({
embeds: [noteEmbed], embeds: [noteEmbed],
components: [buttons], components: [buttons],
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
// Checks if the message is not an APIMessage // Checks if the message is not an APIMessage
if (!isMessageInstance(message)) { if (message.resource === null) {
await interaction.editReply('Failed to retrieve the message :('); await interaction.editReply('Failed to retrieve the message :(');
return; return;
} }
if (!channel.isSendable()) {
await interaction.editReply('Cannot send messages in this channel!');
return;
}
// Listen for the button presses // Listen for the button presses
const collector = channel.createMessageComponentCollector({ const collector = channel.createMessageComponentCollector({
max: 1, // Maximum of 1 button press max: 1, // Maximum of 1 button press
@ -519,8 +524,8 @@ export class SusCommand extends Subcommand {
if (guild === null || channel === null) { if (guild === null || channel === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild or channel!', content: 'Error fetching guild or channel!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
@ -531,8 +536,8 @@ export class SusCommand extends Subcommand {
if (member === undefined) { if (member === undefined) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching user!', content: 'Error fetching user!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
@ -545,8 +550,8 @@ export class SusCommand extends Subcommand {
if (notes.length === 0) { if (notes.length === 0) {
await interaction.reply({ await interaction.reply({
content: `${user} had no notes!`, content: `${user} had no notes!`,
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
@ -596,16 +601,21 @@ export class SusCommand extends Subcommand {
const message = await interaction.reply({ const message = await interaction.reply({
embeds: [noteEmbed], embeds: [noteEmbed],
components: [buttons], components: [buttons],
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
// Checks if the message is not an APIMessage // Checks if the message is not an APIMessage
if (!isMessageInstance(message)) { if (message.resource === null) {
await interaction.editReply('Failed to retrieve the message :('); await interaction.editReply('Failed to retrieve the message :(');
return; return;
} }
if (!channel.isSendable()) {
await interaction.editReply('Cannot send messages in this channel!');
return;
}
// Listen for the button presses // Listen for the button presses
const collector = channel.createMessageComponentCollector({ const collector = channel.createMessageComponentCollector({
max: 1, // Maximum of 1 button press max: 1, // Maximum of 1 button press

View File

@ -17,9 +17,8 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { isMessageInstance } from '@sapphire/discord.js-utilities';
import { Command } from '@sapphire/framework'; import { Command } from '@sapphire/framework';
import type { Message } from 'discord.js'; import { Message, MessageFlagsBitField } from 'discord.js';
export class PingCommand extends Command { export class PingCommand extends Command {
public constructor(context: Command.LoaderContext, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
@ -41,12 +40,13 @@ export class PingCommand extends Command {
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) { public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
const msg = await interaction.reply({ const msg = await interaction.reply({
content: 'Ping?', content: 'Ping?',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
if (isMessageInstance(msg)) { if (msg.resource !== null && msg.resource.message !== null) {
const diff = msg.createdTimestamp - interaction.createdTimestamp; const diff =
msg.resource.message.createdTimestamp - interaction.createdTimestamp;
const ping = Math.round(this.container.client.ws.ping); const ping = Math.round(this.container.client.ws.ping);
return interaction.editReply( return interaction.editReply(
`Pong 🏓! (Round trip took: ${diff}ms. Heartbeat: ${ping}ms.)`, `Pong 🏓! (Round trip took: ${diff}ms. Heartbeat: ${ping}ms.)`,
@ -57,6 +57,11 @@ export class PingCommand extends Command {
} }
public async messageRun(message: Message) { public async messageRun(message: Message) {
if (!message.channel.isSendable()) {
// TODO manage logging/errors properly
return;
}
const msg = await message.channel.send('Ping?'); const msg = await message.channel.send('Ping?');
const diff = msg.createdTimestamp - message.createdTimestamp; const diff = msg.createdTimestamp - message.createdTimestamp;

View File

@ -27,6 +27,10 @@ import '@sapphire/plugin-logger/register';
import { PrismaClient } from '@prisma/client'; import { PrismaClient } from '@prisma/client';
import { Redis } from 'ioredis'; import { Redis } from 'ioredis';
const REDIS_PORT = process.env.REDIS_PORT
? parseInt(process.env.REDIS_PORT)
: undefined;
// Setting up the Sapphire client // Setting up the Sapphire client
const client = new SapphireClient({ const client = new SapphireClient({
defaultPrefix: process.env.DEFAULT_PREFIX, defaultPrefix: process.env.DEFAULT_PREFIX,
@ -49,7 +53,10 @@ const client = new SapphireClient({
tasks: { tasks: {
bull: { bull: {
connection: { connection: {
host: process.env.REDIS_URL, host: process.env.REDIS_HOST,
username: process.env.REDIS_USER,
password: process.env.REDIS_PASSWORD,
port: REDIS_PORT,
}, },
}, },
}, },
@ -62,9 +69,12 @@ const main = async () => {
client.logger.info('Logging in'); client.logger.info('Logging in');
// Create databases // Create databases
container.database = await new PrismaClient(); container.database = new PrismaClient();
container.redis = new Redis({ container.redis = new Redis({
host: process.env.REDIS_URL, host: process.env.REDIS_HOST,
username: process.env.REDIS_USER,
password: process.env.REDIS_PASSWORD,
port: REDIS_PORT,
db: 1, db: 1,
}); });

View File

@ -49,6 +49,11 @@ export class XpListener extends Listener {
// If no counts exist on the database, then create the first count from the bot // If no counts exist on the database, then create the first count from the bot
if (lastCount === null) { if (lastCount === null) {
if (this.container.client.id === null) { if (this.container.client.id === null) {
if (!message.channel.isSendable()) {
// TODO manage logging/errors properly
return;
}
message.channel.send( message.channel.send(
'An unexpected error occurred trying to set up the counting channel, please contact a developer!', 'An unexpected error occurred trying to set up the counting channel, please contact a developer!',
); );
@ -63,6 +68,11 @@ export class XpListener extends Listener {
lastCount = await getLastCount(); lastCount = await getLastCount();
if (lastCount === null) { if (lastCount === null) {
if (!message.channel.isSendable()) {
// TODO manage logging/errors properly
return;
}
message.channel.send( message.channel.send(
'An unexpected error occurred, please contact a developer!', 'An unexpected error occurred, please contact a developer!',
); );

View File

@ -22,6 +22,7 @@
import { Listener } from '@sapphire/framework'; import { Listener } from '@sapphire/framework';
import type { Client } from 'discord.js'; import type { Client } from 'discord.js';
import IDs from '#utils/ids';
export class ReadyListener extends Listener { export class ReadyListener extends Listener {
public constructor( public constructor(
@ -35,8 +36,24 @@ export class ReadyListener extends Listener {
}); });
} }
public run(client: Client) { public async run(client: Client) {
const { username, id } = client.user!; const { username, id } = client.user!;
this.container.logger.info(`Successfully logged in as ${username} (${id})`); this.container.logger.info(`Successfully logged in as ${username} (${id})`);
const botLogChannel = await client.channels.fetch(IDs.channels.logs.bot);
if (botLogChannel === null) {
this.container.logger.error(
'ReadyListener: Could not find the channel for bot logs.',
);
return;
} else if (!botLogChannel.isSendable()) {
this.container.logger.info(
'ReadyListener: No permission to send in bots logs channel.',
);
return;
}
botLogChannel.send('The bot has started up!');
} }
} }

View File

@ -83,6 +83,11 @@ export class Suggestions extends Listener {
return; return;
} }
if (!mailbox.isSendable()) {
// TODO manage logging/errors properly
return;
}
const sent = await mailbox.send({ const sent = await mailbox.send({
embeds: [suggestion], embeds: [suggestion],
content: message.author.toString(), content: message.author.toString(),

View File

@ -18,6 +18,7 @@
*/ */
const devIDs = { const devIDs = {
guild: '999431674972618792',
roles: { roles: {
trusted: '999431675081666599', trusted: '999431675081666599',
booster: '', booster: '',
@ -126,6 +127,7 @@ const devIDs = {
}, },
logs: { logs: {
restricted: '999431681217937513', restricted: '999431681217937513',
bot: '999431681217937516',
economy: '999431681599623198', economy: '999431681599623198',
sus: '999431681599623199', sus: '999431681599623199',
}, },

View File

@ -20,17 +20,18 @@
import devIDs from '#utils/devIDs'; import devIDs from '#utils/devIDs';
let IDs = { let IDs = {
guild: '730907954345279591',
roles: { roles: {
trusted: '731563158011117590', trusted: '1329089675977035879',
booster: '731213264540795012', booster: '731213264540795012',
nonvegan: { nonvegan: {
nonvegan: '774763753308815400', nonvegan: '1329093962153332848',
vegCurious: '832656046572961803', vegCurious: '1329107984227369020',
convinced: '797132019166871612', convinced: '797132019166871612',
}, },
vegan: { vegan: {
vegan: '788114978020392982', vegan: '788114978020392982',
activist: '730915638746546257', activist: '1329112833115295815',
nvAccess: '1076857105648209971', nvAccess: '1076857105648209971',
plus: '798682625619132428', plus: '798682625619132428',
araVegan: '995394977658044506', araVegan: '995394977658044506',
@ -75,7 +76,7 @@ let IDs = {
stageHost: '854893757593419786', stageHost: '854893757593419786',
patron: '765370219207852055', patron: '765370219207852055',
patreon: '993848684640997406', patreon: '993848684640997406',
verifyBlock: '1032765019269640203', verifyBlock: '1329107805130461247',
bookClub: '955516408249352212', bookClub: '955516408249352212',
debateHost: '935508325615931443', debateHost: '935508325615931443',
gameNightHost: '952779915701415966', gameNightHost: '952779915701415966',
@ -128,6 +129,7 @@ let IDs = {
}, },
logs: { logs: {
restricted: '920993034462715925', restricted: '920993034462715925',
bot: '872126272015314966',
economy: '932050015034159174', economy: '932050015034159174',
sus: '872884989950324826', sus: '872884989950324826',
}, },