A powerful and extendable Discord bot, with it's own module system :3 thevoid.cafe/projects/voidy

🔧 Add traefik configuration

Changed files
+181 -2
packages
bot
src
modules
economy
schemas
user
commands
integration
schemas
+5
.env.example
··· 1 + BOT_TOKEN: #Get this from https://discord.com/developers - REQUIRED 2 + BOT_CLIENT_ID: #Get this from https://discord.com/developers - REQUIRED 3 + BOT_ADMINS: #Discord Ids of bot administrators, comma separated 4 + DB_URI: #Your MongoDB connection URI - REQUIRED 5 + ACCESS_TOKEN: # Your API Bearer token
+23 -2
bun.lock
··· 8 8 "oxfmt": "^0.17.0", 9 9 }, 10 10 }, 11 + "packages/api": { 12 + "name": "@voidy/api", 13 + "version": "0.1.0", 14 + "dependencies": { 15 + "@voidy/bot": "workspace:*", 16 + "hono": "^4.11.1", 17 + "mongoose": "^9.0.1", 18 + }, 19 + "devDependencies": { 20 + "@types/bun": "latest", 21 + }, 22 + "peerDependencies": { 23 + "typescript": "^5.9.2", 24 + }, 25 + }, 11 26 "packages/bot": { 12 27 "name": "@voidy/bot", 13 28 "version": "0.1.0", 14 29 "dependencies": { 30 + "@voidy/api": "workspace:*", 15 31 "@voidy/framework": "workspace:*", 16 32 "discord.js": "^14.25.1", 33 + "hono": "^4.11.1", 17 34 "mongoose": "^9.0.1", 18 35 }, 19 36 "devDependencies": { ··· 74 91 75 92 "@sapphire/snowflake": ["@sapphire/snowflake@3.5.3", "", {}, "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ=="], 76 93 77 - "@types/bun": ["@types/bun@1.3.4", "", { "dependencies": { "bun-types": "1.3.4" } }, "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA=="], 94 + "@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="], 78 95 79 96 "@types/node": ["@types/node@24.1.0", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w=="], 80 97 ··· 85 102 "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], 86 103 87 104 "@vladfrangu/async_event_emitter": ["@vladfrangu/async_event_emitter@2.4.6", "", {}, "sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA=="], 105 + 106 + "@voidy/api": ["@voidy/api@workspace:packages/api"], 88 107 89 108 "@voidy/bot": ["@voidy/bot@workspace:packages/bot"], 90 109 ··· 92 111 93 112 "bson": ["bson@7.0.0", "", {}, "sha512-Kwc6Wh4lQ5OmkqqKhYGKIuELXl+EPYSCObVE6bWsp1T/cGkOCBN0I8wF/T44BiuhHyNi1mmKVPXk60d41xZ7kw=="], 94 113 95 - "bun-types": ["bun-types@1.3.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ=="], 114 + "bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="], 96 115 97 116 "discord-api-types": ["discord-api-types@0.38.37", "", {}, "sha512-Cv47jzY1jkGkh5sv0bfHYqGgKOWO1peOrGMkDFM4UmaGMOTgOW8QSexhvixa9sVOiz8MnVOBryWYyw/CEVhj7w=="], 98 117 99 118 "discord.js": ["discord.js@14.25.1", "", { "dependencies": { "@discordjs/builders": "^1.13.0", "@discordjs/collection": "1.5.3", "@discordjs/formatters": "^0.6.2", "@discordjs/rest": "^2.6.0", "@discordjs/util": "^1.2.0", "@discordjs/ws": "^1.2.3", "@sapphire/snowflake": "3.5.3", "discord-api-types": "^0.38.33", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-2l0gsPOLPs5t6GFZfQZKnL1OJNYFcuC/ETWsW4VtKVD/tg4ICa9x+jb9bkPffkMdRpRpuUaO/fKkHCBeiCKh8g=="], 100 119 101 120 "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], 121 + 122 + "hono": ["hono@4.11.1", "", {}, "sha512-KsFcH0xxHes0J4zaQgWbYwmz3UPOOskdqZmItstUG93+Wk1ePBLkLGwbP9zlmh1BFUiL8Qp+Xfu9P7feJWpGNg=="], 102 123 103 124 "kareem": ["kareem@3.0.0", "", {}, "sha512-RKhaOBSPN8L7y4yAgNhDT2602G5FD6QbOIISbjN9D6mjHPeqeg7K+EB5IGSU5o81/X2Gzm3ICnAvQW3x3OP8HA=="], 104 125
+40
docker-compose.yml
··· 1 + # Don't change these development defaults, use a docker-compose.override.yml file instead :) 2 + services: 3 + api: 4 + build: 5 + context: . 6 + dockerfile: packages/api/Dockerfile 7 + env_file: .env 8 + ports: 9 + - "4300:4300" 10 + labels: 11 + - "traefik.enable=true" 12 + - "traefik.http.routers.voidy-api.entrypoints=websecure" 13 + - "traefik.http.routers.voidy-api.rule=Host(`voidy.thevoid.cafe`)" 14 + - "traefik.http.services.voidy-api.loadbalancer.server.port=4300" 15 + networks: 16 + - default 17 + - proxy 18 + restart: unless-stopped 19 + 20 + bot: 21 + build: 22 + context: . 23 + dockerfile: packages/bot/Dockerfile 24 + env_file: .env 25 + depends_on: 26 + - api 27 + networks: 28 + - default 29 + restart: unless-stopped 30 + 31 + mongo: 32 + image: mongo:latest 33 + container_name: voidy_db 34 + ports: 35 + - "27017:27017" 36 + environment: 37 + MONGO_INITDB_ROOT_USERNAME: voidy 38 + MONGO_INITDB_ROOT_PASSWORD: voidy 39 + networks: 40 + - default
+11
packages/bot/Dockerfile
··· 1 + FROM oven/bun:1.1 2 + 3 + WORKDIR /app 4 + 5 + COPY . . 6 + 7 + RUN bun install 8 + 9 + WORKDIR /app/packages/bot 10 + 11 + CMD ["bun", "run", "dev"]
+8
packages/bot/package.json
··· 4 4 "module": "src/index.ts", 5 5 "type": "module", 6 6 "private": true, 7 + "exports": { 8 + "./db": { 9 + "import": "./src/modules/index.ts", 10 + "require": "./src/modules/index.cjs" 11 + } 12 + }, 7 13 "scripts": { 8 14 "dev": "bun --watch ." 9 15 }, ··· 15 21 }, 16 22 "dependencies": { 17 23 "@voidy/framework": "workspace:*", 24 + "@voidy/api": "workspace:*", 18 25 "discord.js": "^14.25.1", 26 + "hono": "^4.11.1", 19 27 "mongoose": "^9.0.1" 20 28 } 21 29 }
+8
packages/bot/src/index.ts
··· 2 2 import { VoidyClient } from "@voidy/framework"; 3 3 import { connect } from "mongoose"; 4 4 5 + //=============================================== 6 + // Discord client initialization 7 + //=============================================== 8 + 5 9 // Client initialization with intents and stuff... 6 10 const client = new VoidyClient({ 7 11 intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages], ··· 22 26 // Token validation and client start 23 27 if (!Bun.env.BOT_TOKEN) throw new Error("[Voidy] Missing bot token"); 24 28 await client.start(Bun.env.BOT_TOKEN, `${import.meta.dirname}/modules`); 29 + 30 + //=============================================== 31 + // API initialization 32 + //===============================================
+1
packages/bot/src/modules/economy/schemas/index.ts
··· 1 + export * from "./UserCurrency";
+2
packages/bot/src/modules/index.ts
··· 1 + export * from "./economy/schemas"; 2 + export * from "./user/schemas";
+65
packages/bot/src/modules/user/commands/integration/update.ts
··· 1 + import { MessageFlags, SlashCommandBuilder } from "discord.js"; 2 + import type { Command } from "@voidy/framework"; 3 + import { UserIntegration } from "../../schemas/UserIntegration"; 4 + 5 + export default { 6 + id: "user.integration.update", 7 + data: new SlashCommandBuilder() 8 + .setName("Update or link integration") 9 + .setDescription("Update one of your external service integrations.") 10 + .addStringOption((option) => 11 + option 12 + .setName("service") 13 + .setDescription("Choose which service to update/link.") 14 + .setRequired(true) 15 + .addChoices( 16 + { name: "Minecraft", value: "minecraft" }, 17 + ), 18 + ) 19 + .addStringOption((option) => 20 + option 21 + .setName("id") 22 + .setDescription("Provide your external service user ID.") 23 + .setRequired(true), 24 + ), 25 + 26 + execute: async (interaction, client) => { 27 + const { options } = interaction; 28 + const service = options.getString("service", true); 29 + const id = options.getString("id", true); 30 + 31 + let userIntegration = await UserIntegration.findOne({ 32 + userId: interaction.user.id, 33 + service: { 34 + type: service, 35 + id: id, 36 + } }); 37 + 38 + if (!userIntegration) { 39 + userIntegration = new UserIntegration({ 40 + userId: interaction.user.id, 41 + service: { 42 + type: service, 43 + id: id, 44 + }, 45 + }); 46 + } 47 + 48 + switch (service) { 49 + case "minecraft": 50 + userIntegration.service.type = service; 51 + userIntegration.service.id = id; 52 + break; 53 + default: 54 + console.error(`Invalid service: ${service}`); 55 + } 56 + 57 + await userIntegration.save(); 58 + await interaction.reply({ 59 + content: `Updated integration \`${service}\` to ID \`${id}\`.`, 60 + flags: [MessageFlags.Ephemeral], 61 + }); 62 + 63 + await client.logger.send(`Updated integration \`${service}\` to ID \`${id}\` for user \`${interaction.user.tag}\``); 64 + }, 65 + } as Command;
+16
packages/bot/src/modules/user/schemas/UserIntegration.ts
··· 1 + import { Schema, model } from "mongoose"; 2 + 3 + export const UserIntegration = model( 4 + "UserIntegration", 5 + new Schema({ 6 + userId: { type: String, required: true }, 7 + service: { 8 + type: Object, 9 + required: true, 10 + default: { 11 + type: { type: String, required: true }, 12 + id: { type: String, required: true }, 13 + } 14 + } 15 + }), 16 + );
+2
packages/bot/src/modules/user/schemas/index.ts
··· 1 + export * from "./UserConfig"; 2 + export * from "./UserIntegration";