├── .env.example ├── .github └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── README.md ├── bun.lockb ├── bun_shim ├── index.js ├── rest.js └── server.js ├── commands ├── CHAT_INPUT │ ├── 8ball.ts │ ├── cat.ts │ ├── catsay.ts │ ├── hack.ts │ ├── hello.ts │ ├── help.ts │ ├── kill.ts │ └── modal.ts ├── MESSAGE │ └── eval.ts └── USER │ ├── avatar.ts │ ├── joinedAt.ts │ └── user.ts ├── package.json ├── polyfill.js ├── run.js └── utils └── kill.ts /.env.example: -------------------------------------------------------------------------------- 1 | DISCORD_APP_ID= 2 | DISCORD_BOT_TOKEN= 3 | DISCORD_PUBLIC_KEY= -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "master" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "master" ] 20 | schedule: 21 | - cron: '20 14 * * 2' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /data 2 | 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | lerna-debug.log* 10 | 11 | #config file 12 | config.json 13 | 14 | # Diagnostic reports (https://nodejs.org/api/report.html) 15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 16 | 17 | # Runtime data 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | 23 | # Directory for instrumented libs generated by jscoverage/JSCover 24 | lib-cov 25 | 26 | # Coverage directory used by tools like istanbul 27 | coverage 28 | *.lcov 29 | 30 | # nyc test coverage 31 | .nyc_output 32 | 33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 34 | .grunt 35 | 36 | # Bower dependency directory (https://bower.io/) 37 | bower_components 38 | 39 | # node-waf configuration 40 | .lock-wscript 41 | 42 | # Compiled binary addons (https://nodejs.org/api/addons.html) 43 | build/Release 44 | 45 | # Dependency directories 46 | node_modules/ 47 | jspm_packages/ 48 | 49 | # TypeScript v1 declaration files 50 | typings/ 51 | 52 | # TypeScript cache 53 | *.tsbuildinfo 54 | 55 | # Optional npm cache directory 56 | .npm 57 | 58 | # Optional eslint cache 59 | .eslintcache 60 | 61 | # Microbundle cache 62 | .rpt2_cache/ 63 | .rts2_cache_cjs/ 64 | .rts2_cache_es/ 65 | .rts2_cache_umd/ 66 | 67 | # Optional REPL history 68 | .node_repl_history 69 | 70 | # Output of 'npm pack' 71 | *.tgz 72 | 73 | # Yarn Integrity file 74 | .yarn-integrity 75 | 76 | # dotenv environment variables file 77 | .env 78 | .env.test 79 | 80 | # parcel-bundler cache (https://parceljs.org/) 81 | .cache 82 | 83 | # TernJS port file 84 | .tern-port 85 | 86 | # pm2 87 | .pm2 88 | .pm2.bak 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | kanna 2 | 3 | # Bun Discord Bot 4 | 5 | An interaction Discord bot written in JS and TS using Bun runtime environment. Contains simple get avatar and get user info commands. Starting up the bot only takes `0.02-0.05 ms`. 6 | 7 | ## This bot is created with Bun runtime 8 | 9 | Using [Bun runtime](https://bun.sh). Please visit their website on how to setup Bun runtime. 10 | 11 | ## Getting Started 12 | 13 | ### Cloning the repo 14 | 15 | ```sh 16 | git clone https://github.com/MiraBellierr/bun-discord-bot 17 | ``` 18 | 19 | ### Development 20 | 21 | To run this locally, rename `.env.example` to `.env` and fill in the variables, then you can run `bun run.js` to start a local dev environment and use something like ngrok/cloudflare to tunnel it to a URL. 22 | 23 | ### Using ngrok 24 | 25 | A way to expose a localhost port to a URL is by using ngrok. [download](https://ngrok.com/download) 26 | 27 | First, download and install ngrok, then type `ngrok http 1337` in a new terminal. This will create a https://\*.ngrok.io URL. 28 | 29 | To instruct discord to use your server for all user-created interactions, you must: 30 | 31 | - Go to to [Discord Developers Portal Applications Page](https://discord.com/developers/applications). 32 | - Select / Create a new application. On the application's page, fill the "Interactions endpoint URL" input with the https://\*.ngrok.io/interactions url. 33 | - Invite your application to your server using this URL: `https://discord.com/oauth2/authorize?client_id=[client-id]&scope=applications.commands` 34 | - You're ready to go! 35 | 36 | Be aware that the ngrok URL expires after 2 hours, you'll have to restart the ngrok command after this delay. 37 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiraBellierr/bun-discord-bot/63399e073aeb75ef90c15bf59eb7e2de1b077640/bun.lockb -------------------------------------------------------------------------------- /bun_shim/index.js: -------------------------------------------------------------------------------- 1 | import { join, extname } from "path"; 2 | import { Creator } from "slash-create"; 3 | import { readdirSync, lstatSync } from "fs"; 4 | import { FetchRequestHandler } from "./rest.js"; 5 | export { default as BunServer } from "./server.js"; 6 | 7 | export class BunSlashCreator extends Creator { 8 | constructor(...args) { 9 | super(...args); 10 | this.requestHandler = new FetchRequestHandler(this); 11 | } 12 | 13 | async registerCommandsIn(commandPath, customExtensions = []) { 14 | const commands = []; 15 | const extensions = [".js", ".ts", ".mjs", ".cjs", ...customExtensions]; 16 | 17 | for (const path of find_files_with_extension(commandPath, extensions)) { 18 | try { 19 | commands.push(await import(path)); 20 | } catch (error) { 21 | this.emit( 22 | "error", 23 | new Error(`Failed to load command ${path}: ${error}`) 24 | ); 25 | } 26 | } 27 | 28 | return this.registerCommands(commands, true); 29 | } 30 | } 31 | 32 | function find_files_with_extension(path, extensions, names = []) { 33 | for (const name of readdirSync(path)) { 34 | const p = join(path, name); 35 | const stat = lstatSync(p); 36 | 37 | if (extensions.includes(extname(name))) names.push(p); 38 | else if (stat.isDirectory()) 39 | find_files_with_extension(p, extensions, names); 40 | } 41 | 42 | return names; 43 | } 44 | -------------------------------------------------------------------------------- /bun_shim/rest.js: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "slash-create"; 2 | import { MultipartData } from "slash-create/lib/util/multipartData.js"; 3 | 4 | export class FetchRequestHandler extends RequestHandler { 5 | toString() { 6 | return "[RequestHandler]"; 7 | } 8 | 9 | async request(method, url, auth = true, body, file) { 10 | const creator = this._creator; 11 | 12 | const headers = { 13 | "user-agent": this.userAgent, 14 | "x-ratelimit-precision": "millisecond", 15 | }; 16 | 17 | if (auth) { 18 | headers.authorization = creator.options.token; 19 | if (!headers.authorization) 20 | throw new Error("No token was set in the SlashCreator."); 21 | } 22 | 23 | if (body) { 24 | if (method !== "GET" && method !== "DELETE") { 25 | body = JSON.stringify(body); 26 | headers["content-type"] = "application/json"; 27 | } 28 | } 29 | 30 | if (file) { 31 | if (Array.isArray(file)) { 32 | } else if (file.file) file = [file]; 33 | else throw new Error("Invalid file object."); 34 | 35 | const form = new MultipartData(); 36 | headers[ 37 | "content-type" 38 | ] = `multipart/form-data; boundary=${form.boundary}`; 39 | 40 | for (const f of file) form.attach(f.name, f.file, f.name); 41 | if (body) form.attach("payload_json", JSON.stringify(body)); 42 | 43 | body = Buffer.concat(form.finish()); 44 | } 45 | 46 | const res = await fetch( 47 | encodeURI("https://discord.com" + this.baseURL + url), 48 | { 49 | body, 50 | method, 51 | headers, 52 | } 53 | ); 54 | 55 | if (res.ok) return res.json(); 56 | throw new Error(`${method} got ${res.status} - ${await res.text()}`); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /bun_shim/server.js: -------------------------------------------------------------------------------- 1 | import { Server } from "slash-create"; 2 | import { MultipartData } from "slash-create/lib/util/multipartData.js"; 3 | 4 | export default class BunServer extends Server { 5 | #server = null; 6 | #handler = null; 7 | isWebserver = true; 8 | 9 | constructor() { 10 | super({ alreadyListening: true }); 11 | } 12 | 13 | createEndpoint(path, handler) { 14 | this.#handler = handler; 15 | } 16 | 17 | stop() { 18 | if (this.#server) this.#server.close(); 19 | else throw new Error("BunServer not started"); 20 | } 21 | 22 | listen(port, options = {}) { 23 | const getHandler = () => this.#handler; 24 | 25 | this.#server = Bun.serve({ 26 | port, 27 | ...options, 28 | 29 | async fetch(req) { 30 | const handler = getHandler(); 31 | if (!handler) 32 | return new Response("Server has no handler.", { status: 503 }); 33 | if (req.method !== "POST") 34 | return new Response("Server only supports POST requests.", { 35 | status: 405, 36 | }); 37 | 38 | const reqHeaders = Object.fromEntries(req.headers.entries()); 39 | 40 | const reqBody = await req.json(); 41 | 42 | return await new Promise(async (ok, err) => { 43 | try { 44 | await handler( 45 | { 46 | request: req, 47 | body: reqBody, 48 | response: null, 49 | headers: reqHeaders, 50 | }, 51 | (response) => { 52 | let body = response.body; 53 | const headers = new Headers(); 54 | 55 | if (response.headers) { 56 | for (const key in response.headers) { 57 | headers.set(key, response.headers[key]); 58 | } 59 | } 60 | 61 | if ("string" !== typeof body) { 62 | body = JSON.stringify(body); 63 | headers.set("content-type", "application/json"); 64 | } 65 | 66 | if (response.files) { 67 | const form = new MultipartData(); 68 | headers.set( 69 | "content-type", 70 | `multipart/form-data; boundary=${form.boundary}` 71 | ); 72 | 73 | form.attach("payload_json", body); 74 | for (const file of response.files) 75 | form.attach(file.name, file.file, file.name); 76 | 77 | body = Buffer.concat(form.finish()); 78 | } 79 | 80 | ok(new Response(body, { headers, status: response.status })); 81 | } 82 | ); 83 | } catch (error) { 84 | err(error); 85 | } 86 | }); 87 | }, 88 | }); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /commands/CHAT_INPUT/8ball.ts: -------------------------------------------------------------------------------- 1 | import { SlashCommand, CommandOptionType, SlashCreator } from "slash-create"; 2 | 3 | var fortunes: string[] = [ 4 | "Yes.", 5 | "It is certain.", 6 | "It is decidedly so.", 7 | "Without a doubt.", 8 | "Yes definelty.", 9 | "You may rely on it.", 10 | "As I see it, yes.", 11 | "Most likely.", 12 | "Outlook good.", 13 | "Signs point to yes.", 14 | "Reply hazy, try again.", 15 | "Ask again later.", 16 | "Better not tell you now...", 17 | "Cannot predict now.", 18 | "Concentrate and ask again.", 19 | "Don't count on it.", 20 | "My reply is no.", 21 | "My sources say no.", 22 | "Outlook not so good...", 23 | "Very doubtful.", 24 | ]; 25 | 26 | export default class EightballCommand extends SlashCommand { 27 | constructor(creator: SlashCreator) { 28 | super(creator, { 29 | name: "8ball", 30 | description: "Tells you a fortune", 31 | options: [ 32 | { 33 | type: CommandOptionType.STRING, 34 | name: "question", 35 | description: "The question you want to ask the magic 8ball", 36 | required: true, 37 | }, 38 | ], 39 | }); 40 | 41 | // @ts-ignore 42 | this.filePath = __filename; 43 | } 44 | 45 | async run() { 46 | return fortunes[Math.floor(Math.random() * fortunes.length)]; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /commands/CHAT_INPUT/cat.ts: -------------------------------------------------------------------------------- 1 | import { SlashCommand, CommandContext, SlashCreator } from "slash-create"; 2 | 3 | export default class HelloCommand extends SlashCommand { 4 | constructor(creator: SlashCreator) { 5 | super(creator, { 6 | name: "cat", 7 | description: "Send a cat pic for you to see", 8 | }); 9 | 10 | // @ts-ignore 11 | this.filePath = __filename; 12 | } 13 | 14 | async run(ctx: CommandContext) { 15 | ctx.defer(); 16 | const response = await fetch("https://aws.random.cat/meow?ref=apilist.fun"); 17 | const json = await response.json(); 18 | 19 | return ctx.editOriginal(json.file); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /commands/CHAT_INPUT/catsay.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SlashCommand, 3 | CommandOptionType, 4 | CommandContext, 5 | SlashCreator, 6 | } from "slash-create"; 7 | 8 | export default class CatsayCommand extends SlashCommand { 9 | constructor(creator: SlashCreator) { 10 | super(creator, { 11 | name: "catsay", 12 | description: "Make The Cat say thing of your choice", 13 | options: [ 14 | { 15 | type: CommandOptionType.STRING, 16 | name: "text", 17 | description: "The text you want the cat to say", 18 | required: true, 19 | }, 20 | ], 21 | }); 22 | 23 | // @ts-ignore 24 | this.filePath = __filename; 25 | } 26 | 27 | async run(ctx: CommandContext) { 28 | var text: string = ctx.options.text; 29 | text = encodeURIComponent(text); 30 | 31 | return `https://cataas.com/cat/cute/says/${text}`; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /commands/CHAT_INPUT/hack.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SlashCommand, 3 | CommandOptionType, 4 | CommandContext, 5 | SlashCreator, 6 | User, 7 | } from "slash-create"; 8 | import { FetchRequestHandler } from "../../bun_shim/rest"; 9 | 10 | export default class HackCommand extends SlashCommand { 11 | constructor(creator: SlashCreator) { 12 | super(creator, { 13 | name: "hack", 14 | description: "Hack someone's data", 15 | options: [ 16 | { 17 | type: CommandOptionType.USER, 18 | name: "user", 19 | description: "The user you want to hack", 20 | required: true, 21 | }, 22 | ], 23 | }); 24 | 25 | // @ts-ignore 26 | this.filePath = __filename; 27 | } 28 | 29 | async run(ctx: CommandContext) { 30 | await ctx.defer(); 31 | 32 | const userid = ctx.options.user; 33 | let index = 0; 34 | let time = 1000; 35 | 36 | const user: User = await new FetchRequestHandler(this.creator).request( 37 | "GET", 38 | `/users/${userid}`, 39 | true 40 | ); 41 | 42 | await ctx.send(`Hacking ${user.username}'s data....`); 43 | 44 | while (index < 10) { 45 | await this.hack(ctx, user, index, time); 46 | 47 | index++; 48 | time += 1000; 49 | } 50 | } 51 | 52 | async hack(ctx: CommandContext, user: User, index: number, time: number) { 53 | return new Promise((resolve) => { 54 | const responses = [ 55 | ` Finding ${user.username}'s Email and Password.....`, 56 | ` Found credentials\nE-mail: ${user.username}@gmail.com\nPassword: \\*\\*\\*\\*\\*\\*\\*\\*`, 57 | " Finding other accounts.....", 58 | " Setting up Epic Games account.....", 59 | " Generate free 3 months of Discord Nitro link", 60 | " Storing token in database.....", 61 | " Making request to Discord.com.....", 62 | " Collecting info.....", 63 | " Sending data to Mira.....", 64 | `Finished hacking ${user.username}'s data!`, 65 | ]; 66 | 67 | setTimeout(async () => { 68 | await ctx.editOriginal(responses[index]); 69 | resolve(true); 70 | }, time); 71 | }); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /commands/CHAT_INPUT/hello.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SlashCommand, 3 | CommandOptionType, 4 | CommandContext, 5 | SlashCreator, 6 | } from "slash-create"; 7 | 8 | export default class HelloCommand extends SlashCommand { 9 | constructor(creator: SlashCreator) { 10 | super(creator, { 11 | name: "hello", 12 | description: "Says hello to you.", 13 | options: [ 14 | { 15 | type: CommandOptionType.STRING, 16 | name: "food", 17 | description: "What food do you like?", 18 | }, 19 | ], 20 | }); 21 | 22 | // @ts-ignore 23 | this.filePath = __filename; 24 | } 25 | 26 | async run(ctx: CommandContext) { 27 | return ctx.options.food 28 | ? `You like ${ctx.options.food}? Nice!` 29 | : `Hello, ${ctx.user.username}!`; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /commands/CHAT_INPUT/help.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SlashCommand, 3 | CommandOptionType, 4 | CommandContext, 5 | SlashCreator, 6 | ApplicationCommandType, 7 | } from "slash-create"; 8 | 9 | export default class HelpCommand extends SlashCommand { 10 | constructor(creator: SlashCreator) { 11 | super(creator, { 12 | name: "help", 13 | description: "List of commands", 14 | }); 15 | 16 | // @ts-ignore 17 | this.filePath = __filename; 18 | } 19 | 20 | async run() { 21 | const userCommands: string[] = []; 22 | const chatInputCommands: string[] = []; 23 | const messageCommands: string[] = []; 24 | 25 | (await this.creator.api.getCommands()) 26 | .filter((command) => command.type === ApplicationCommandType.MESSAGE) 27 | .forEach((command) => { 28 | messageCommands.push( 29 | ` > \`${command.name}\` - ${command.description || "No description"}` 30 | ); 31 | }); 32 | 33 | (await this.creator.api.getCommands()) 34 | .filter((command) => command.type === ApplicationCommandType.USER) 35 | .forEach((command) => { 36 | userCommands.push( 37 | ` > \`${command.name}\` - ${command.description || "No description"}` 38 | ); 39 | }); 40 | 41 | (await this.creator.api.getCommands()) 42 | .filter((command) => command.type === ApplicationCommandType.CHAT_INPUT) 43 | .forEach((command) => { 44 | chatInputCommands.push( 45 | `> \`${command.name}\` - ${command.description || "No description"}` 46 | ); 47 | }); 48 | 49 | return `list of commands:\n\n${chatInputCommands.join( 50 | "\n" 51 | )}\n\nThere are also some commands on the user UI:\n${userCommands.join( 52 | "\n" 53 | )}\n\nAnd some commands on the message UI:\n${messageCommands.join( 54 | "\n" 55 | )}\n\nInvite bot here\n> `; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /commands/CHAT_INPUT/kill.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SlashCommand, 3 | CommandOptionType, 4 | CommandContext, 5 | SlashCreator, 6 | User, 7 | } from "slash-create"; 8 | import { FetchRequestHandler } from "../../bun_shim/rest"; 9 | import { kill } from "../../utils/kill"; 10 | 11 | export default class HackCommand extends SlashCommand { 12 | constructor(creator: SlashCreator) { 13 | super(creator, { 14 | name: "kill", 15 | description: "kill someone or is it?", 16 | options: [ 17 | { 18 | type: CommandOptionType.USER, 19 | name: "user", 20 | description: "The user you want to kill", 21 | required: true, 22 | }, 23 | ], 24 | }); 25 | 26 | // @ts-ignore 27 | this.filePath = __filename; 28 | } 29 | 30 | async run(ctx: CommandContext) { 31 | const author = ctx.user; 32 | const userId = ctx.options.user; 33 | 34 | if (author.id === userId) return `*kills <@${author.id}>*`; 35 | 36 | const target: User = await new FetchRequestHandler(this.creator).request( 37 | "GET", 38 | `/users/${userId}`, 39 | true 40 | ); 41 | 42 | return kill(`<@${author.id}>`, `<@${target.id}>`); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /commands/CHAT_INPUT/modal.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SlashCommand, 3 | ComponentType, 4 | TextInputStyle, 5 | CommandContext, 6 | SlashCreator, 7 | } from "slash-create"; 8 | 9 | export default class ModalCommand extends SlashCommand { 10 | constructor(creator: SlashCreator) { 11 | super(creator, { 12 | name: "modal", 13 | description: "Send a cool modal.", 14 | }); 15 | 16 | // @ts-ignore 17 | this.filePath = __filename; 18 | } 19 | 20 | async run(ctx: CommandContext) { 21 | ctx.sendModal( 22 | { 23 | title: "Example Modal", 24 | components: [ 25 | { 26 | type: ComponentType.ACTION_ROW, 27 | components: [ 28 | { 29 | type: ComponentType.TEXT_INPUT, 30 | label: "Text Input", 31 | style: TextInputStyle.SHORT, 32 | custom_id: "text_input", 33 | placeholder: "Type something...", 34 | }, 35 | ], 36 | }, 37 | { 38 | type: ComponentType.ACTION_ROW, 39 | components: [ 40 | { 41 | type: ComponentType.TEXT_INPUT, 42 | label: "Long Text Input", 43 | style: TextInputStyle.PARAGRAPH, 44 | custom_id: "long_text_input", 45 | placeholder: "Type something...", 46 | }, 47 | ], 48 | }, 49 | ], 50 | }, 51 | (mctx) => { 52 | mctx.send( 53 | `Your input: ${mctx.values.text_input}\nYour long input: ${mctx.values.long_text_input}` 54 | ); 55 | } 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /commands/MESSAGE/eval.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SlashCommand, 3 | CommandContext, 4 | SlashCreator, 5 | ApplicationCommandType, 6 | } from "slash-create"; 7 | 8 | export default class EvalCommand extends SlashCommand { 9 | constructor(creator: SlashCreator) { 10 | super(creator, { 11 | name: "evaluate", 12 | type: ApplicationCommandType.MESSAGE, 13 | }); 14 | 15 | // @ts-ignore 16 | this.filePath = __filename; 17 | } 18 | 19 | async run(ctx: CommandContext) { 20 | const author = ctx.user; 21 | 22 | if (author.id !== "548050617889980426") 23 | return "You are not allowed to use this command."; 24 | 25 | try { 26 | const code = ctx.targetMessage!.content; 27 | 28 | const result = eval(code); 29 | 30 | console.log(result); 31 | 32 | return `*evaluates <@${author.id}>*\n\`\`\`js\n${result}\n\`\`\``; 33 | } catch (e) { 34 | return `*evaluates <@${author.id}>*\n\`\`\`js\n${e}\n\`\`\``; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /commands/USER/avatar.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SlashCommand, 3 | ApplicationCommandType, 4 | SlashCreator, 5 | CommandContext, 6 | } from "slash-create"; 7 | 8 | export default class AvatarCommand extends SlashCommand { 9 | constructor(creator: SlashCreator) { 10 | super(creator, { 11 | type: ApplicationCommandType.USER, 12 | name: "Get Avatar URL", 13 | }); 14 | 15 | // @ts-ignore 16 | this.filePath = __filename; 17 | } 18 | 19 | async run(ctx: CommandContext) { 20 | const target = ctx.targetUser; 21 | 22 | const embed = { 23 | author: { 24 | name: ctx.user.username, 25 | icon_url: ctx.user.avatarURL, 26 | }, 27 | title: `${target?.username}'s Avatar`, 28 | color: 0xcd1c6c, 29 | image: { 30 | url: target?.dynamicAvatarURL(undefined, 4096), 31 | }, 32 | timestamp: new Date(), 33 | footer: { 34 | text: `Requested by ${ctx.user.username}`, 35 | }, 36 | }; 37 | 38 | return ctx.send({ embeds: [embed] }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /commands/USER/joinedAt.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SlashCommand, 3 | ApplicationCommandType, 4 | SlashCreator, 5 | CommandContext, 6 | } from "slash-create"; 7 | 8 | export default class JoinedAtCommand extends SlashCommand { 9 | constructor(creator: SlashCreator) { 10 | super(creator, { 11 | type: ApplicationCommandType.USER, 12 | name: "Joined At", 13 | }); 14 | 15 | // @ts-ignore 16 | this.filePath = __filename; 17 | } 18 | 19 | async run(ctx: CommandContext) { 20 | const target = ctx.targetMember; 21 | 22 | return `${target?.user.username} joined at ${new Intl.DateTimeFormat( 23 | "en-US", 24 | { 25 | weekday: "long", 26 | year: "numeric", 27 | month: "long", 28 | day: "numeric", 29 | timeZone: "UTC", 30 | } 31 | ).format(target?.joinedAt)}`; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /commands/USER/user.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SlashCommand, 3 | ApplicationCommandType, 4 | SlashCreator, 5 | CommandContext, 6 | } from "slash-create"; 7 | 8 | export default class UserCommand extends SlashCommand { 9 | constructor(creator: SlashCreator) { 10 | super(creator, { 11 | type: ApplicationCommandType.USER, 12 | name: "Get User Info", 13 | }); 14 | 15 | // @ts-ignore 16 | this.filePath = __filename; 17 | } 18 | 19 | async run(ctx: CommandContext) { 20 | const target = ctx.targetMember; 21 | 22 | const flags = target?.user.flags 23 | .toArray() 24 | .map((flag) => flag.toLowerCase()) 25 | .map((flag) => 26 | flag 27 | .split("_") 28 | .map((f) => f.charAt(0).toUpperCase() + f.slice(1)) 29 | .join(" ") 30 | ) 31 | .join(", "); 32 | 33 | const joinedAt = new Intl.DateTimeFormat("en-US", { 34 | weekday: "long", 35 | year: "numeric", 36 | month: "long", 37 | day: "numeric", 38 | timeZone: "UTC", 39 | }).format(target?.joinedAt); 40 | 41 | const embed = { 42 | author: { 43 | name: ctx.user.username, 44 | icon_url: ctx.user.avatarURL, 45 | }, 46 | title: `${target?.user.username}'s Information`, 47 | description: `**• Username:** ${target?.user.username}\n**• Mention:** ${ 48 | target?.user.mention 49 | }\n**• ID:** ${ 50 | target?.user.id 51 | }\n**• Flags:** ${flags}\n**• Discriminator:** ${ 52 | target?.user.discriminator 53 | }\n**• Type:** ${ 54 | target?.user.bot ? "Bot" : "User" 55 | }\n**• Avatar URL:** [click here](${ 56 | target?.avatarURL 57 | })\n**• Joined At:** ${joinedAt}`, 58 | color: 0xcd1c6c, 59 | image: { 60 | url: target?.dynamicAvatarURL(undefined, 4096), 61 | }, 62 | timestamp: new Date(), 63 | footer: { 64 | text: `Requested by ${ctx.user.username}`, 65 | }, 66 | }; 67 | 68 | return ctx.send({ embeds: [embed] }); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bun-discord-bot", 3 | "version": "0.0.5", 4 | "dependencies": { 5 | "slash-create": "^5.7.1" 6 | } 7 | } -------------------------------------------------------------------------------- /polyfill.js: -------------------------------------------------------------------------------- 1 | Error.captureStackTrace = () => {}; 2 | Buffer.isBuffer = Buffer.isBuffer.bind(Buffer); 3 | -------------------------------------------------------------------------------- /run.js: -------------------------------------------------------------------------------- 1 | // polyfill v8 and node (TODO: fix in bun) 2 | import "./polyfill.js"; 3 | 4 | import path from "node:path"; 5 | import { BunServer, BunSlashCreator } from "./bun_shim/index.js"; 6 | 7 | const client = new BunSlashCreator({ 8 | token: process.env.DISCORD_BOT_TOKEN, 9 | publicKey: process.env.DISCORD_PUBLIC_KEY, 10 | applicationID: process.env.DISCORD_APP_ID, 11 | }); 12 | 13 | // client.on('debug', console.log); 14 | client.on("error", console.error); 15 | 16 | client.withServer(new BunServer()); 17 | await client.registerCommandsIn(path.join(__dirname, "commands")); 18 | 19 | client.syncCommands(); 20 | await client.server.listen(1337); 21 | 22 | // client.server.stop(); // stop server 23 | -------------------------------------------------------------------------------- /utils/kill.ts: -------------------------------------------------------------------------------- 1 | export const kill = (author: string, target: string) => { 2 | const killText = [ 3 | ` after a long day, plops down on the couch with ${target} and turns on The Big Bang Theory. After a Sheldon Cooper joke, ${target} laughs uncontrollably as they die.`, 4 | `${author} Alt+F4'd ${target}.exe!`, 5 | `${author} attempted to play a flute, exploding the head of ${target}.`, 6 | `${author} blew his ear drums out listening to music too hard.`, 7 | `${author} challenges ${target} to a fist fight to the death. ${target} wins.`, 8 | `${author} cleaves the head of ${target} with a keyboard.`, 9 | `${author} crushes ${target} with a fridge.`, 10 | `${author} decapitates ${target} with a sword.`, 11 | `${author} drags ${target}s ears too hard and rips them off.`, 12 | `${author} drowns ${target} in a beer barrel.`, 13 | `${author} drowns ${target} in a tub of hot chocolate. *How was your last drink?*`, 14 | `${author} eviscerates ${target} with a rusty butter knife. Ouch!`, 15 | `${author} feeds toothpaste-filled oreos to ${target}, who were apparently allergic to fluorine. GGWP.`, 16 | `${author} fell in love with ${target} then broke his heart literally.`, 17 | `${author} fires a supersonic frozen turkey at ${target}, killing them instantly.`, 18 | `${author} forgot to leave the car door window open and ${target} dies from overheating`, 19 | `${author} forgot to zombie-proof ${target} lawn... Looks like zombies had a feast last night.`, 20 | `${author} gets ${target} to watch anime with them. ${target} couldn't handle it.`, 21 | `${author} grabs ${target} and shoves them into an auto-freeze machine with some juice and sets the temperature to 100 Kelvin, creating human ice pops.`, 22 | `${author} hired me to kill you, but I don't want to! ${target}`, 23 | `${author} hugs ${target} too hard..`, 24 | `${author} hulk smashes ${target} into a pulp.`, 25 | `${author} killed ${target} by ripping the skin off of their face and making a mask out of it.`, 26 | `${author} kills ${target} after hours of torture.`, 27 | `${author} kills ${target} with a candlestick in the study`, 28 | `${author} kills ${target} with kindness`, 29 | `${author} kills ${target} with their own foot.`, 30 | `${author} murders ${target} with an axe.`, 31 | `${author} pressed delete. It deleted ${target}`, 32 | `${author} pushes ${target} into the cold vacuum of space.`, 33 | `${author} runs ${target} over with a PT Cruiser.`, 34 | `${author} shoots ${target} in the head.`, 35 | `${author} shoots in ${target} mouth with rainbow laser, causing ${target} head to explode with rainbows and ${target} is reborn as unicorn. :unicorn:`, 36 | `${author} shot ${target} using the Starkiller Base!`, 37 | `${author} slips bleach into ${target}'s lemonade.`, 38 | `${author} strangles ${target}.`, 39 | `${author} straps ${target} to an ICBM and sends them to North Korea along with it.`, 40 | `${author} strikes ${target} with the killing curse... *Avada Kedavra!*`, 41 | `${author} tears off ${target}s lips after a kiss.`, 42 | `${author} thicc and collapses ${target}'s rib cage`, 43 | `${author} tries to shoot the broad side of a barn, misses and hits ${target} instead.`, 44 | `${author} turns on Goosebumps(2015 film) on the TV. ${target} being a scaredy-cat, dies of an heart attack.`, 45 | `${author} was so swag that ${target} died due to it. #Swag`, 46 | `${author}, are you sure you want to kill ${target}? They seem nice to me.`, 47 | `${target} accidentally clicked on a popup ad that reads \`Doctors hate us, see the one best trick for dying today!\``, 48 | `${target} accidentally tripped and died while getting up to write their suicide note.`, 49 | `${target} ate a piece of exotic butter. It was so amazing that it killed them.`, 50 | `${target} ate an apple and turned out it was made out of wax. Someone died from wax poisoning later that day.`, 51 | `${target} ate too many laxatives and drowned in their own shit. Ew.`, 52 | `${target} bleeds out after trying to get on \`Dumbest hillbilly moments\`.`, 53 | `${target} bought a fidget spinner and drowned in pussy.`, 54 | `${target} can't be killed, as they are a ghost.`, 55 | `${target} chokes in a trash can.`, 56 | `${target} chokes on a chicken bone.`, 57 | `${target} chokes on cheerios and dies. What an idiot...`, 58 | `${target} cranks up the music system only to realize the volume was at max and the song playing was Baby by Justin Beiber...`, 59 | `${target} \\*\\*\\*\\* in eye, goes blind, runs for help but ran straight onto train tracks and gets plowed by a train.`, 60 | `${target} decided it was a good idea to fight a tiger while smelling like meat. It did not end well.`, 61 | `${target} did not make a meme dank enough and was stoned.`, 62 | `${target} died after fapping 50 times in a row with no break.`, 63 | `${target} died after gaming for 90 hours straight without moving or eating.`, 64 | `${target} died after playing with an edgy razor blade fidget spinner.`, 65 | `${target} died after realizing how shitty their grammar was`, 66 | `${target} died after trying to out-meme Dank Memer.`, 67 | `${target} died an honorable death. Death by snoo snoo.`, 68 | `${target} died because RemindMeBot forgot to remind them to breathe`, 69 | `${target} died because they started playing with a fidget spinner but they realise its 2016 so you start fapping to the old witch in snow white and obama starts mowing their lawn and they jump out of the window and get ripped to pieces by Obama's lawn mower`, 70 | `${target} died due to ${author} being so stupid`, 71 | `${target} died due to eating WAY too many hotdogs in preparation for their date Friday night.`, 72 | `${target} died eating expired and infected raw fish with the filthiest rice in the world as sushi while being constantly stabbed in the scrotum with a 9inch nail sharp enough to stab through kevlar. The soy sauce was cat piss.`, 73 | `${target} died from a high salt intake`, 74 | `${target} died from a swift kick to the brain.`, 75 | `${target} died from a tragic amount of bad succ`, 76 | `${target} died from doing the ice bucket challenge.`, 77 | `${target} died from drinking too much water Huh, I guess it IS possible!.`, 78 | `${target} died from eating cactus needles.`, 79 | `${target} died from eating too much ass.`, 80 | `${target} died from eating too much bread :/`, 81 | `${target} died from ebola.`, 82 | `${target} died from meme underdose :/`, 83 | `${target} died from not eating enough ass.`, 84 | `${target} died from not whacking it enough. (There's a healthy balance, boys)`, 85 | `${target} died from reposting in the wrong neighborhood`, 86 | `${target} died from shitting for 36 hours straight.`, 87 | `${target} died from swallowing rocks too fast`, 88 | `${target} died from too many sunburns.`, 89 | `${target} died from whacking it too much. (There's a healthy balance, boys)`, 90 | `${target} died of oversucc`, 91 | `${target} died when testing a hydrogen bomb. There is nothing left to bury.`, 92 | `${target} died while listening to 'It's every day bro'`, 93 | `${target} died while playing hopscotch on *seemingly* deactivated land mines.`, 94 | `${target} died while trying to find the city of England`, 95 | `${target} died. OOF`, 96 | `${target} dies after swallowing a toothpick.`, 97 | `${target} dies at the hands of ${author}.`, 98 | `${target} dies because they used a bobby pin to lift their eyelashes`, 99 | `${target} dies because they were just too angry.`, 100 | `${target} dies by swearing on a Christian Minecraft server`, 101 | `${target} dies due to lack of friends.`, 102 | `${target} dies from bad succ.`, 103 | `${target} dies from dabbing too hard.`, 104 | `${target} dies from dabbing too hard`, 105 | `${target} dies from disrespecting wahmen.`, 106 | `${target} dies from just being a bad, un-likeable dude.`, 107 | `${target} dies from posting normie memes.`, 108 | `${target} dies from severe dislike of sand. It's coarse and rough and irritating it gets everywhere`, 109 | `${target} dies from watching the emoji movie and enjoying it.`, 110 | `${target} dies in a horrible accident, and it was engineered by ${author}.`, 111 | `${target} dies north of the wall and transforms into a white walker`, 112 | `${target} dies of AIDS.`, 113 | `${target} dies of dysentery.`, 114 | `${target} dies of natural causes.`, 115 | `${target} dies of starvation.`, 116 | `${target} dies on death row via lethal injection after murdering ${author} and their family.`, 117 | `${target} dies, but don't let this distract you from the fact that in 1998, The Undertaker threw Mankind off Hell In A Cell, and plummeted 16 ft through an announcer’s table`, 118 | `${target} dies.`, 119 | `After a struggle, ${target} kills ${author}`, 120 | `${target} disappeared from the universe.`, 121 | `${target} drank some toxic soda before it was recalled.`, 122 | `${target} dropped a Nokia phone on their face and split their skull.`, 123 | `${target} drowned in their own tears.`, 124 | `${target} eats too much copypasta and explodes`, 125 | `${target} fell down a cliff while playing Pokemon Go. Good job on keeping your nose in that puny phone. :iphone:`, 126 | `${target} fell into a pit of angry feminists.`, 127 | `${target} gets hit by a car.`, 128 | `${target} gets stabbed by ${author}`, 129 | `${target} gets struck by lightning.`, 130 | `${target} goes genocide and Sans totally dunks ${target}!`, 131 | `${target} got into a knife fight with the pope. One of them is in hell now.`, 132 | `${target} got stepped on by an elephant.`, 133 | `${target} died from eating too much ass.`, 134 | `${target} has a stroke after a sad miserable existence. They are then devoured by their ample cats.`, 135 | `${target} has been found guilty, time for their execution!`, 136 | `${target} has some bad chinese food, and pays the ultimate price.`, 137 | `${target} is abducted by aliens, and the government kills them to cover it up.`, 138 | `${target} is dead at the hands of ${author}.`, 139 | `${target} is injected with chocolate syrup, which mutates them into a person made out of chocolate. While doing a part-time job at the Daycare, they are devoured by the hungry babies. :chocolate_bar:`, 140 | `${target} is killed by a rabbit with a vicious streak a mile wide`, 141 | `${target} is killed by their own stupidity.`, 142 | `${target} is killed in a robbery gone wrong.`, 143 | `${target} is not able to be killed. Oh, wait, no, ${author} kills them anyway.`, 144 | `${target} is so dumb that they choked on oxygen.`, 145 | `${target} is stuffed into a suit by Freddy on their night guard duty. Oh, not those animatronics again!`, 146 | `${target} is sucked into Minecraft. ${target}, being a noob at the so called Real-Life Minecraft faces the Game Over screen.`, 147 | `${target} killed themselves after seeing the normie memes that ${author} posts.`, 148 | `${target} kills themselves after realizing how dumb ${author} is.`, 149 | `${target} lives, despite ${author}'s murder attempt.`, 150 | `${target} loses the will to live`, 151 | `${target} presses a random button and is teleported to the height of 100m, allowing them to fall to their inevitable death. Moral of the story: Don't go around pressing random buttons.`, 152 | `${target} reads memes till they die.`, 153 | `${target} ripped his heart out..`, 154 | `${target} ripped their own heart out to show their love for ${author}.`, 155 | `${target} screams in terror as they accidentally spawn in the cthulhu while uttering random latin words. Cthulhu grabs ${target} by the right leg and takes them to his dimension yelling, \`Honey, Dinner's ready!\``, 156 | `${target} slipped in the bathroom and choked on the shower curtain.`, 157 | `${target} slips on a banana peel and falls down the stairs.`, 158 | `${target} spins a fidget spinner and when it stops he dies...`, 159 | `${target} steps on a george foreman and dies of waffle foot.`, 160 | `${target} takes an arrow to the knee. And everywhere else.`, 161 | `${target} talked back to mods and got destroyed by the ban hammer.`, 162 | `${target} tips his fedora too far and falls onto the tracks of an oncoming subway.`, 163 | `${target} tried to get crafty, but they accidentally cut themselves with the scissors.:scissors:`, 164 | `${target} tried to get famous on YouTube by live-streaming something dumb. Skydiving while chained to a fridge.`, 165 | `${target} tried to outrun a train, the train won.`, 166 | `${target} tried to pick out the holy grail. He chose... poorly.`, 167 | `${target} tried to play in the street...`, 168 | `${target} trips over his own shoe laces and dies.`, 169 | `${target} vocally opposed the Clintons and then suddenly disappeared.`, 170 | `${target} was a resident of Alderaan before Darth Vader destroyed the planet...`, 171 | `${target} was accused of stealing Neptune's crown...`, 172 | `${target} was charging their Samsung Galaxy Note 7...`, 173 | `${target} was eaten alive by ants`, 174 | `${target} was given a chance to synthesize element 119 (Ununennium) and have it named after them, but they messed up. R.I.P.`, 175 | `${target} was killed by ${author} with baby wipes.`, 176 | `${target} was murdered by ${author} and everyone knows it, but there is no proof.`, 177 | `${target} was scooped by ${author} and their innards are now Ennard.`, 178 | `${target} was teleported to the timeline where Jurassic World was real and they were eaten alive by the Indominus Rex.`, 179 | `${target} was thrown in the crusher of a trash truck by ${author}.`, 180 | `${target} was walking normally when out of the corner of their eye they saw someone do a bottle flip and dab causing ${target} to have a stroke.`, 181 | `${target} watched the Emoji Movie and died of sheer cringe.`, 182 | `${target} went on a ride with a lead balloon.`, 183 | `After getting pushed into the ocean by ${author}, ${target} is eaten by a shark.`, 184 | `After raid of roblox kids entered the server, ${target} died of cancer.`, 185 | `Aids, ${target} died from aids.`, 186 | `Calling upon the divine powers, ${author} smites ${target} and their heathen ways`, 187 | `In a sudden turn of events, I **don't** kill ${target}.`, 188 | `no u`, 189 | `Our lord and savior Gaben strikes ${target} with a lighting bolt.`, 190 | `Sorry, ${author}, I don't like killing people.`, 191 | `The bullet missed Harambe and hit ${target} instead. Yay for Harambe!`, 192 | `While performing colonoscopy on an elephant, ${target} gets their head stuck in the elephants rectum and chokes.`, 193 | ]; 194 | 195 | return killText[Math.floor(Math.random() * killText.length)]; 196 | }; 197 | --------------------------------------------------------------------------------