From e1300a98b3515a755d63d79efc9badde2c1e5f13 Mon Sep 17 00:00:00 2001 From: Space-Banane Date: Sun, 22 Feb 2026 15:31:37 +0100 Subject: [PATCH] ran prettier lol --- src/commands/dateToEpoch.ts | 105 ++++--- src/commands/ping.ts | 16 +- src/config.ts | 19 +- src/handlers/registerCommands.ts | 52 ++-- src/handlers/startup/checkRules.ts | 271 +++++++++--------- src/handlers/startup/rotatingActivity.ts | 26 +- src/index.ts | 66 +++-- src/lib/commandRegistry.ts | 10 +- src/lib/loadStartupHandlers.ts | 60 ++-- src/listeners/GuildMemberAdd/addUser.ts | 52 ++-- src/listeners/GuildMemberRemove/removeUser.ts | 2 +- src/listeners/messageCreate/helloWorld.ts | 30 +- src/listeners/messageCreate/logMessage.ts | 38 +-- src/web/gitCommit.ts | 90 +++--- src/web/health.ts | 6 +- src/web/index.ts | 12 +- src/webserver.ts | 26 +- 17 files changed, 473 insertions(+), 408 deletions(-) diff --git a/src/commands/dateToEpoch.ts b/src/commands/dateToEpoch.ts index 746534d..4ecd1a3 100644 --- a/src/commands/dateToEpoch.ts +++ b/src/commands/dateToEpoch.ts @@ -2,55 +2,68 @@ import { Client, SlashCommandBuilder } from "discord.js"; import { registerCommand } from "../lib/commandRegistry"; export default async function (_client: Client) { - registerCommand({ - data: new SlashCommandBuilder() - .setName("date-to-epoch") - .setDescription("Converts a date (DD/MM/YYYY) to a Unix epoch timestamp") - .addStringOption((option) => - option - .setName("date") - .setDescription("Date in DD/MM/YYYY format (e.g. 25/12/2026)") - .setRequired(true) - ) as SlashCommandBuilder, - async execute(interaction) { - const input = interaction.options.getString("date", true).trim(); + registerCommand({ + data: new SlashCommandBuilder() + .setName("date-to-epoch") + .setDescription( + "Converts a date (DD/MM/YYYY) to a Unix epoch timestamp", + ) + .addStringOption((option) => + option + .setName("date") + .setDescription( + "Date in DD/MM/YYYY format (e.g. 25/12/2026)", + ) + .setRequired(true), + ) as SlashCommandBuilder, + async execute(interaction) { + const input = interaction.options.getString("date", true).trim(); - const ddmmyyyy = /^(\d{2})\/(\d{2})\/(\d{4})$/.exec(input); - if (!ddmmyyyy) { - await interaction.reply({ - ephemeral: true, - content: "❌ Invalid format. Please use `DD/MM/YYYY` (e.g. `25/12/2026`).", - }); - return; - } + const ddmmyyyy = /^(\d{2})\/(\d{2})\/(\d{4})$/.exec(input); + if (!ddmmyyyy) { + await interaction.reply({ + ephemeral: true, + content: + "❌ Invalid format. Please use `DD/MM/YYYY` (e.g. `25/12/2026`).", + }); + return; + } - const [, dd, mm, yyyy] = ddmmyyyy; - const date = new Date(Number(yyyy), Number(mm) - 1, Number(dd)); + const [, dd, mm, yyyy] = ddmmyyyy; + const date = new Date(Number(yyyy), Number(mm) - 1, Number(dd)); - if (isNaN(date.getTime())) { - await interaction.reply({ - ephemeral: true, - content: "❌ The date you provided is invalid.", - }); - return; - } + if (isNaN(date.getTime())) { + await interaction.reply({ + ephemeral: true, + content: "❌ The date you provided is invalid.", + }); + return; + } - const epoch = Math.floor(date.getTime() / 1000); + const epoch = Math.floor(date.getTime() / 1000); - await interaction.reply({ - ephemeral: true, - embeds: [ - { - title: "📅 Date → Epoch", - color: 0x5865f2, - fields: [ - { name: "Input", value: input, inline: true }, - { name: "Epoch", value: `\`${epoch}\``, inline: true }, - { name: "Preview", value: ` ()`, inline: false }, - ], - }, - ], - }); - }, - }); + await interaction.reply({ + ephemeral: true, + embeds: [ + { + title: "📅 Date → Epoch", + color: 0x5865f2, + fields: [ + { name: "Input", value: input, inline: true }, + { + name: "Epoch", + value: `\`${epoch}\``, + inline: true, + }, + { + name: "Preview", + value: ` ()`, + inline: false, + }, + ], + }, + ], + }); + }, + }); } diff --git a/src/commands/ping.ts b/src/commands/ping.ts index d6c8987..07868ce 100644 --- a/src/commands/ping.ts +++ b/src/commands/ping.ts @@ -2,12 +2,12 @@ import { Client, SlashCommandBuilder } from "discord.js"; import { registerCommand } from "../lib/commandRegistry"; export default async function (_client: Client) { - registerCommand({ - data: new SlashCommandBuilder() - .setName("ping") - .setDescription("Replies with Pong!"), - async execute(interaction) { - await interaction.reply("Pong!"); - }, - }); + registerCommand({ + data: new SlashCommandBuilder() + .setName("ping") + .setDescription("Replies with Pong!"), + async execute(interaction) { + await interaction.reply("Pong!"); + }, + }); } diff --git a/src/config.ts b/src/config.ts index 8ae6524..849bc8e 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,9 +1,6 @@ import { ActivityType } from "discord.js"; - - -export const GUILD_ID = '1475098530505953441'; - +export const GUILD_ID = "1475098530505953441"; export const CHANNELS = { RULES: "1475100731991392539", @@ -20,13 +17,13 @@ export const ROLES = { RULES: "1475100051352191047", MAINTAINERS: "1475099468591272090", COMMUNITY_ADMINS: "1475099507527258218", - I_USE_SHSF: "1475099569019949077" + I_USE_SHSF: "1475099569019949077", }; // Discord modified the way activities work, for now, we'll only use custom ones -export const ROTATE_ACTIVITIES:{content:string;type:ActivityType}[] = [ - {content: "Fixing Bugs", type: ActivityType.Custom}, - {content: "Adding New Features", type: ActivityType.Custom}, - {content: "Improving Performance", type: ActivityType.Custom}, - {content: "Listening to Feedback", type: ActivityType.Custom}, -] \ No newline at end of file +export const ROTATE_ACTIVITIES: { content: string; type: ActivityType }[] = [ + { content: "Fixing Bugs", type: ActivityType.Custom }, + { content: "Adding New Features", type: ActivityType.Custom }, + { content: "Improving Performance", type: ActivityType.Custom }, + { content: "Listening to Feedback", type: ActivityType.Custom }, +]; diff --git a/src/handlers/registerCommands.ts b/src/handlers/registerCommands.ts index efdc802..14ccea7 100644 --- a/src/handlers/registerCommands.ts +++ b/src/handlers/registerCommands.ts @@ -3,34 +3,40 @@ import { getAllCommands, getCommand } from "../lib/commandRegistry"; import { GUILD_ID } from "../config"; export default async function (client: Client) { - const token = process.env.DISCORD_TOKEN!; - const clientId = client.user!.id; // use the ready client's ID + const token = process.env.DISCORD_TOKEN!; + const clientId = client.user!.id; // use the ready client's ID - const rest = new REST().setToken(token); + const rest = new REST().setToken(token); - const commandData = getAllCommands().map((cmd) => cmd.data.toJSON()); + const commandData = getAllCommands().map((cmd) => cmd.data.toJSON()); - await rest.put(Routes.applicationGuildCommands(clientId, GUILD_ID), { - body: commandData, - }); + await rest.put(Routes.applicationGuildCommands(clientId, GUILD_ID), { + body: commandData, + }); - console.log(`Registered ${commandData.length} slash command(s).`); + console.log(`Registered ${commandData.length} slash command(s).`); - client.on(Events.InteractionCreate, async (interaction) => { - if (!interaction.isChatInputCommand()) return; + client.on(Events.InteractionCreate, async (interaction) => { + if (!interaction.isChatInputCommand()) return; - const command = getCommand(interaction.commandName); - if (!command) return; + const command = getCommand(interaction.commandName); + if (!command) return; - try { - await command.execute(interaction); - } catch (err) { - console.error(err); - if (interaction.replied || interaction.deferred) { - await interaction.followUp({ content: "An error occurred.", ephemeral: true }); - } else { - await interaction.reply({ content: "An error occurred.", ephemeral: true }); - } - } - }); + try { + await command.execute(interaction); + } catch (err) { + console.error(err); + if (interaction.replied || interaction.deferred) { + await interaction.followUp({ + content: "An error occurred.", + ephemeral: true, + }); + } else { + await interaction.reply({ + content: "An error occurred.", + ephemeral: true, + }); + } + } + }); } diff --git a/src/handlers/startup/checkRules.ts b/src/handlers/startup/checkRules.ts index aac6f6f..9634f9d 100644 --- a/src/handlers/startup/checkRules.ts +++ b/src/handlers/startup/checkRules.ts @@ -1,155 +1,152 @@ import { - Client, - EmbedBuilder, - Events, - GuildMember, - Partials, - TextChannel, + Client, + EmbedBuilder, + Events, + GuildMember, + Partials, + TextChannel, } from "discord.js"; import { CHANNELS, GUILD_ID, ROLES } from "../../config"; const RULES_EMOJI = "✅"; export default async function checkRules(client: Client): Promise { - const guild = await client.guilds.fetch(GUILD_ID); - const channel = (await guild.channels.fetch( - CHANNELS["RULES"], - )) as TextChannel; + const guild = await client.guilds.fetch(GUILD_ID); + const channel = (await guild.channels.fetch( + CHANNELS["RULES"], + )) as TextChannel; - if (!channel || !channel.isTextBased()) { - console.error( - "[checkRules] Rules channel not found or is not a text channel.", + if (!channel || !channel.isTextBased()) { + console.error( + "[checkRules] Rules channel not found or is not a text channel.", + ); + return; + } + + // Look for an existing rules message posted by the bot + const messages = await channel.messages.fetch({ limit: 50 }); + const existingMessage = messages.find( + (m) => m.author.id === client.user!.id, ); - return; - } - // Look for an existing rules message posted by the bot - const messages = await channel.messages.fetch({ limit: 50 }); - const existingMessage = messages.find((m) => m.author.id === client.user!.id); + if (!existingMessage) { + const embed1 = new EmbedBuilder() + .setTitle("📜 Server Rules — Part 1: General Conduct") + .setColor(0x5865f2) + .setDescription( + "Please read and follow all rules to keep this server a safe and welcoming place for everyone.", + ) + .addFields( + { + name: "1. Be Respectful", + value: "Treat all members with respect. Harassment, hate speech, slurs, and discrimination of any kind will not be tolerated.", + }, + { + name: "2. No Spam", + value: "Do not spam messages, emojis, or mentions. Keep conversations relevant to the channel topic.", + }, + { + name: "3. No NSFW Content", + value: "Explicit, graphic, or otherwise inappropriate content is strictly prohibited.", + }, + { + name: "4. No Self-Promotion", + value: "Do not advertise other servers, social media accounts, or services without prior approval from staff.", + }, + { + name: "5. Follow Discord ToS", + value: "All members must comply with [Discord's Terms of Service](https://discord.com/terms) and [Community Guidelines](https://discord.com/guidelines).", + }, + ) + .setTimestamp(); - if (!existingMessage) { - const embed1 = new EmbedBuilder() - .setTitle("📜 Server Rules — Part 1: General Conduct") - .setColor(0x5865f2) - .setDescription( - "Please read and follow all rules to keep this server a safe and welcoming place for everyone.", - ) - .addFields( - { - name: "1. Be Respectful", - value: - "Treat all members with respect. Harassment, hate speech, slurs, and discrimination of any kind will not be tolerated.", - }, - { - name: "2. No Spam", - value: - "Do not spam messages, emojis, or mentions. Keep conversations relevant to the channel topic.", - }, - { - name: "3. No NSFW Content", - value: - "Explicit, graphic, or otherwise inappropriate content is strictly prohibited.", - }, - { - name: "4. No Self-Promotion", - value: - "Do not advertise other servers, social media accounts, or services without prior approval from staff.", - }, - { - name: "5. Follow Discord ToS", - value: - "All members must comply with [Discord's Terms of Service](https://discord.com/terms) and [Community Guidelines](https://discord.com/guidelines).", - }, - ) - .setTimestamp(); + const embed2 = new EmbedBuilder() + .setTitle("📋 Server Rules — Part 2: Channels & Community") + .setColor(0x57f287) + .addFields( + { + name: "6. Use the Right Channels", + value: "Keep discussions in their appropriate channels. Off-topic conversations belong in the designated channel.", + }, + { + name: "7. No Doxxing", + value: "Sharing personal or private information of others without their explicit consent is strictly forbidden.", + }, + { + name: "8. English in Main Channels", + value: "Please communicate in English in main channels so all members and staff can participate.", + }, + { + name: "9. Listen to Staff", + value: "Follow the instructions of moderators and admins. If you disagree with a decision, open a support ticket calmly.", + }, + { + name: "10. Have Fun!", + value: "This is a community — be kind, stay positive, and enjoy your time here. 🎉", + }, + ) + .setFooter({ + text: "React with ✅ below to accept the rules and gain access to the server.", + }) + .setTimestamp(); - const embed2 = new EmbedBuilder() - .setTitle("📋 Server Rules — Part 2: Channels & Community") - .setColor(0x57f287) - .addFields( - { - name: "6. Use the Right Channels", - value: - "Keep discussions in their appropriate channels. Off-topic conversations belong in the designated channel.", - }, - { - name: "7. No Doxxing", - value: - "Sharing personal or private information of others without their explicit consent is strictly forbidden.", - }, - { - name: "8. English in Main Channels", - value: - "Please communicate in English in main channels so all members and staff can participate.", - }, - { - name: "9. Listen to Staff", - value: - "Follow the instructions of moderators and admins. If you disagree with a decision, open a support ticket calmly.", - }, - { - name: "10. Have Fun!", - value: - "This is a community — be kind, stay positive, and enjoy your time here. 🎉", - }, - ) - .setFooter({ - text: "React with ✅ below to accept the rules and gain access to the server.", - }) - .setTimestamp(); - - const rulesMessage = await channel.send({ embeds: [embed1, embed2] }); - await rulesMessage.react(RULES_EMOJI); - console.log("[checkRules] Rules message posted successfully."); - } else { - console.log("[checkRules] Rules message already exists, skipping."); - } - - // Grant role when a member reacts with ✅ in the rules channel - client.on(Events.MessageReactionAdd, async (reaction, user) => { - if (user.bot) return; - if (reaction.message.channelId !== CHANNELS.RULES) return; - if (reaction.emoji.name !== RULES_EMOJI) return; - - // Resolve partials if needed - try { - if (reaction.partial) await reaction.fetch(); - if (user.partial) await user.fetch(); - } catch { - return; + const rulesMessage = await channel.send({ embeds: [embed1, embed2] }); + await rulesMessage.react(RULES_EMOJI); + console.log("[checkRules] Rules message posted successfully."); + } else { + console.log("[checkRules] Rules message already exists, skipping."); } - try { - const member = await guild.members.fetch(user.id); - await member.roles.add(ROLES.RULES); - console.log(`[checkRules] Granted rules role to ${user.tag ?? user.id}`); - } catch (err) { - console.error("[checkRules] Failed to add rules role:", err); - } - }); + // Grant role when a member reacts with ✅ in the rules channel + client.on(Events.MessageReactionAdd, async (reaction, user) => { + if (user.bot) return; + if (reaction.message.channelId !== CHANNELS.RULES) return; + if (reaction.emoji.name !== RULES_EMOJI) return; - // Remove role when the reaction is removed - client.on(Events.MessageReactionRemove, async (reaction, user) => { - if (user.bot) return; - if (reaction.message.channelId !== CHANNELS.RULES) return; - if (reaction.emoji.name !== RULES_EMOJI) return; + // Resolve partials if needed + try { + if (reaction.partial) await reaction.fetch(); + if (user.partial) await user.fetch(); + } catch { + return; + } - try { - if (reaction.partial) await reaction.fetch(); - if (user.partial) await user.fetch(); - } catch (err) { - console.error("[checkRules] Failed to fetch reaction or user:", err); - return; - } + try { + const member = await guild.members.fetch(user.id); + await member.roles.add(ROLES.RULES); + console.log( + `[checkRules] Granted rules role to ${user.tag ?? user.id}`, + ); + } catch (err) { + console.error("[checkRules] Failed to add rules role:", err); + } + }); - try { - const member = await guild.members.fetch(user.id); - await member.roles.remove(ROLES.RULES); - console.log( - `[checkRules] Removed rules role from ${user.tag ?? user.id}`, - ); - } catch (err) { - console.error("[checkRules] Failed to remove rules role:", err); - } - }); + // Remove role when the reaction is removed + client.on(Events.MessageReactionRemove, async (reaction, user) => { + if (user.bot) return; + if (reaction.message.channelId !== CHANNELS.RULES) return; + if (reaction.emoji.name !== RULES_EMOJI) return; + + try { + if (reaction.partial) await reaction.fetch(); + if (user.partial) await user.fetch(); + } catch (err) { + console.error( + "[checkRules] Failed to fetch reaction or user:", + err, + ); + return; + } + + try { + const member = await guild.members.fetch(user.id); + await member.roles.remove(ROLES.RULES); + console.log( + `[checkRules] Removed rules role from ${user.tag ?? user.id}`, + ); + } catch (err) { + console.error("[checkRules] Failed to remove rules role:", err); + } + }); } diff --git a/src/handlers/startup/rotatingActivity.ts b/src/handlers/startup/rotatingActivity.ts index 2131a91..efefa87 100644 --- a/src/handlers/startup/rotatingActivity.ts +++ b/src/handlers/startup/rotatingActivity.ts @@ -2,17 +2,19 @@ import { ActivityType, Client } from "discord.js"; import { ROTATE_ACTIVITIES } from "../../config"; export default async function rotatingActivity(client: Client): Promise { - if (!client.user) { - console.error("[rotatingActivity] Client is not ready yet."); - return; - } + if (!client.user) { + console.error("[rotatingActivity] Client is not ready yet."); + return; + } - client.user.setActivity("Fixing Bugs", { type: ActivityType.Custom }); - let index = 0; - setInterval(() => { - const activity = ROTATE_ACTIVITIES[index]; - client.user!.setActivity(activity.content, { type: activity.type }); - index = (index + 1) % ROTATE_ACTIVITIES.length; - console.log(`[rotatingActivity] Updated activity to: ${activity.content} (${activity.type})`); - }, 60000); // Rotate every 60 seconds + client.user.setActivity("Fixing Bugs", { type: ActivityType.Custom }); + let index = 0; + setInterval(() => { + const activity = ROTATE_ACTIVITIES[index]; + client.user!.setActivity(activity.content, { type: activity.type }); + index = (index + 1) % ROTATE_ACTIVITIES.length; + console.log( + `[rotatingActivity] Updated activity to: ${activity.content} (${activity.type})`, + ); + }, 60000); // Rotate every 60 seconds } diff --git a/src/index.ts b/src/index.ts index b0045bf..6c4ecdd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,47 +1,55 @@ -import { Client, Events, GatewayIntentBits, Message, Partials } from 'discord.js'; -import dotenv from 'dotenv'; -import path from 'path'; -import loadModulesFromDir from './lib/loadStartupHandlers'; +import { + Client, + Events, + GatewayIntentBits, + Message, + Partials, +} from "discord.js"; +import dotenv from "dotenv"; +import path from "path"; +import loadModulesFromDir from "./lib/loadStartupHandlers"; import * as mongoDB from "mongodb"; -import { env } from 'process'; -import webserver from './webserver'; +import { env } from "process"; +import webserver from "./webserver"; dotenv.config(); -const dbclient: mongoDB.MongoClient = new mongoDB.MongoClient( - env.MONGO_DB! -); +const dbclient: mongoDB.MongoClient = new mongoDB.MongoClient(env.MONGO_DB!); const db: mongoDB.Db = dbclient.db(env.DB_NAME!); export { db, dbclient }; - const client = new Client({ - intents: [ - GatewayIntentBits.Guilds, - GatewayIntentBits.GuildMessages, - GatewayIntentBits.GuildMessageReactions, - GatewayIntentBits.GuildMembers, - GatewayIntentBits.MessageContent, - ], - partials: [Partials.Message, Partials.Channel, Partials.Reaction, Partials.User], + intents: [ + GatewayIntentBits.Guilds, + GatewayIntentBits.GuildMessages, + GatewayIntentBits.GuildMessageReactions, + GatewayIntentBits.GuildMembers, + GatewayIntentBits.MessageContent, + ], + partials: [ + Partials.Message, + Partials.Channel, + Partials.Reaction, + Partials.User, + ], }); let readyClient: typeof client | null = null; client.once(Events.ClientReady, async (rc) => { - readyClient = rc; - console.log(`Logged in as ${rc.user.tag}`); + readyClient = rc; + console.log(`Logged in as ${rc.user.tag}`); - const dirs = [ - path.join(__dirname, 'commands'), // load commands first - path.join(__dirname, 'handlers'), - path.join(__dirname, 'listeners'), - ]; + const dirs = [ + path.join(__dirname, "commands"), // load commands first + path.join(__dirname, "handlers"), + path.join(__dirname, "listeners"), + ]; - for (const dir of dirs) { - await loadModulesFromDir(dir, client); - } + for (const dir of dirs) { + await loadModulesFromDir(dir, client); + } }); // Start webserver in the background @@ -50,4 +58,4 @@ webserver().catch(console.error); // Exports export { client, readyClient }; -client.login(process.env.DISCORD_TOKEN); \ No newline at end of file +client.login(process.env.DISCORD_TOKEN); diff --git a/src/lib/commandRegistry.ts b/src/lib/commandRegistry.ts index deed97d..998906d 100644 --- a/src/lib/commandRegistry.ts +++ b/src/lib/commandRegistry.ts @@ -1,20 +1,20 @@ import { ChatInputCommandInteraction, SlashCommandBuilder } from "discord.js"; export interface Command { - data: SlashCommandBuilder; - execute: (interaction: ChatInputCommandInteraction) => Promise; + data: SlashCommandBuilder; + execute: (interaction: ChatInputCommandInteraction) => Promise; } const commands = new Map(); export function registerCommand(command: Command) { - commands.set(command.data.name, command); + commands.set(command.data.name, command); } export function getCommand(name: string): Command | undefined { - return commands.get(name); + return commands.get(name); } export function getAllCommands(): Command[] { - return Array.from(commands.values()); + return Array.from(commands.values()); } diff --git a/src/lib/loadStartupHandlers.ts b/src/lib/loadStartupHandlers.ts index 4692932..0d41c2f 100644 --- a/src/lib/loadStartupHandlers.ts +++ b/src/lib/loadStartupHandlers.ts @@ -2,31 +2,39 @@ import { Client } from "discord.js"; import fs from "fs"; import path from "path"; -export default async function loadModulesFromDir(dir: string, client: Client): Promise { - if (!fs.existsSync(dir)) { - console.warn(`[loadModules] Directory not found: ${dir}`); - return; - } - - const entries = fs.readdirSync(dir, { withFileTypes: true }); - - for (const entry of entries) { - const fullPath = path.join(dir, entry.name); - - if (entry.isDirectory()) { - await loadModulesFromDir(fullPath, client); // recurse - } else if (entry.isFile() && /\.(ts|js)$/.test(entry.name)) { - try { - const mod = await import(fullPath); - if (typeof mod.default === "function") { - await mod.default(client); - console.log(`[loadModules] Loaded: ${fullPath}`); - } else { - console.warn(`[loadModules] No default export function in: ${entry.name}`); - } - } catch (err) { - console.error(`[loadModules] Failed to load ${entry.name}:`, err); - } +export default async function loadModulesFromDir( + dir: string, + client: Client, +): Promise { + if (!fs.existsSync(dir)) { + console.warn(`[loadModules] Directory not found: ${dir}`); + return; + } + + const entries = fs.readdirSync(dir, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + + if (entry.isDirectory()) { + await loadModulesFromDir(fullPath, client); // recurse + } else if (entry.isFile() && /\.(ts|js)$/.test(entry.name)) { + try { + const mod = await import(fullPath); + if (typeof mod.default === "function") { + await mod.default(client); + console.log(`[loadModules] Loaded: ${fullPath}`); + } else { + console.warn( + `[loadModules] No default export function in: ${entry.name}`, + ); + } + } catch (err) { + console.error( + `[loadModules] Failed to load ${entry.name}:`, + err, + ); + } + } } - } } diff --git a/src/listeners/GuildMemberAdd/addUser.ts b/src/listeners/GuildMemberAdd/addUser.ts index e2aff15..7c8f761 100644 --- a/src/listeners/GuildMemberAdd/addUser.ts +++ b/src/listeners/GuildMemberAdd/addUser.ts @@ -3,38 +3,38 @@ import { db } from "../.."; // Adds a User to the database as they just joined the server. This is used for tracking purposes and to store user data in the future. export default async function addUserToDB(client: Client): Promise { - client.on(Events.GuildMemberAdd, async (member) => { - console.log(`[addUserToDB] [${member.user.tag}] Joined the server`); + client.on(Events.GuildMemberAdd, async (member) => { + console.log(`[addUserToDB] [${member.user.tag}] Joined the server`); - await db.collection("users").insertOne({ - userId: member.user.id, - authorTag: member.user.tag, - content: member.user.username, - guildId: member.guild.id, - timestamp: new Date(member.joinedTimestamp!), - isBot: member.user.bot, + await db.collection("users").insertOne({ + userId: member.user.id, + authorTag: member.user.tag, + content: member.user.username, + guildId: member.guild.id, + timestamp: new Date(member.joinedTimestamp!), + isBot: member.user.bot, + }); }); - }); } // Silenced becuase its very noisy on every message export async function addUserToDBManually(member: GuildMember): Promise { - // console.log(`[addUserToDBManually] [${member.user.tag}] Adding user to DB manually`); + // console.log(`[addUserToDBManually] [${member.user.tag}] Adding user to DB manually`); - const existingUser = await db - .collection("users") - .findOne({ userId: member.user.id }); - if (existingUser) { - // console.log(`[addUserToDBManually] [${member.user.tag}] User already exists in DB, skipping.`); - return; - } + const existingUser = await db + .collection("users") + .findOne({ userId: member.user.id }); + if (existingUser) { + // console.log(`[addUserToDBManually] [${member.user.tag}] User already exists in DB, skipping.`); + return; + } - await db.collection("users").insertOne({ - userId: member.user.id, - authorTag: member.user.tag, - content: member.user.username, - guildId: member.guild.id, - timestamp: new Date(member.joinedTimestamp!), - isBot: member.user.bot, - }); + await db.collection("users").insertOne({ + userId: member.user.id, + authorTag: member.user.tag, + content: member.user.username, + guildId: member.guild.id, + timestamp: new Date(member.joinedTimestamp!), + isBot: member.user.bot, + }); } diff --git a/src/listeners/GuildMemberRemove/removeUser.ts b/src/listeners/GuildMemberRemove/removeUser.ts index 9bb816a..c982324 100644 --- a/src/listeners/GuildMemberRemove/removeUser.ts +++ b/src/listeners/GuildMemberRemove/removeUser.ts @@ -11,4 +11,4 @@ export default async function removeUserFromDB(client: Client): Promise { guildId: member.guild.id, }); }); -} \ No newline at end of file +} diff --git a/src/listeners/messageCreate/helloWorld.ts b/src/listeners/messageCreate/helloWorld.ts index bb00fad..03bad76 100644 --- a/src/listeners/messageCreate/helloWorld.ts +++ b/src/listeners/messageCreate/helloWorld.ts @@ -1,20 +1,20 @@ import { Client, Events, Message } from "discord.js"; export default async function helloWorld(client: Client): Promise { - client.on(Events.MessageCreate, (message: Message) => { - // Ignore messages from bots - if (message.author.bot) return; + client.on(Events.MessageCreate, (message: Message) => { + // Ignore messages from bots + if (message.author.bot) return; - if (message.content.toLowerCase().replace("!", "") === "hello world") { - message.reply({ - embeds: [ - { - title: "Hello, World!", - description: "Hello from the SHSF Team!", - color: 0x00ff00, - }, - ], - }); - } - }); + if (message.content.toLowerCase().replace("!", "") === "hello world") { + message.reply({ + embeds: [ + { + title: "Hello, World!", + description: "Hello from the SHSF Team!", + color: 0x00ff00, + }, + ], + }); + } + }); } diff --git a/src/listeners/messageCreate/logMessage.ts b/src/listeners/messageCreate/logMessage.ts index 9325a43..6063bbc 100644 --- a/src/listeners/messageCreate/logMessage.ts +++ b/src/listeners/messageCreate/logMessage.ts @@ -3,24 +3,26 @@ import { db } from "../.."; import { addUserToDBManually } from "../GuildMemberAdd/addUser"; export default async function logMessage(client: Client): Promise { - client.on(Events.MessageCreate, async (message: Message) => { - if (!message.guildId) return; // Only log messages from guilds (ignore DMs) - if (!message.member) return; // Ignore messages without member info (should be rare) - - // Log ALL message inlcuding bots - console.log(`[logMessage] [${message.author.tag}] Sent a message (${message.content.length} chars)`); + client.on(Events.MessageCreate, async (message: Message) => { + if (!message.guildId) return; // Only log messages from guilds (ignore DMs) + if (!message.member) return; // Ignore messages without member info (should be rare) - await db.collection("messages").insertOne({ - messageId: message.id, - authorId: message.author.id, - authorTag: message.author.tag, - content: message.content, - channelId: message.channelId, - guildId: message.guildId, - timestamp: new Date(message.createdTimestamp), + // Log ALL message inlcuding bots + console.log( + `[logMessage] [${message.author.tag}] Sent a message (${message.content.length} chars)`, + ); + + await db.collection("messages").insertOne({ + messageId: message.id, + authorId: message.author.id, + authorTag: message.author.tag, + content: message.content, + channelId: message.channelId, + guildId: message.guildId, + timestamp: new Date(message.createdTimestamp), + }); + + // Does this user exist in our database? If not, add them (this is for users who were in the server before the bot was added) + await addUserToDBManually(message.member); }); - - // Does this user exist in our database? If not, add them (this is for users who were in the server before the bot was added) - await addUserToDBManually(message.member); - }); } diff --git a/src/web/gitCommit.ts b/src/web/gitCommit.ts index ad70474..fe2e706 100644 --- a/src/web/gitCommit.ts +++ b/src/web/gitCommit.ts @@ -1,23 +1,28 @@ -import { Express, Request, Response } from 'express'; -import { EmbedBuilder, TextChannel } from 'discord.js'; -import { CHANNELS } from '../config'; -import { client, db } from '../index'; +import { Express, Request, Response } from "express"; +import { EmbedBuilder, TextChannel } from "discord.js"; +import { CHANNELS } from "../config"; +import { client, db } from "../index"; const configured_channel = CHANNELS.UPDATES; export default async function gitCommitPOST(app: Express) { - app.post('/git-commit', async (req: Request, res: Response) => { + app.post("/git-commit", async (req: Request, res: Response) => { try { - const event = req.headers['x-github-event'] as string; + const event = req.headers["x-github-event"] as string; // Acknowledge ping events - if (event === 'ping') { - console.log('[WEB-gitCommit] Received GitHub ping event'); - return res.status(200).json({ success: true, message: 'pong' }); + if (event === "ping") { + console.log("[WEB-gitCommit] Received GitHub ping event"); + return res.status(200).json({ success: true, message: "pong" }); } - if (event !== 'push') { - return res.status(200).json({ success: true, message: `Event '${event}' ignored` }); + if (event !== "push") { + return res + .status(200) + .json({ + success: true, + message: `Event '${event}' ignored`, + }); } const body = req.body; @@ -25,20 +30,20 @@ export default async function gitCommitPOST(app: Express) { const pusher = body.pusher; const commits: any[] = body.commits ?? []; const headCommit = body.head_commit; - const ref: string = body.ref ?? ''; - const branch = ref.replace('refs/heads/', ''); - const compareUrl: string = body.compare ?? ''; + const ref: string = body.ref ?? ""; + const branch = ref.replace("refs/heads/", ""); + const compareUrl: string = body.compare ?? ""; const forced: boolean = body.forced ?? false; if (!repo || !headCommit) { - return res.status(400).json({ error: 'Invalid push payload' }); + return res.status(400).json({ error: "Invalid push payload" }); } // Build commit list (max X) const SHOW_MAX = 5; const commitLines = commits.slice(0, SHOW_MAX).map((c: any) => { const shortId = c.id.substring(0, 7); - const msg = c.message.split('\n')[0].substring(0, 64); + const msg = c.message.split("\n")[0].substring(0, 64); return `[\`${shortId}\`](${c.url}) ${msg}...`; }); @@ -48,7 +53,9 @@ export default async function gitCommitPOST(app: Express) { const embed = new EmbedBuilder() .setColor(forced ? 0xff4444 : 0x2ea44f) - .setTitle(`${forced ? '⚠️ Force Push' : '📦 New Push'} to \`${branch}\``) + .setTitle( + `${forced ? "⚠️ Force Push" : "📦 New Push"} to \`${branch}\``, + ) .setURL(compareUrl) .setAuthor({ name: pusher.name, @@ -56,28 +63,45 @@ export default async function gitCommitPOST(app: Express) { url: `https://github.com/${pusher.name}`, }) .addFields( - { name: '🌿 Branch', value: `\`${branch}\``, inline: true }, - { name: `📝 Commits (${commits.length})`, value: commitLines.join('\n') || '_No commits_' }, + { name: "🌿 Branch", value: `\`${branch}\``, inline: true }, + { + name: `📝 Commits (${commits.length})`, + value: commitLines.join("\n") || "_No commits_", + }, ) - .setFooter({ text: `Delivery: ${req.headers['x-github-delivery'] ?? 'unknown'}` }) - .setTimestamp(headCommit.timestamp ? new Date(headCommit.timestamp) : new Date()); + .setFooter({ + text: `Delivery: ${req.headers["x-github-delivery"] ?? "unknown"}`, + }) + .setTimestamp( + headCommit.timestamp + ? new Date(headCommit.timestamp) + : new Date(), + ); - const channel = await client.channels.fetch(configured_channel) as TextChannel | null; + const channel = (await client.channels.fetch( + configured_channel, + )) as TextChannel | null; if (!channel || !channel.isTextBased()) { - console.error('[WEB-gitCommit] Configured channel not found or not text-based'); - return res.status(500).json({ error: 'Discord channel unavailable' }); + console.error( + "[WEB-gitCommit] Configured channel not found or not text-based", + ); + return res + .status(500) + .json({ error: "Discord channel unavailable" }); } const message = await channel.send({ embeds: [embed] }); - console.log(`[WEB-gitCommit] Push event sent to configured channel (${commits.length} commits on ${branch})`); + console.log( + `[WEB-gitCommit] Push event sent to configured channel (${commits.length} commits on ${branch})`, + ); // Reactions for engagement - await message.react('👍'); - await message.react('🔥'); - await message.react('🤯'); + await message.react("👍"); + await message.react("🔥"); + await message.react("🤯"); // Add to DB - await db.collection('git_commits').insertOne({ + await db.collection("git_commits").insertOne({ repository: repo.full_name, pusher: pusher.name, branch, @@ -89,8 +113,10 @@ export default async function gitCommitPOST(app: Express) { return res.status(200).json({ success: true }); } catch (error) { - console.error('[WEB-gitCommit] Error handling git commit:', error); - return res.status(500).json({ success: false, error: 'Internal Server Error' }); + console.error("[WEB-gitCommit] Error handling git commit:", error); + return res + .status(500) + .json({ success: false, error: "Internal Server Error" }); } }); -} \ No newline at end of file +} diff --git a/src/web/health.ts b/src/web/health.ts index 26f1b19..69f5c3d 100644 --- a/src/web/health.ts +++ b/src/web/health.ts @@ -1,7 +1,7 @@ -import { Express } from 'express'; +import { Express } from "express"; export default function healthRoute(app: Express) { - app.get('/health', (req, res) => { - res.status(200).send('ok'); + app.get("/health", (req, res) => { + res.status(200).send("ok"); }); } diff --git a/src/web/index.ts b/src/web/index.ts index 3671865..a63db21 100644 --- a/src/web/index.ts +++ b/src/web/index.ts @@ -1,11 +1,11 @@ import { Express, Request, Response } from "express"; export default async function gitCommitPOST(app: Express) { - app.get("/", (req: Request, res: Response) => { - res.status(200).json({ message: "Hello, world!" }); - }); + app.get("/", (req: Request, res: Response) => { + res.status(200).json({ message: "Hello, world!" }); + }); - app.post("/", (req: Request, res: Response) => { - res.status(200).json({ message: "Hello, world!" }); - }); + app.post("/", (req: Request, res: Response) => { + res.status(200).json({ message: "Hello, world!" }); + }); } diff --git a/src/webserver.ts b/src/webserver.ts index e663c26..17f3595 100644 --- a/src/webserver.ts +++ b/src/webserver.ts @@ -1,6 +1,6 @@ -import express from 'express'; -import fs from 'fs'; -import path from 'path'; +import express from "express"; +import fs from "fs"; +import path from "path"; export default async function webserver() { const app = express(); @@ -8,13 +8,15 @@ export default async function webserver() { app.use(express.json()); - const webDir = path.join(__dirname, 'web'); + const webDir = path.join(__dirname, "web"); if (fs.existsSync(webDir)) { - const files = fs.readdirSync(webDir).filter(f => f.endsWith('.ts') || f.endsWith('.js')); + const files = fs + .readdirSync(webDir) + .filter((f) => f.endsWith(".ts") || f.endsWith(".js")); for (const file of files) { const mod = await import(path.join(webDir, file)); const handler = mod.default ?? mod; - if (typeof handler === 'function') { + if (typeof handler === "function") { await handler(app); console.log(`[WEB] Loaded web route: ${file}`); } @@ -25,7 +27,7 @@ export default async function webserver() { console.log(`[WEB] Web server is running on port ${PORT}`); }); - const IgnoredPaths = ['/favicon.ico', '/robots.txt', "/", "/hello"]; + const IgnoredPaths = ["/favicon.ico", "/robots.txt", "/", "/hello"]; const KnownPaths = ["/git-commit", "/", "/health"]; // log all incoming requests @@ -34,10 +36,14 @@ export default async function webserver() { return next(); } if (!KnownPaths.includes(req.url)) { - console.warn(`[WEB] Unknown Route request: ${req.method} ${req.url} {${req.ip}}`); + console.warn( + `[WEB] Unknown Route request: ${req.method} ${req.url} {${req.ip}}`, + ); } else { - console.log(`[WEB] Trusted Route request: ${req.method} ${req.url} {${req.ip}}`); + console.log( + `[WEB] Trusted Route request: ${req.method} ${req.url} {${req.ip}}`, + ); } next(); }); -} \ No newline at end of file +}