├── .gitattributes ├── .gitignore ├── .hintrc ├── .vscode └── settings.json ├── LICENSE ├── examples └── modal │ ├── dbi.js │ ├── login.js │ ├── package.json │ ├── publish.js │ └── src │ ├── chatInput.js │ ├── components.js │ ├── event.js │ ├── interactionlocales.js │ ├── locales.js │ └── modal.js ├── generated └── namespaceData.d.ts ├── package.json ├── pnpm-lock.yaml ├── readme.md ├── src ├── DBI.ts ├── Events.ts ├── data │ └── eventMap.json ├── index.ts ├── methods │ ├── handleMessageCommands.ts │ ├── hookEventListeners.ts │ ├── hookInteractionListeners.ts │ └── publishInteractions.ts ├── types │ ├── ApplicationRoleConnectionMetadata.ts │ ├── Builders │ │ ├── ButtonBuilder.ts │ │ ├── ChannelSelectMenuBuilder.ts │ │ ├── MentionableSelectMenuBuilder.ts │ │ ├── ModalBuilder.ts │ │ ├── RoleSelectMenuBuilder.ts │ │ ├── StringSelectMenuBuilder.ts │ │ └── UserSelectMenuBuilder.ts │ ├── ChatInput │ │ ├── ChatInput.ts │ │ └── ChatInputOptions.ts │ ├── Components │ │ ├── Button.ts │ │ ├── ChannelSelectMenu.ts │ │ ├── MentionableSelectMenu.ts │ │ ├── Modal.ts │ │ ├── RoleSelectMenu.ts │ │ ├── StringSelectMenu.ts │ │ └── UserSelectMenu.ts │ ├── Event.ts │ ├── Interaction.ts │ └── other │ │ ├── CustomEvent.ts │ │ ├── FakeMessageInteraction.ts │ │ ├── InteractionLocale.ts │ │ ├── Locale.ts │ │ ├── MessageContextMenu.ts │ │ └── UserContextMenu.ts └── utils │ ├── MemoryStore.ts │ ├── UtilTypes.ts │ ├── customId.ts │ ├── permissions.ts │ ├── recursiveImport.ts │ ├── recursiveUnload.ts │ └── unloadModule.ts └── tsconfig.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | dist/ 11 | 12 | # Diagnostic reports (https://nodejs.org/api/report.html) 13 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 14 | 15 | # Runtime data 16 | pids 17 | *.pid 18 | *.seed 19 | *.pid.lock 20 | 21 | # Directory for instrumented libs generated by jscoverage/JSCover 22 | lib-cov 23 | 24 | # Coverage directory used by tools like istanbul 25 | coverage 26 | *.lcov 27 | 28 | # nyc test coverage 29 | .nyc_output 30 | 31 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 32 | .grunt 33 | 34 | # Bower dependency directory (https://bower.io/) 35 | bower_components 36 | 37 | # node-waf configuration 38 | .lock-wscript 39 | 40 | # Compiled binary addons (https://nodejs.org/api/addons.html) 41 | build/Release 42 | 43 | # Dependency directories 44 | node_modules/ 45 | jspm_packages/ 46 | 47 | # Snowpack dependency directory (https://snowpack.dev/) 48 | web_modules/ 49 | 50 | # TypeScript cache 51 | *.tsbuildinfo 52 | 53 | # Optional npm cache directory 54 | .npm 55 | 56 | # Optional eslint cache 57 | .eslintcache 58 | 59 | # Optional stylelint cache 60 | .stylelintcache 61 | 62 | # Microbundle cache 63 | .rpt2_cache/ 64 | .rts2_cache_cjs/ 65 | .rts2_cache_es/ 66 | .rts2_cache_umd/ 67 | 68 | # Optional REPL history 69 | .node_repl_history 70 | 71 | # Output of 'npm pack' 72 | *.tgz 73 | 74 | # Yarn Integrity file 75 | .yarn-integrity 76 | 77 | # dotenv environment variable files 78 | .env 79 | .env.development.local 80 | .env.test.local 81 | .env.production.local 82 | .env.local 83 | 84 | # parcel-bundler cache (https://parceljs.org/) 85 | .cache 86 | .parcel-cache 87 | 88 | # Next.js build output 89 | .next 90 | out 91 | 92 | # Nuxt.js build / generate output 93 | .nuxt 94 | 95 | 96 | # Gatsby files 97 | .cache/ 98 | # Comment in the public line in if your project uses Gatsby and not Next.js 99 | # https://nextjs.org/blog/next-9-1#public-directory-support 100 | # public 101 | 102 | # vuepress build output 103 | .vuepress/dist 104 | 105 | # vuepress v2.x temp and cache directory 106 | .temp 107 | .cache 108 | 109 | # Serverless directories 110 | .serverless/ 111 | 112 | # FuseBox cache 113 | .fusebox/ 114 | 115 | # DynamoDB Local files 116 | .dynamodb/ 117 | 118 | # TernJS port file 119 | .tern-port 120 | 121 | # Stores VSCode versions used for testing VSCode extensions 122 | .vscode-test 123 | 124 | # yarn v2 125 | .yarn/cache 126 | .yarn/unplugged 127 | .yarn/build-state.yml 128 | .yarn/install-state.gz 129 | .pnp.* 130 | 131 | examples/modal/package-lock.json 132 | yarn.lock -------------------------------------------------------------------------------- /.hintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "development" 4 | ], 5 | "hints": { 6 | "typescript-config/strict": "off" 7 | } 8 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.wordBasedSuggestions": "off" 3 | } -------------------------------------------------------------------------------- /examples/modal/dbi.js: -------------------------------------------------------------------------------- 1 | const { createDBI } = require("@mostfeatured/dbi"); 2 | 3 | let dbi = createDBI("modal-example", { 4 | strict: true, 5 | discord: { 6 | token: "", 7 | options: { 8 | intents: [ 9 | "Guilds", 10 | "MessageContent", 11 | "GuildMessages", 12 | "GuildMessageTyping", 13 | ] 14 | } 15 | }, 16 | defaults: { 17 | locale: "en", 18 | defaultMemberPermissions: ["SendMessages"], 19 | directMessages: false 20 | }, 21 | references: { 22 | autoClear: { 23 | ttl: 60 * 1000 * 60, 24 | check: 60 * 1000 25 | } 26 | } 27 | }); 28 | 29 | 30 | module.exports = dbi; -------------------------------------------------------------------------------- /examples/modal/login.js: -------------------------------------------------------------------------------- 1 | const { recursiveImport } = require("@mostfeatured/dbi"); 2 | const dbi = require("./dbi"); 3 | 4 | (async () => { 5 | await recursiveImport("./src"); 6 | 7 | await dbi.load(); 8 | await dbi.login(); 9 | 10 | await dbi.client.user.setActivity({ 11 | name: "hello world!" 12 | }); 13 | 14 | console.log(`Logged in! ${dbi.client.user.tag} (${dbi.client.user.id})`); 15 | })(); -------------------------------------------------------------------------------- /examples/modal/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-project", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "keywords": [], 9 | "author": "risy", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@mostfeatured/dbi": "^0.0.43", 13 | "discord.js": "^14.3.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/modal/publish.js: -------------------------------------------------------------------------------- 1 | const { recursiveImport } = require("@mostfeatured/dbi"); 2 | const dbi = require("./dbi"); 3 | 4 | (async () => { 5 | await recursiveImport("./src"); 6 | 7 | await dbi.load(); 8 | await dbi.publish("Guild", ""); 9 | // await dbi.publish("Global"); 10 | await dbi.unload(); 11 | 12 | console.log("Published!"); 13 | })(); -------------------------------------------------------------------------------- /examples/modal/src/chatInput.js: -------------------------------------------------------------------------------- 1 | const dbi = require("../dbi"); 2 | const Discord = require("discord.js"); 3 | 4 | dbi.register(({ ChatInput, ChatInputOptions }) => { 5 | ChatInput({ 6 | name: "cinsiyet seç", 7 | description: "Cinsiyet seçmenizi sağlar.", 8 | onExecute({ interaction, locale }) { 9 | let gender = interaction.options.get("cinsiyet").value; 10 | let genderNames = locale.user.data.genders; 11 | let genderText = locale.user.data.genderText(interaction.user, genderNames[gender]()); 12 | 13 | interaction.reply({ 14 | content: genderText, 15 | components: [ 16 | { 17 | type: Discord.ComponentType.ActionRow, 18 | components: [ 19 | dbi.interaction("viewGender").toJSON({ override: { label: locale.user.data.clickText() }, reference: { ttl: 1000 * 60 * 10, data: [gender] } }), 20 | ] 21 | } 22 | ] 23 | }); 24 | }, 25 | options: [ 26 | ChatInputOptions.stringChoices({ 27 | name: "cinsiyet", 28 | description: "Seçeceğiniz cinsiyet.", 29 | required: true, 30 | choices: [ 31 | { name: "Erkek", value: "erkek" }, 32 | { name: "Kadın", value: "kadın" }, 33 | { name: "Diğer", value: "diğer" }, 34 | ] 35 | }) 36 | ], 37 | }); 38 | }); -------------------------------------------------------------------------------- /examples/modal/src/components.js: -------------------------------------------------------------------------------- 1 | const dbi = require("../dbi"); 2 | const Discord = require("discord.js"); 3 | 4 | dbi.register(({ Button }) => { 5 | Button({ 6 | name: "viewGender", 7 | onExecute({ interaction, locale, data }) { 8 | interaction.showModal(dbi.interaction("my-modal").toJSON({ 9 | override: { 10 | title: locale.user.data.modal.title(), 11 | components: [ 12 | { 13 | type: Discord.ComponentType.ActionRow, 14 | components: [ 15 | { 16 | customId: "name", 17 | type: Discord.ComponentType.TextInput, 18 | style: Discord.TextInputStyle.Short, 19 | label: locale.user.data.modal.label(), 20 | } 21 | ] 22 | }, 23 | ], 24 | }, 25 | data 26 | })); 27 | }, 28 | options: { 29 | style: Discord.ButtonStyle.Danger, 30 | label: "example", 31 | } 32 | }); 33 | }); -------------------------------------------------------------------------------- /examples/modal/src/event.js: -------------------------------------------------------------------------------- 1 | const dbi = require("../dbi"); 2 | 3 | dbi.register(({ Event })=>{ 4 | Event({ 5 | name: "ready", 6 | id: "botIsReady", 7 | onExecute() { 8 | console.log(`Bot ready!`); 9 | } 10 | }); 11 | 12 | Event({ 13 | name: "messageCreate", 14 | onExecute({ message }) { 15 | if (dbi.client.user.id === message.author.id) return; 16 | 17 | message.channel.send("hello !"); 18 | } 19 | }) 20 | }); -------------------------------------------------------------------------------- /examples/modal/src/interactionlocales.js: -------------------------------------------------------------------------------- 1 | const dbi = require("../dbi"); 2 | 3 | dbi.register(({ InteractionLocale })=>{ 4 | InteractionLocale({ 5 | name: "cinsiyet seç", 6 | data: { 7 | en: { 8 | name: "select gender", 9 | description: "Allows you to select a gender.", 10 | options: { 11 | cinsiyet: { 12 | name: "gender", 13 | description: "Select your gender correctly.", 14 | choices: { 15 | "Erkek": "Male", 16 | "Kadın": "Female", 17 | "Diğer": "Other" 18 | } 19 | } 20 | } 21 | } 22 | } 23 | }); 24 | }); -------------------------------------------------------------------------------- /examples/modal/src/locales.js: -------------------------------------------------------------------------------- 1 | const dbi = require("../dbi"); 2 | dbi.register(({ Locale })=>{ 3 | Locale({ 4 | name: "tr", 5 | data: { 6 | genders: { 7 | erkek: "Erkek", 8 | kadın: "Kadın", 9 | diğer: "Diğer", 10 | }, 11 | modal: { 12 | title: "İsmin nedir ?", 13 | label: "İsim", 14 | text: "Senin adın {0}" 15 | }, 16 | genderText: "{0} adlı üye {1} cinsiyetini seçti.", 17 | clickText: "Bana tıkla!", 18 | } 19 | }); 20 | 21 | Locale({ 22 | name: "en", 23 | data: { 24 | genders: { 25 | erkek: "Male", 26 | kadın: "Female", 27 | diğer: "Other" 28 | }, 29 | modal: { 30 | title: "What is your name?", 31 | label: "Name", 32 | text: "Your name is {0}" 33 | }, 34 | genderText: "{0}, picked {1} gender.", 35 | clickText: "Click me!", 36 | } 37 | }); 38 | }); -------------------------------------------------------------------------------- /examples/modal/src/modal.js: -------------------------------------------------------------------------------- 1 | const dbi = require('../dbi'); 2 | const Discord = require("discord.js"); 3 | 4 | dbi.register(({ Modal }) => { 5 | Modal({ 6 | name: "my-modal", 7 | onExecute({ interaction, locale, data }) { 8 | const text = locale.user.data.modal.text(interaction.fields.getField('name').value, data[0]); 9 | interaction.reply(text); 10 | }, 11 | options: { 12 | title: "example", 13 | components: [ 14 | { 15 | type: Discord.ComponentType.ActionRow, 16 | components: [ 17 | { 18 | customId: "name", 19 | type: Discord.ComponentType.TextInput, 20 | style: Discord.TextInputStyle.Short, 21 | label: "example", 22 | } 23 | ] 24 | }, 25 | ], 26 | } 27 | }) 28 | }); -------------------------------------------------------------------------------- /generated/namespaceData.d.ts: -------------------------------------------------------------------------------- 1 | import { DBICustomEvent } from "../src/types/other/CustomEvent.js"; 2 | import { DBIBaseInteraction, TDBIInteractions } from "../src/types/Interaction"; 3 | import { DBILangObject, TDBILocaleString } from "../src/types/other/Locale.js"; 4 | export interface NamespaceData { 5 | [k: string]: { 6 | contentLocale: DBILangObject; 7 | interactionMapping: { [k: string]: DBIBaseInteraction }; 8 | eventNames: string; 9 | localeNames: TDBILocaleString; 10 | customEvents: { }, 11 | clientNamespaces: string; 12 | } 13 | } 14 | 15 | export type NamespaceEnums = keyof NamespaceData; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@discordjs/rest": "^2.4.0", 4 | "async-and-quick": "^1.0.2", 5 | "discord-api-types": "^0.37.100", 6 | "discord-hybrid-sharding": "^2.2.6", 7 | "discord.js": "^14.16.2", 8 | "lodash": "^4.17.21", 9 | "plsargs": "^0.1.6", 10 | "snakecase-keys": "^8.0.1", 11 | "stuffs": "^0.1.42", 12 | "tslib": "^2.6.3" 13 | }, 14 | "name": "@mostfeatured/dbi", 15 | "version": "0.1.32", 16 | "main": "dist/index.js", 17 | "type": "commonjs", 18 | "private": false, 19 | "scripts": { 20 | "test": "npx tsc && node dist/test", 21 | "build": "npx tsc", 22 | "prepublish": "npx tsc", 23 | "publish:dev": "npx tsc && npm publish --tag dev", 24 | "publish:release": "npx tsc && npm publish" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/MostFeatured/DiscordBotInfrastructure.git" 29 | }, 30 | "author": "Kıraç Armağan Önal", 31 | "license": "GPL-3.0-or-later", 32 | "bugs": { 33 | "url": "https://github.com/MostFeatured/DiscordBotInfrastructure/issues" 34 | }, 35 | "homepage": "https://github.com/MostFeatured/DiscordBotInfrastructure#readme", 36 | "description": "", 37 | "devDependencies": { 38 | "@types/lodash": "^4.14.182", 39 | "typescript": "^5.2.2" 40 | }, 41 | "publishConfig": { 42 | "access": "public", 43 | "registry": "https://registry.npmjs.org/" 44 | } 45 | } -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | dependencies: 11 | '@discordjs/rest': 12 | specifier: ^2.4.0 13 | version: 2.4.0 14 | async-and-quick: 15 | specifier: ^1.0.2 16 | version: 1.0.2 17 | discord-api-types: 18 | specifier: ^0.37.100 19 | version: 0.37.100 20 | discord-hybrid-sharding: 21 | specifier: ^2.2.6 22 | version: 2.2.6 23 | discord.js: 24 | specifier: ^14.16.2 25 | version: 14.16.2 26 | lodash: 27 | specifier: ^4.17.21 28 | version: 4.17.21 29 | plsargs: 30 | specifier: ^0.1.6 31 | version: 0.1.6 32 | snakecase-keys: 33 | specifier: ^8.0.1 34 | version: 8.0.1 35 | stuffs: 36 | specifier: ^0.1.42 37 | version: 0.1.42 38 | tslib: 39 | specifier: ^2.6.3 40 | version: 2.6.3 41 | devDependencies: 42 | '@types/lodash': 43 | specifier: ^4.14.182 44 | version: 4.17.6 45 | typescript: 46 | specifier: ^5.2.2 47 | version: 5.5.2 48 | 49 | packages: 50 | 51 | '@discordjs/builders@1.9.0': 52 | resolution: {integrity: sha512-0zx8DePNVvQibh5ly5kCEei5wtPBIUbSoE9n+91Rlladz4tgtFbJ36PZMxxZrTEOQ7AHMZ/b0crT/0fCy6FTKg==} 53 | engines: {node: '>=18'} 54 | 55 | '@discordjs/collection@1.5.3': 56 | resolution: {integrity: sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==} 57 | engines: {node: '>=16.11.0'} 58 | 59 | '@discordjs/collection@2.1.0': 60 | resolution: {integrity: sha512-mLcTACtXUuVgutoznkh6hS3UFqYirDYAg5Dc1m8xn6OvPjetnUlf/xjtqnnc47OwWdaoCQnHmHh9KofhD6uRqw==} 61 | engines: {node: '>=18'} 62 | 63 | '@discordjs/collection@2.1.1': 64 | resolution: {integrity: sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==} 65 | engines: {node: '>=18'} 66 | 67 | '@discordjs/formatters@0.5.0': 68 | resolution: {integrity: sha512-98b3i+Y19RFq1Xke4NkVY46x8KjJQjldHUuEbCqMvp1F5Iq9HgnGpu91jOi/Ufazhty32eRsKnnzS8n4c+L93g==} 69 | engines: {node: '>=18'} 70 | 71 | '@discordjs/rest@2.4.0': 72 | resolution: {integrity: sha512-Xb2irDqNcq+O8F0/k/NaDp7+t091p+acb51iA4bCKfIn+WFWd6HrNvcsSbMMxIR9NjcMZS6NReTKygqiQN+ntw==} 73 | engines: {node: '>=18'} 74 | 75 | '@discordjs/util@1.1.1': 76 | resolution: {integrity: sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g==} 77 | engines: {node: '>=18'} 78 | 79 | '@discordjs/ws@1.1.1': 80 | resolution: {integrity: sha512-PZ+vLpxGCRtmr2RMkqh8Zp+BenUaJqlS6xhgWKEZcgC/vfHLEzpHtKkB0sl3nZWpwtcKk6YWy+pU3okL2I97FA==} 81 | engines: {node: '>=16.11.0'} 82 | 83 | '@sapphire/async-queue@1.5.2': 84 | resolution: {integrity: sha512-7X7FFAA4DngXUl95+hYbUF19bp1LGiffjJtu7ygrZrbdCSsdDDBaSjB7Akw0ZbOu6k0xpXyljnJ6/RZUvLfRdg==} 85 | engines: {node: '>=v14.0.0', npm: '>=7.0.0'} 86 | 87 | '@sapphire/async-queue@1.5.3': 88 | resolution: {integrity: sha512-x7zadcfJGxFka1Q3f8gCts1F0xMwCKbZweM85xECGI0hBTeIZJGGCrHgLggihBoprlQ/hBmDR5LKfIPqnmHM3w==} 89 | engines: {node: '>=v14.0.0', npm: '>=7.0.0'} 90 | 91 | '@sapphire/shapeshift@4.0.0': 92 | resolution: {integrity: sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==} 93 | engines: {node: '>=v16'} 94 | 95 | '@sapphire/snowflake@3.5.3': 96 | resolution: {integrity: sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==} 97 | engines: {node: '>=v14.0.0', npm: '>=7.0.0'} 98 | 99 | '@types/lodash@4.17.6': 100 | resolution: {integrity: sha512-OpXEVoCKSS3lQqjx9GGGOapBeuW5eUboYHRlHP9urXPX25IKZ6AnP5ZRxtVf63iieUbsHxLn8NQ5Nlftc6yzAA==} 101 | 102 | '@types/node@20.14.9': 103 | resolution: {integrity: sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==} 104 | 105 | '@types/ws@8.5.10': 106 | resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==} 107 | 108 | '@vladfrangu/async_event_emitter@2.4.0': 109 | resolution: {integrity: sha512-eNb/9DMwNvhhgn1UuQ8Rl90jhj9PBkYH4oQ522TkiWUVWRfbh3PjdOTFkVGNKs5+xUXalkgFrUSwtY8u0g0S4g==} 110 | engines: {node: '>=v14.0.0', npm: '>=7.0.0'} 111 | 112 | '@vladfrangu/async_event_emitter@2.4.6': 113 | resolution: {integrity: sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA==} 114 | engines: {node: '>=v14.0.0', npm: '>=7.0.0'} 115 | 116 | async-and-quick@1.0.2: 117 | resolution: {integrity: sha512-xALyAv1NGiyfGaQ5TEGMrzPS6CfvC3ItelP0s6XgULZkYQKa66MjR9qTM0o+tJjNmDRr80WszKuUKjrrgC84lQ==} 118 | 119 | camel-case@4.1.2: 120 | resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} 121 | 122 | chillout@5.0.0: 123 | resolution: {integrity: sha512-xCyFUOrkOVBSaZ1pKXUZcrKhMsPkWh4jWPa9rp4zE+wHE5h4NTO7z8K5rLhV989dtQatDOoMhTe9Vi2S96QEhQ==} 124 | engines: {node: '>=8.10.0'} 125 | 126 | discord-api-types@0.37.100: 127 | resolution: {integrity: sha512-a8zvUI0GYYwDtScfRd/TtaNBDTXwP5DiDVX7K5OmE+DRT57gBqKnwtOC5Ol8z0mRW8KQfETIgiB8U0YZ9NXiCA==} 128 | 129 | discord-api-types@0.37.83: 130 | resolution: {integrity: sha512-urGGYeWtWNYMKnYlZnOnDHm8fVRffQs3U0SpE8RHeiuLKb/u92APS8HoQnPTFbnXmY1vVnXjXO4dOxcAn3J+DA==} 131 | 132 | discord-api-types@0.37.97: 133 | resolution: {integrity: sha512-No1BXPcVkyVD4ZVmbNgDKaBoqgeQ+FJpzZ8wqHkfmBnTZig1FcH3iPPersiK1TUIAzgClh2IvOuVUYfcWLQAOA==} 134 | 135 | discord-hybrid-sharding@2.2.6: 136 | resolution: {integrity: sha512-I/MaEg3+/DD1oDZx5kDWXyajkKqiJwG4nxF5hv8nLBfioFSO19sXjaq2z9klMJRINvPhX0zNAj52dRRNuzqyzQ==} 137 | 138 | discord.js@14.16.2: 139 | resolution: {integrity: sha512-VGNi9WE2dZIxYM8/r/iatQQ+3LT8STW4hhczJOwm+DBeHq66vsKDCk8trChNCB01sMO9crslYuEMeZl2d7r3xw==} 140 | engines: {node: '>=18'} 141 | 142 | dot-case@3.0.4: 143 | resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} 144 | 145 | fast-deep-equal@3.1.3: 146 | resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} 147 | 148 | lodash.snakecase@4.1.1: 149 | resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} 150 | 151 | lodash@4.17.21: 152 | resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} 153 | 154 | lower-case@2.0.2: 155 | resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} 156 | 157 | magic-bytes.js@1.10.0: 158 | resolution: {integrity: sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==} 159 | 160 | map-obj@4.3.0: 161 | resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} 162 | engines: {node: '>=8'} 163 | 164 | no-case@3.0.4: 165 | resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} 166 | 167 | pascal-case@3.1.2: 168 | resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} 169 | 170 | plsargs@0.1.6: 171 | resolution: {integrity: sha512-WQz2xmuliJ8HtNsSKhEQf9dA148NZB0uWiCKKRssI24nHERXNU1kbj/wnN4JIoV1pXJe0O4mcteMy6eZczBA3A==} 172 | 173 | snake-case@3.0.4: 174 | resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} 175 | 176 | snakecase-keys@8.0.1: 177 | resolution: {integrity: sha512-Sj51kE1zC7zh6TDlNNz0/Jn1n5HiHdoQErxO8jLtnyrkJW/M5PrI7x05uDgY3BO7OUQYKCvmeMurW6BPUdwEOw==} 178 | engines: {node: '>=18'} 179 | 180 | stuffs@0.1.42: 181 | resolution: {integrity: sha512-fSJOEYscAuxUeVKSe55oak0E5dGonBqWRFRr2OZFJkfKG9393aB7LdpqhDut5LX6o8C5T3UD8bVUdANXoWLB8A==} 182 | 183 | ts-mixer@6.0.4: 184 | resolution: {integrity: sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==} 185 | 186 | tslib@2.6.3: 187 | resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} 188 | 189 | type-fest@4.25.0: 190 | resolution: {integrity: sha512-bRkIGlXsnGBRBQRAY56UXBm//9qH4bmJfFvq83gSz41N282df+fjy8ofcEgc1sM8geNt5cl6mC2g9Fht1cs8Aw==} 191 | engines: {node: '>=16'} 192 | 193 | typescript@5.5.2: 194 | resolution: {integrity: sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==} 195 | engines: {node: '>=14.17'} 196 | hasBin: true 197 | 198 | undici-types@5.26.5: 199 | resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} 200 | 201 | undici@6.19.8: 202 | resolution: {integrity: sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==} 203 | engines: {node: '>=18.17'} 204 | 205 | ws@8.17.1: 206 | resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} 207 | engines: {node: '>=10.0.0'} 208 | peerDependencies: 209 | bufferutil: ^4.0.1 210 | utf-8-validate: '>=5.0.2' 211 | peerDependenciesMeta: 212 | bufferutil: 213 | optional: true 214 | utf-8-validate: 215 | optional: true 216 | 217 | snapshots: 218 | 219 | '@discordjs/builders@1.9.0': 220 | dependencies: 221 | '@discordjs/formatters': 0.5.0 222 | '@discordjs/util': 1.1.1 223 | '@sapphire/shapeshift': 4.0.0 224 | discord-api-types: 0.37.97 225 | fast-deep-equal: 3.1.3 226 | ts-mixer: 6.0.4 227 | tslib: 2.6.3 228 | 229 | '@discordjs/collection@1.5.3': {} 230 | 231 | '@discordjs/collection@2.1.0': {} 232 | 233 | '@discordjs/collection@2.1.1': {} 234 | 235 | '@discordjs/formatters@0.5.0': 236 | dependencies: 237 | discord-api-types: 0.37.97 238 | 239 | '@discordjs/rest@2.4.0': 240 | dependencies: 241 | '@discordjs/collection': 2.1.1 242 | '@discordjs/util': 1.1.1 243 | '@sapphire/async-queue': 1.5.3 244 | '@sapphire/snowflake': 3.5.3 245 | '@vladfrangu/async_event_emitter': 2.4.6 246 | discord-api-types: 0.37.97 247 | magic-bytes.js: 1.10.0 248 | tslib: 2.6.3 249 | undici: 6.19.8 250 | 251 | '@discordjs/util@1.1.1': {} 252 | 253 | '@discordjs/ws@1.1.1': 254 | dependencies: 255 | '@discordjs/collection': 2.1.0 256 | '@discordjs/rest': 2.4.0 257 | '@discordjs/util': 1.1.1 258 | '@sapphire/async-queue': 1.5.2 259 | '@types/ws': 8.5.10 260 | '@vladfrangu/async_event_emitter': 2.4.0 261 | discord-api-types: 0.37.83 262 | tslib: 2.6.3 263 | ws: 8.17.1 264 | transitivePeerDependencies: 265 | - bufferutil 266 | - utf-8-validate 267 | 268 | '@sapphire/async-queue@1.5.2': {} 269 | 270 | '@sapphire/async-queue@1.5.3': {} 271 | 272 | '@sapphire/shapeshift@4.0.0': 273 | dependencies: 274 | fast-deep-equal: 3.1.3 275 | lodash: 4.17.21 276 | 277 | '@sapphire/snowflake@3.5.3': {} 278 | 279 | '@types/lodash@4.17.6': {} 280 | 281 | '@types/node@20.14.9': 282 | dependencies: 283 | undici-types: 5.26.5 284 | 285 | '@types/ws@8.5.10': 286 | dependencies: 287 | '@types/node': 20.14.9 288 | 289 | '@vladfrangu/async_event_emitter@2.4.0': {} 290 | 291 | '@vladfrangu/async_event_emitter@2.4.6': {} 292 | 293 | async-and-quick@1.0.2: 294 | dependencies: 295 | chillout: 5.0.0 296 | 297 | camel-case@4.1.2: 298 | dependencies: 299 | pascal-case: 3.1.2 300 | tslib: 2.6.3 301 | 302 | chillout@5.0.0: {} 303 | 304 | discord-api-types@0.37.100: {} 305 | 306 | discord-api-types@0.37.83: {} 307 | 308 | discord-api-types@0.37.97: {} 309 | 310 | discord-hybrid-sharding@2.2.6: {} 311 | 312 | discord.js@14.16.2: 313 | dependencies: 314 | '@discordjs/builders': 1.9.0 315 | '@discordjs/collection': 1.5.3 316 | '@discordjs/formatters': 0.5.0 317 | '@discordjs/rest': 2.4.0 318 | '@discordjs/util': 1.1.1 319 | '@discordjs/ws': 1.1.1 320 | '@sapphire/snowflake': 3.5.3 321 | discord-api-types: 0.37.97 322 | fast-deep-equal: 3.1.3 323 | lodash.snakecase: 4.1.1 324 | tslib: 2.6.3 325 | undici: 6.19.8 326 | transitivePeerDependencies: 327 | - bufferutil 328 | - utf-8-validate 329 | 330 | dot-case@3.0.4: 331 | dependencies: 332 | no-case: 3.0.4 333 | tslib: 2.6.3 334 | 335 | fast-deep-equal@3.1.3: {} 336 | 337 | lodash.snakecase@4.1.1: {} 338 | 339 | lodash@4.17.21: {} 340 | 341 | lower-case@2.0.2: 342 | dependencies: 343 | tslib: 2.6.3 344 | 345 | magic-bytes.js@1.10.0: {} 346 | 347 | map-obj@4.3.0: {} 348 | 349 | no-case@3.0.4: 350 | dependencies: 351 | lower-case: 2.0.2 352 | tslib: 2.6.3 353 | 354 | pascal-case@3.1.2: 355 | dependencies: 356 | no-case: 3.0.4 357 | tslib: 2.6.3 358 | 359 | plsargs@0.1.6: 360 | dependencies: 361 | camel-case: 4.1.2 362 | tslib: 2.6.3 363 | 364 | snake-case@3.0.4: 365 | dependencies: 366 | dot-case: 3.0.4 367 | tslib: 2.6.3 368 | 369 | snakecase-keys@8.0.1: 370 | dependencies: 371 | map-obj: 4.3.0 372 | snake-case: 3.0.4 373 | type-fest: 4.25.0 374 | 375 | stuffs@0.1.42: {} 376 | 377 | ts-mixer@6.0.4: {} 378 | 379 | tslib@2.6.3: {} 380 | 381 | type-fest@4.25.0: {} 382 | 383 | typescript@5.5.2: {} 384 | 385 | undici-types@5.26.5: {} 386 | 387 | undici@6.19.8: {} 388 | 389 | ws@8.17.1: {} 390 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # DiscordBotInfrastructure - [English] 2 | 3 | The most advanced, up-to-date and simple to use Discord bot infrastructure you can find on the internet. 4 | 5 | # Usage Scenario 6 | 7 | The MostFeatured/DiscordBotInfrastructure project is not a stand-alone project like other infrastructures you are used to, it is an NPM module. In this context, before you start using it, you need to open a folder and download the infrastructure and discord.js by entering the `npm install @mostfeatured/dbi discord.js` command. 8 | 9 | # While Starting 10 | 11 | First we will need `3 files` and `1 folder`. This structure can be configured completely in accordance with your working order. However, the scenario we propose is as follows. 12 | 13 | Files and folders that need to be opened; Files `dbi.js`, `login.js`, `publish.js` and `src` folder. 14 | 15 | As the first step, we open our file named `dbi.js` and lay the foundations of the infrastructure in it. 16 | ```js 17 | const { createDBI } = require("@mostfeatured/dbi"); 18 | let dbi = createDBI("xd", { 19 | strict: true, 20 | discord: { 21 | token: "", 22 | options: { 23 | intents: [ 24 | "Guilds" 25 | ] 26 | } 27 | }, 28 | defaults: { 29 | locale: { 30 | name: "en" 31 | }, 32 | defaultMemberPermissions: ["SendMessages"], 33 | directMessages: false 34 | }, 35 | references: { 36 | autoClear: { 37 | ttl: 60 * 1000 * 60, 38 | check: 60 * 1000 39 | } 40 | }, 41 | // Message Commands are optional. Message Commands work trough emulating the slash commands.. 42 | messageCommands: { 43 | prefixes: ["!", "."], 44 | typeAliases: { 45 | booleans: { 46 | "true": true, 47 | "false": false, 48 | "yes": true, 49 | "no": false, 50 | } 51 | } 52 | } 53 | }); 54 | 55 | dbi.events.on("messageCommandArgumentError", (data) => { 56 | data.message.reply(`‼️ Invalid argument \`${data.error.option.name}\` (Index: \`${data.error.index}\`). Error Kind: \`${data.error.type}\`. Expected: \`${ApplicationCommandOptionType[data.error.option.type]}\`${data.error.extra ? ` with any of \`${data.error.extra.map(i => i.name).join(", ")}\`` : ""}.`); 57 | return false; 58 | }); 59 | 60 | module.exports = dbi; 61 | ``` 62 | 63 | We start the infrastructure with the `createDBI()` function. The first parameter of the function is your bot's 'codeName' and the second parameter is your bot's configuration. 64 | 65 | As the second step, we open our `login.js` file and write the code group that we will use to open the bot. 66 | ```js 67 | const { Utils } = require("@mostfeatured/dbi"); 68 | const dbi = require("./dbi"); 69 | (async () => { 70 | await Utils.recursiveImport("./src"); 71 | await dbi.load(); 72 | await dbi.login(); 73 | await dbi.client.user.setActivity({ 74 | name: "MostFeatured ❤️ TheArmagan" 75 | }); 76 | console.log(`Logged in! ${dbi.client.user.tag} (${dbi.client.user.id})`); 77 | })(); 78 | ``` 79 | 80 | We import the `Utils.recursiveImport()` function into it. With this function we can `import`/`require` all files in `x` folder endlessly. In this way, the infrastructure is aware of these files. 81 | We access our infrastructure from our main `dbi.js` file and create an `async` environment and first define all project files to the infrastructure. After the definition, we load all the features we have defined to the infrastructure with the `load()` function. Right after, we log in to Discord with the `login()` function. 82 | 83 | As the 3rd step, we open our `publish.js` file and repeat all the operations we did in the second step. 84 | ```js 85 | const { Utils } = require("@mostfeatured/dbi"); 86 | const dbi = require("./dbi"); 87 | (async () => { 88 | await Utils.recursiveImport("./src"); 89 | await dbi.load(); 90 | await dbi.publish("Guild", ""); 91 | // await dbi.publish("Global"); 92 | await dbi.unload(); 93 | console.log("Published!"); 94 | })(); 95 | ``` 96 | 97 | However, instead of calling the `login()` function, we call the `publish()` function and with this function we introduce the commands of our bot wherever we want. 98 | 99 | # Fantastic! 100 | 101 | We now have all the necessary files to use the infrastructure. (Information: If these steps that we have explained in detail are complex and difficult for you, we are sorry that this infrastructure is not suitable for you.) 102 | 103 | # Detailed Usage Scenario 104 | 105 | You can fully use all kinds of Discord's features with our specially developed infrastructure that fully supports v14. (`ChatInput`, `Event`, `Locale`, `Button`, `SelectMenu`, `MessageContextMenu`, `UserContextMenu`, `Modal`, `InteractionLocale`) 106 | 107 | Now we want to show you how to define properties in infrastructure. Example: (ChatInput/Button etc.) 108 | 109 | First, we include the relevant feature in our infrastructure. 110 | ```js 111 | const dbi = require("../dbi"); 112 | ``` 113 | And then we call the `register()` function on the infrastructure and put a `callback` function in it. This `callback` function presents as the first parameter everything you can define on the infrastructure as an object. You can perform the definition to the infrastructure by calling the feature function you want from this object. 114 | ```js 115 | dbi.register(({ ChatInput, Event }) => { 116 | ChatInput({ ... }); 117 | Event({ ... }); 118 | }) 119 | ``` 120 | Remember, you can summon as many features as you want at the same time! 121 | 122 | # ChatInput & ChatInputOptions 123 | 124 | `ChatInput` is defined as "Slash Command" as you can understand. 125 | 126 | `src/chatInput.js` file: 127 | 128 | ```js 129 | const dbi = require("../dbi"); 130 | const Discord = require("discord.js"); 131 | 132 | dbi.register(({ ChatInput, ChatInputOptions }) => { 133 | ChatInput({ 134 | name: "cinsiyet seç", 135 | description: "Cinsiyet seçmenizi sağlar.", 136 | onExecute({ interaction, locale }) { 137 | let gender = interaction.options.get("cinsiyet").value; 138 | let genderNames = locale.user.data.genders; 139 | let genderText = locale.user.data.genderText(interaction.user, genderNames[gender]()); 140 | 141 | interaction.reply({ 142 | content: genderText, 143 | components: [ 144 | { 145 | type: Discord.ComponentType.ActionRow, 146 | components: [ 147 | dbi.interaction("viewGender").toJSON({ overrides: { label: locale.user.data.clickText() }, reference: { ttl: 1000 * 60 * 10, data: [gender] } }), 148 | ] 149 | } 150 | ] 151 | }); 152 | }, 153 | options: [ 154 | ChatInputOptions.stringChoices({ 155 | name: "cinsiyet", 156 | description: "Seçeceğiniz cinsiyet.", 157 | required: true, 158 | choices: [ 159 | { name: "Erkek", value: "erkek" }, 160 | { name: "Kadın", value: "kadın" }, 161 | { name: "Diğer", value: "diğer" }, 162 | ] 163 | }) 164 | ], 165 | }); 166 | }); 167 | ``` 168 | In general, the structure of `ChatInput` may seem familiar to you, except for `options`. When you try to fill something in options directly, you will not get autocomplete. Because options expect static functions in ChatInputOptions class. The functions are similar to: `stringChoices`, `user`, `numberAutocomplete` etc. 169 | You can also find more examples below for a sample demonstration of how the Locale and Component system we have shown above is used. 170 | 171 | # Event 172 | 173 | `src/event.js` file: 174 | 175 | ```js 176 | const dbi = require("../dbi"); 177 | dbi.register(({ Event }) => { 178 | Event({ 179 | name: "ready", 180 | id: "botIsReady", 181 | onExecute() { 182 | console.log(`Bot ready!`); 183 | } 184 | }); 185 | }); 186 | ``` 187 | When defining an event, you can write the name of the event you want in the `name` field. However, if you want to open more than one of the same event, you need to define an 'id' for that event. 188 | 189 | # Locale 190 | 191 | `src/locales.js` file: 192 | 193 | ```js 194 | const dbi = require("../dbi"); 195 | dbi.register(({ Locale }) => { 196 | Locale({ 197 | name: "tr", 198 | data: { 199 | genders: { 200 | erkek: "Erkek", 201 | kadın: "Kadın", 202 | diğer: "Diğer" 203 | }, 204 | genderText: "{0} adlı üye {1} cinsiyetini seçti." 205 | } 206 | }); 207 | Locale({ 208 | name: "en", 209 | data: { 210 | genders: { 211 | erkek: "Male", 212 | kadın: "Female", 213 | diğer: "Other" 214 | }, 215 | genderText: "{0}, picked {1} gender." 216 | } 217 | }); 218 | }); 219 | ``` 220 | 221 | Thanks to Locale, there is information in each interaction that will make it easier for you to respond according to the language of the user or the server. The `name` in Locale contains the values you want to keep, and the `data` part for which language you define. The `{0}` and `{1}` signs in the text are also our variables in the text. When using locale, we can take the value we want and call it like a function. And in it we give the parameters respectively. For example `locale.data.genderText(user, gender)`. 222 | 223 | # Button & SelectMenu & Modal 224 | 225 | In this section, we will look at three of our features. (Actually, it's all the same feature in the background.) 226 | 227 | `src/components.js` file: 228 | 229 | ```js 230 | const dbi = require("../dbi"); 231 | const Discord = require("discord.js"); 232 | dbi.register(({ Button, SelectMenu, Modal }) => { 233 | Button({ 234 | name: "viewGender", 235 | onExecute({ interaction, data }) { 236 | interaction.reply(`\`${data[0]}\``); 237 | }, 238 | options: { 239 | style: Discord.ButtonStyle.Primary, 240 | label: "View Gender" 241 | } 242 | }); 243 | }); 244 | ``` 245 | We bring you a very cool and cool feature about Button & SelectMenu & Modal. Now you can move the value you want on them. (Like Reference/Object or text/number.) For example `dbi.interaction("viewGender").toJSON("male")` will provide us a valid component powered by Discord.js. And you can reach the value presented in the json both by turning the `options` part into a function and when the interaction itself is called. There is one thing you should not forget about this subject. If you are going to carry plain text or numbers, the total length of these texts or numbers should not exceed 100 characters. Because these values ​​are carried directly on the button and they work in such a way that they remain there even if you turn the bot off and on. 246 | 247 | # MessageContextMenu & UserContextMenu 248 | 249 | It has exactly the same properties as `ChatInput` but does not take `options` value. 250 | 251 | # InteractionLocale 252 | 253 | We offer you another unique feature. You can now define a custom script language for each user. 254 | For example, the command that appears as `/select gender` to a Turkish user may appear as `/select gender` to a foreign user. (You can configure it as you wish.) 255 | 256 | `src/interactionlocales.js` file: 257 | 258 | ```js 259 | const dbi = require("../dbi"); 260 | dbi.register(({ InteractionLocale }) => { 261 | InteractionLocale({ 262 | name: "cinsiyet seç", 263 | data: { 264 | en: { 265 | name: "select gender", 266 | description: "Allows you to select a gender.", 267 | options: { 268 | cinsiyet: { 269 | name: "gender", 270 | description: "Select your gender correctly.", 271 | choices: { 272 | "Erkek": "Male", 273 | "Kadın": "Female", 274 | "Diğer": "Other" 275 | } 276 | } 277 | } 278 | } 279 | } 280 | }); 281 | }); 282 | ``` 283 | 284 | Final note: The value `name` for `InteractionLocale` will be the name of one of the other interactions you define. For example `select gender`. We tried to simplify Data's structure. First you select the language you want and then you fill in the content as in the example. You can add how it will appear in as many languages as you want at the same time. 285 | 286 | You can contact me via Discord for errors or similar issues. (Armagan#4869) 287 | 288 | Remember: "There will always be something free and valuable on earth." 289 | 290 | 12.09.2022: Original text written by TheArmagan, edited by Maschera. 291 | 292 | # DiscordBotInfrastructure - [Turkish] 293 | 294 | İnternet üzerinde bulabileceğiniz en gelişmiş, güncel ve kullanımı basit Discord bot altyapısı. 295 | 296 | # Kullanım Senaryosu 297 | 298 | MostFeatured/DiscordBotInfrastructure projesi diğer alışık olduğunuz altyapılar gibi kendi halinde bir proje değil, bir NPM modülüdür. Bu bağlamda kullanmaya başlamadan önce bir klasör açmanız ve içerisine `npm install @mostfeatured/dbi discord.js` komutunu girerek altyapıyı ve discord.js'i indirmeniz gerekmektedir. 299 | 300 | # Başlarken 301 | 302 | İlk olarak `3 dosya` ve `1 klasöre` ihitiyacımız olacak. Bu yapı tamamen sizin çalışma düzeninize uygun olarak konfigüre edilebilmektedir. Lakin bizim önerdiğimiz senaryo aşağıdaki gibidir. 303 | 304 | Açılması gereken dosyalar ve klasörler; `dbi.js`, `login.js`, `publish.js` dosyaları ve `src` klasörü. 305 | 306 | 1. adım olarak `dbi.js` adındaki dosyamızı açıyoruz ve içerisine altyapının temellerini atıyoruz. 307 | ```js 308 | const { createDBI } = require("@mostfeatured/dbi"); 309 | 310 | let dbi = createDBI("xd", { 311 | strict: true, 312 | discord: { 313 | token: "", 314 | options: { 315 | intents: [ 316 | "Guilds" 317 | ] 318 | } 319 | }, 320 | defaults: { 321 | locale: "en", 322 | defaultMemberPermissions: ["SendMessages"], 323 | directMessages: false 324 | }, 325 | references: { 326 | autoClear: { 327 | ttl: 60 * 1000 * 60, 328 | check: 60 * 1000 329 | } 330 | }, 331 | // Mesaj Komutları isteğe bağlıdır. Mesaj Komutları slash komutlarını taklit ederek çalışır. Yani siz sadece slash komut kodlasanız bile uyumlu olarak çalışacaktır. 332 | messageCommands: { 333 | prefixes: ["!", "."], 334 | typeAliases: { 335 | booleans: { 336 | "true": true, 337 | "false": false, 338 | "yes": true, 339 | "no": false, 340 | } 341 | } 342 | } 343 | }); 344 | 345 | dbi.events.on("messageCommandArgumentError", (data) => { 346 | data.message.reply(`‼️ Hatalı argument \`${data.error.option.name}\` (Konum: \`${data.error.index}\`). Hata Tipi: \`${data.error.type}\`. Beklenen: \`${ApplicationCommandOptionType[data.error.option.type]}\`${data.error.extra ? ` şunlardan herhangi biri \`${data.error.extra.map(i => i.name).join(", ")}\`` : ""}.`); 347 | return false; 348 | }); 349 | 350 | module.exports = dbi; 351 | ``` 352 | 353 | `createDBI()` fonksiyonu ile altyapıyı başlatıyoruz. Fonksiyonun ilk parametresi botunuzun `kodAdı`, ikinci parametresi ise botunuzun konfigürasyonudur. 354 | 355 | 2. adım olarak `login.js` dosyamızı açıyoruz ve içerisine botu açmak için kullanacağımız kod gurubunu yazıyoruz. 356 | ```js 357 | const { Utils } = require("@mostfeatured/dbi"); 358 | const dbi = require("./dbi"); 359 | 360 | (async () => { 361 | await Utils.recursiveImport("./src"); 362 | 363 | await dbi.load(); 364 | await dbi.login(); 365 | 366 | await dbi.client.user.setActivity({ 367 | name: "MostFeatured ❤️ TheArmagan" 368 | }); 369 | 370 | console.log(`Logged in! ${dbi.client.user.tag} (${dbi.client.user.id})`); 371 | })(); 372 | ``` 373 | 374 | İçeriye `Utils.recursiveImport()` fonksiyonunu alıyoruz. Bu fonksiyon ile sonsuz bir şekilde `x` klasöründeki bütün dosyaları `import`/`require` edebiliyoruz. Bu sayede altyapı bu dosyalardan haberdar oluyor. 375 | Ana `dbi.js` dosyamızdan altyapımıza ulaşıyoruz ve `async` bir ortam oluşturup ilk önce tüm proje dosylarını altyapıya tanımlıyoruz. Tanımlamanın ardından tanımladığımız tüm özellikleri altyapıya `load()` fonksiyonunu ile yüklüyoruz. Hemen ardından `login()` fonksiyonu ile Discord'a giriş sağlıyoruz. 376 | 377 | 378 | 3. adım olarak `publish.js` dosyamızı açıyoruz ve ikinci aşamada yaptığımız tüm işlemleri tekrardan uyguluyoruz. 379 | ```js 380 | const { Utils } = require("@mostfeatured/dbi"); 381 | const dbi = require("./dbi"); 382 | 383 | (async () => { 384 | await Utils.recursiveImport("./src"); 385 | 386 | await dbi.load(); 387 | await dbi.publish("Guild", ""); 388 | // await dbi.publish("Global"); 389 | await dbi.unload(); 390 | 391 | console.log("Published!"); 392 | })(); 393 | ``` 394 | 395 | Ancak `login()` fonksiyonunu çağırmak yerine, `publish()` fonksiyonunu çağrıyoruz ve bu fonksiyon ile botumuzun komutlarını istediğimiz yere tanıtıyoruz. 396 | 397 | # Harika! 398 | 399 | Artık altyapıyı kullanabilmek için gerekli olan bütün dosyalara sahibiz. (Bilgilendirme: Detaylı bir şekilde anlattığımız bu adımlar size karmaşık ve zor geliyorsa üzgünüz ki bu altyapı size uygun değil.) 400 | 401 | # Detaylı Kullanım Senaryosu 402 | 403 | Özel olarak geliştirdiğimiz ve v14'ü eksiksiz bir şekilde destekleyen altyapımız ile Discord'un her türlü özelliğini tam anlamıyla kullanabiliyorsunuz. (`ChatInput`, `Event`, `Locale`, `Button`, `SelectMenu`, `MessageContextMenu`, `UserContextMenu`, `Modal`, `InteractionLocale`) 404 | 405 | Şimdi sizlere altyapıya özellik tanımlamayı göstermek istiyoruz. Örnek: (ChatInput/Button vb.) 406 | 407 | İlk olarak ilgili özelliği altyapımıza dahil ediyoruz. 408 | ```js 409 | const dbi = require("../dbi"); 410 | ``` 411 | Ve devamında altyapının üzerindeki `register()` fonksiyonunu çağırıp içerisine bir `callback` fonksiyonu koyuyoruz. Bu `callback` fonksiyonu ilk parametre olarak size altyapı üzerine tanımlayabilceğiniz tüm her şeyi bir obje olarak sunuyor. Bu obje içerisinden istediğiniz özellik fonksiyonunu çağırarak altyapıya tanımlama işlemini gerçekleştirebiliyorsunuz. 412 | ```js 413 | dbi.register(({ ChatInput, Event }) => { 414 | ChatInput({ ... }); 415 | Event({ ... }); 416 | }) 417 | ``` 418 | Unutmayın ki aynı anda istediğiniz kadar özelliği çağırabilirsiniz! 419 | 420 | # ChatInput & ChatInputOptions 421 | 422 | `ChatInput` sizin anlayacağınız şekilde "Slash Komut" olarak tanımlanmaktadır. 423 | 424 | `src/chatInput.js` dosyası: 425 | 426 | ```js 427 | const dbi = require("../dbi"); 428 | const Discord = require("discord.js"); 429 | 430 | dbi.register(({ ChatInput, ChatInputOptions }) => { 431 | ChatInput({ 432 | name: "cinsiyet seç", 433 | description: "Cinsiyet seçmenizi sağlar.", 434 | onExecute({ interaction, locale }) { 435 | let gender = interaction.options.get("cinsiyet").value; 436 | let genderNames = locale.user.data.genders; 437 | let genderText = locale.user.data.genderText(interaction.user, genderNames[gender]()); 438 | interaction.reply({ 439 | content: genderText, 440 | components: [ 441 | { 442 | type: Discord.ComponentType.ActionRow, 443 | components: [ 444 | dbi.interaction("viewGender").toJSON({ overrides: { label: locale.user.data.clickText() }, reference: { ttl: 1000 * 60 * 10, data: [gender] } }), 445 | ] 446 | } 447 | ] 448 | }); 449 | }, 450 | options: [ 451 | ChatInputOptions.stringChoices({ 452 | name: "cinsiyet", 453 | description: "Seçeceğiniz cinsiyet.", 454 | required: true, 455 | choices: [ 456 | { name: "Erkek", value: "erkek" }, 457 | { name: "Kadın", value: "kadın" }, 458 | { name: "Diğer", value: "diğer" }, 459 | ] 460 | }) 461 | ], 462 | }); 463 | }); 464 | ``` 465 | Genel olarak `ChatInput`'un yapısı gözünüze `options` dışında tanıdık gelmiş olabilir. Direkt olarak options içerisine bir şey doldurmaya çalıştığınızda otomatik tamamlama alamayacaksınız. Çünkü options içerisinde ChatInputOptions class'ındaki statik fonksiyonlardan beklemekte. Fonksiyonlar ise şunlara benzemekte; `stringChoices`, `user`, `numberAutocomplete` vb. 466 | Ayrıca yukarıda göstermiş olduğumuz Locale ve Component sisteminin nasıl kullanıldığıyla ilgili örnek gösterim için aşağıdan daha fazla örneğe ulaşabilirsiniz. 467 | 468 | # Event 469 | 470 | `src/event.js` dosyası: 471 | 472 | ```js 473 | const dbi = require("../dbi"); 474 | 475 | dbi.register(({ Event }) => { 476 | Event({ 477 | name: "ready", 478 | id: "botIsReady", 479 | onExecute() { 480 | console.log(`Bot ready!`); 481 | } 482 | }); 483 | }); 484 | ``` 485 | Event yani olay tanımlarken `name` kısmına istediğiniz olayın ismini yazabilirsiniz. Ancak eğer aynı olaydan birden fazla açmak istiyorsanız o olaya bir `id` tanımlamanız gerekmektedir. 486 | 487 | # Locale 488 | 489 | `src/locales.js` dosyası: 490 | 491 | ```js 492 | const dbi = require("../dbi"); 493 | 494 | dbi.register(({ Locale }) => { 495 | Locale({ 496 | name: "tr", 497 | data: { 498 | genders: { 499 | erkek: "Erkek", 500 | kadın: "Kadın", 501 | diğer: "Diğer" 502 | }, 503 | genderText: "{0} adlı üye {1} cinsiyetini seçti." 504 | } 505 | }); 506 | 507 | Locale({ 508 | name: "en", 509 | data: { 510 | genders: { 511 | erkek: "Male", 512 | kadın: "Female", 513 | diğer: "Other" 514 | }, 515 | genderText: "{0}, picked {1} gender." 516 | } 517 | }); 518 | }); 519 | ``` 520 | 521 | Locale sayesinde her interaksiyon içerisinde kullanıcının veya sunucunun diline göre cevap vermenizi kolaylaştıracak bilgiler bulunmakta. Locale içerisindeki `name` hangi dil için tanımlama yaptığınız `data` kısmı ise tutmak istediğiniz değerleri içermekte. Yazı içerisindeki `{0}` ve `{1}` işaretleri aynı şekilde yazı içerisindeki değişkenlerimiz. Locale kullanırken istediğimiz değeri alıp onu bir fonksiyon gibi çağırabiliyoruz. Ve içerisine sırasıyla parametreleri veriyoruz. Örneğin `locale.data.genderText(user, gender)`. 522 | 523 | # Button & SelectMenu & Modal 524 | 525 | Bu bölümde ise üç adet özelliğimize bakacağız. (Aslında arka planda hepsi aynı özellik.) 526 | 527 | `src/components.js` dosyası: 528 | 529 | ```js 530 | const dbi = require("../dbi"); 531 | const Discord = require("discord.js"); 532 | 533 | dbi.register(({ Button, SelectMenu, Modal }) => { 534 | Button({ 535 | name: "viewGender", 536 | onExecute({ interaction, data }) { 537 | interaction.reply(`\`${data[0]}\``); 538 | }, 539 | options: { 540 | style: Discord.ButtonStyle.Primary, 541 | label: "View Gender" 542 | } 543 | }); 544 | }); 545 | ``` 546 | Button & SelectMenu & Modal hakkında çok güzel ve havalı bir özelliği sizlerle buluşturuyoruz. Artık bunların üzerinde istediğiniz değeri taşıyabilirsiniz. (Referans/Obje veya yazı/sayı gibi.) Örneğin `dbi.interaction("viewGender").toJSON("male")` bize Discord.js tarafından desteklenen geçerli bir component sunacaktır. Ve to json içerisinde sunulan değere hem `options` kısmını bir fonksiyona çevirerek ulaşabilir hem de interaksiyonun kendisi çağırıldığında ulaşabilirsiniz. Bu konuyla ilgili unutmamanız gereken bir şey var. Eğer düz yazı veya sayı taşıyacaksanız bu yazı veya sayıların toplam uzunluğu 100 karakteri geçmemeli. Çünkü bu değerler direkt olarak düğmenin üzerinde taşınmakta ve siz botu kapatıp açsanız bile orada kalacak şekilde çalışmaktadırlar. 547 | 548 | # MessageContextMenu & UserContextMenu 549 | 550 | `ChatInput` ile birebir aynı özelliklere sahip ancak `options` değeri almamaktadır. 551 | 552 | # InteractionLocale 553 | 554 | Bir eşsiz özelliği daha sizlere sunuyoruz. Artık her kullanıcıya özel komut dili tanımlamanızı sağlayabilirsiniz. 555 | Örneğin Türk bir kullanıcıya `/cinsiyet seç` şeklinde gözüken komut yabancı bir kullanıcıya `/select gender` olarak gözükebilir. (Dilediğiniz gibi konfigüre edebilirsiniz.) 556 | 557 | `src/interactionlocales.js` dosyası: 558 | 559 | ```js 560 | const dbi = require("../dbi"); 561 | 562 | dbi.register(({ InteractionLocale }) => { 563 | InteractionLocale({ 564 | name: "cinsiyet seç", 565 | data: { 566 | en: { 567 | name: "select gender", 568 | description: "Allows you to select a gender.", 569 | options: { 570 | cinsiyet: { 571 | name: "gender", 572 | description: "Select your gender correctly.", 573 | choices: { 574 | "Erkek": "Male", 575 | "Kadın": "Female", 576 | "Diğer": "Other" 577 | } 578 | } 579 | } 580 | } 581 | } 582 | }); 583 | }); 584 | ``` 585 | 586 | Son not: `InteractionLocale` için `name` değeri sizin tanımladığınız diğer interaksiyonlardan birinin ismi olacaktır. Örneğin `cinsiyet seç`. Data'nın yapısını en basit hale getirmeye çalıştık. ilk önce istediğiniz dili seçiyorsunuz ve sonrasında içerisini örnekteki gibi uygun bir şekilde dolduruyorsunuz. İçerisine aynı anda istediğin kadar dilde nasıl gözükeceğini ekleyebilirsiniz. 587 | 588 | Karşılacağınız hatalar ya da benzer konular için Discord üzerinden tarafıma ulaşabilirsiniz. (Armagan#4869) 589 | 590 | Unutmayın ki: "Yeryüzünde her zaman ücretsiz ve değerli bir şeyler olacaktır." 591 | 592 | 12.09.2022: Orjinal metin TheArmagan tarafından yazıldı, Maschera tarafından düzenlendi. -------------------------------------------------------------------------------- /src/Events.ts: -------------------------------------------------------------------------------- 1 | import { NamespaceEnums, NamespaceData } from "../generated/namespaceData"; 2 | import { DBI } from "./DBI"; 3 | import { TDBIMessageCommandArgumentErrorTypes } from "./methods/handleMessageCommands"; 4 | import { DBIChatInput } from "./types/ChatInput/ChatInput"; 5 | import { TDBIValueName } from "./types/ChatInput/ChatInputOptions"; 6 | import { ClientEvents, DBIEvent } from "./types/Event"; 7 | import { IDBIBaseExecuteCtx, TDBIRateLimitTypes } from "./types/Interaction"; 8 | import { FakeMessageInteraction } from "./types/other/FakeMessageInteraction"; 9 | import { DBILocale } from "./types/other/Locale"; 10 | import Discord, { PermissionsString } from "discord.js"; 11 | 12 | export type TDBIEventNames = 13 | | "beforeInteraction" 14 | | "afterInteraction" 15 | | "interactionRateLimit" 16 | | "beforeEvent" 17 | | "afterEvent" 18 | | "interactionError" 19 | | "eventError" 20 | | "messageCommandArgumentError" 21 | | "messageCommandDirectMessageUsageError" 22 | | "messageCommandDefaultMemberPermissionsError" 23 | | "clientsReady"; 24 | 25 | export type TDBIEventHandlerCtx = { 26 | [K in keyof (ClientEvents & NamespaceData[TNamespace]["customEvents"])]: { 27 | other: Record; 28 | locale?: { guild: DBILocale }; 29 | eventName: K; 30 | dbiEvent: DBIEvent; 31 | } & (ClientEvents & NamespaceData[TNamespace]["customEvents"])[K]; 32 | }[keyof (ClientEvents & NamespaceData[TNamespace]["customEvents"])]; 33 | 34 | export class Events { 35 | DBI: DBI; 36 | handlers: Record boolean | Promise>>; 37 | constructor(DBI: DBI) { 38 | this.DBI = DBI; 39 | 40 | this.handlers = { 41 | beforeInteraction: [], 42 | afterInteraction: [], 43 | interactionRateLimit: [], 44 | beforeEvent: [], 45 | afterEvent: [], 46 | interactionError: [], 47 | eventError: [], 48 | }; 49 | } 50 | 51 | async trigger(name: TDBIEventNames, data?: any, ignoreResponse = false): Promise { 52 | let handlers = this.handlers[name]; 53 | if (!handlers?.length) return true; 54 | for (let i = 0; i < handlers.length; i++) { 55 | const handler = handlers[i]; 56 | if (!ignoreResponse) { 57 | let returned = await handler(data); 58 | if (returned !== true) return false; 59 | } else { 60 | handler(data); 61 | } 62 | } 63 | return true; 64 | } 65 | 66 | on( 67 | eventName: "clientsReady", 68 | handler: () => any, 69 | options?: { once: boolean } 70 | ): () => any; 71 | 72 | on( 73 | eventName: "beforeInteraction" | "afterInteraction", 74 | handler: ( 75 | data: IDBIBaseExecuteCtx 76 | ) => Promise | boolean, 77 | options?: { once: boolean } 78 | ): () => any; 79 | 80 | on( 81 | eventName: "interactionError", 82 | handler: ( 83 | data: IDBIBaseExecuteCtx & { error: any } 84 | ) => Promise | boolean, 85 | options?: { once: boolean } 86 | ): () => any; 87 | 88 | on( 89 | eventName: "beforeEvent" | "afterEvent", 90 | handler: ( 91 | data: TDBIEventHandlerCtx 92 | ) => Promise | boolean, 93 | options?: { once: boolean } 94 | ): () => any; 95 | 96 | on( 97 | eventName: "eventError", 98 | handler: ( 99 | data: TDBIEventHandlerCtx & { 100 | error: any; 101 | dbiEvent: DBIEvent; 102 | } 103 | ) => Promise | boolean, 104 | options?: { once: boolean } 105 | ): () => any; 106 | 107 | on( 108 | eventName: "interactionRateLimit", 109 | handler: ( 110 | data: Omit, "other" | "setRateLimit"> & { 111 | rateLimit: { type: TDBIRateLimitTypes; duration: number; at: number }; 112 | } 113 | ) => Promise | boolean, 114 | options?: { once: boolean } 115 | ): () => any; 116 | 117 | on( 118 | eventName: "messageCommandArgumentError", 119 | handler: (data: { 120 | message: Discord.Message; 121 | interaction: FakeMessageInteraction; 122 | error: { 123 | type: TDBIMessageCommandArgumentErrorTypes; 124 | option: any; 125 | index: number; 126 | extra?: any; 127 | }; 128 | value: any; 129 | locale: { guild?: DBILocale; user: DBILocale }; 130 | dbiInteraction: DBIChatInput; 131 | }) => Promise | boolean, 132 | options?: { once: boolean } 133 | ): () => any; 134 | 135 | on( 136 | eventName: "messageCommandDefaultMemberPermissionsError", 137 | handler: (data: { 138 | message: Discord.Message; 139 | interaction: FakeMessageInteraction; 140 | locale: { guild?: DBILocale; user: DBILocale }; 141 | permissions: PermissionsString[]; 142 | dbiInteraction: DBIChatInput; 143 | }) => Promise | boolean, 144 | options?: { once: boolean } 145 | ): () => any; 146 | 147 | on( 148 | eventName: "messageCommandDirectMessageUsageError", 149 | handler: (data: { 150 | message: Discord.Message; 151 | interaction: FakeMessageInteraction; 152 | locale: { guild?: DBILocale; user: DBILocale }; 153 | dbiInteraction: DBIChatInput; 154 | }) => Promise | boolean, 155 | options?: { once: boolean } 156 | ): () => any; 157 | 158 | on( 159 | eventName: TDBIEventNames, 160 | handler: (data: any) => Promise | boolean, 161 | options: { once: boolean } = { once: false } 162 | ): () => any { 163 | if (!this.handlers.hasOwnProperty(eventName)) this.handlers[eventName] = []; 164 | if (options.once) { 165 | let h = (data) => { 166 | this.off(eventName, h); 167 | return handler(data); 168 | }; 169 | this.on(eventName as any, h as any, { once: false }); 170 | return () => { 171 | this.off(eventName, h); 172 | }; 173 | } else { 174 | this.handlers[eventName].push(handler); 175 | return () => { 176 | this.off(eventName, handler); 177 | }; 178 | } 179 | } 180 | 181 | off( 182 | eventName: TDBIEventNames, 183 | handler: (data: any) => Promise | boolean 184 | ) { 185 | let l = this.handlers[eventName]; 186 | if (!l) return []; 187 | return l.splice(l.indexOf(handler), 1); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/data/eventMap.json: -------------------------------------------------------------------------------- 1 | { 2 | "applicationCommandPermissionsUpdate": [ 3 | "data" 4 | ], 5 | "cacheSweep": [ 6 | "message" 7 | ], 8 | "channelCreate": [ 9 | "channel" 10 | ], 11 | "channelDelete": [ 12 | "channel" 13 | ], 14 | "channelPinsUpdate": [ 15 | "channel", 16 | "date" 17 | ], 18 | "channelUpdate": [ 19 | "oldChannel", 20 | "newChannel" 21 | ], 22 | "debug": [ 23 | "message" 24 | ], 25 | "warn": [ 26 | "message" 27 | ], 28 | "emojiCreate": [ 29 | "emoji" 30 | ], 31 | "emojiDelete": [ 32 | "emoji" 33 | ], 34 | "emojiUpdate": [ 35 | "oldEmoji", 36 | "newEmoji" 37 | ], 38 | "error": [ 39 | "error" 40 | ], 41 | "guildBanAdd": [ 42 | "ban" 43 | ], 44 | "guildBanRemove": [ 45 | "ban" 46 | ], 47 | "guildCreate": [ 48 | "guild" 49 | ], 50 | "guildDelete": [ 51 | "guild" 52 | ], 53 | "guildUnavailable": [ 54 | "guild" 55 | ], 56 | "guildIntegrationsUpdate": [ 57 | "guild" 58 | ], 59 | "guildMemberAdd": [ 60 | "member" 61 | ], 62 | "guildMemberAvailable": [ 63 | "member" 64 | ], 65 | "guildMemberRemove": [ 66 | "member" 67 | ], 68 | "guildMembersChunk": [ 69 | "members", 70 | "guild", 71 | "data" 72 | ], 73 | "guildMemberUpdate": [ 74 | "oldMember", 75 | "newMember" 76 | ], 77 | "guildUpdate": [ 78 | "oldGuild", 79 | "newGuild" 80 | ], 81 | "inviteCreate": [ 82 | "invite" 83 | ], 84 | "inviteDelete": [ 85 | "invite" 86 | ], 87 | "messageCreate": [ 88 | "message" 89 | ], 90 | "messageDelete": [ 91 | "message" 92 | ], 93 | "messageReactionRemoveAll": [ 94 | "message", 95 | "reactions" 96 | ], 97 | "messageReactionRemoveEmoji": [ 98 | "reaction" 99 | ], 100 | "messageDeleteBulk": [ 101 | "messages", 102 | "channel" 103 | ], 104 | "messageReactionAdd": [ 105 | "reaction", 106 | "user" 107 | ], 108 | "messageReactionRemove": [ 109 | "reaction", 110 | "user" 111 | ], 112 | "messageUpdate": [ 113 | "oldMessage", 114 | "newMessage" 115 | ], 116 | "presenceUpdate": [ 117 | "oldPresence", 118 | "newPresence" 119 | ], 120 | "ready": [ 121 | "client" 122 | ], 123 | "invalidated": [ 124 | "undefined" 125 | ], 126 | "roleCreate": [ 127 | "role" 128 | ], 129 | "roleDelete": [ 130 | "role" 131 | ], 132 | "roleUpdate": [ 133 | "oldRole", 134 | "newRole" 135 | ], 136 | "threadCreate": [ 137 | "thread", 138 | "newlyCreated" 139 | ], 140 | "threadDelete": [ 141 | "thread" 142 | ], 143 | "threadListSync": [ 144 | "threads", 145 | "guild" 146 | ], 147 | "threadMemberUpdate": [ 148 | "oldMember", 149 | "newMember" 150 | ], 151 | "threadMembersUpdate": [ 152 | "addedMembers", 153 | "removedMembers", 154 | "thread" 155 | ], 156 | "threadUpdate": [ 157 | "oldThread", 158 | "newThread" 159 | ], 160 | "typingStart": [ 161 | "typing" 162 | ], 163 | "userUpdate": [ 164 | "oldUser", 165 | "newUser" 166 | ], 167 | "voiceStateUpdate": [ 168 | "oldState", 169 | "newState" 170 | ], 171 | "webhookUpdate": [ 172 | "channel" 173 | ], 174 | "interactionCreate": [ 175 | "interaction" 176 | ], 177 | "shardDisconnect": [ 178 | "closeEvent", 179 | "shardId" 180 | ], 181 | "shardError": [ 182 | "error", 183 | "shardId" 184 | ], 185 | "shardReady": [ 186 | "shardId", 187 | "unavailableGuilds" 188 | ], 189 | "shardReconnecting": [ 190 | "shardId" 191 | ], 192 | "shardResume": [ 193 | "shardId", 194 | "replayedEvents" 195 | ], 196 | "stageInstanceCreate": [ 197 | "stageInstance" 198 | ], 199 | "stageInstanceUpdate": [ 200 | "oldStageInstance", 201 | "newStageInstance" 202 | ], 203 | "stageInstanceDelete": [ 204 | "stageInstance" 205 | ], 206 | "stickerCreate": [ 207 | "sticker" 208 | ], 209 | "stickerDelete": [ 210 | "sticker" 211 | ], 212 | "stickerUpdate": [ 213 | "oldSticker", 214 | "newSticker" 215 | ], 216 | "guildScheduledEventCreate": [ 217 | "guildScheduledEvent" 218 | ], 219 | "guildScheduledEventUpdate": [ 220 | "oldGuildScheduledEvent", 221 | "newGuildScheduledEvent" 222 | ], 223 | "guildScheduledEventDelete": [ 224 | "guildScheduledEvent" 225 | ], 226 | "guildScheduledEventUserAdd": [ 227 | "guildScheduledEvent", 228 | "user" 229 | ], 230 | "guildScheduledEventUserRemove": [ 231 | "guildScheduledEvent", 232 | "user" 233 | ], 234 | "autoModerationRuleCreate": [ 235 | "autoModerationRule" 236 | ], 237 | "autoModerationRuleDelete": [ 238 | "autoModerationRule" 239 | ], 240 | "autoModerationRuleUpdate": [ 241 | "oldAutoModerationRule", 242 | "newAutoModerationRule" 243 | ], 244 | "guildAuditLogEntryCreate": [ 245 | "auditLogEntry", 246 | "guild" 247 | ] 248 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { NamespaceEnums } from "../generated/namespaceData"; 2 | import { DBI, DBIConfigConstructor } from "./DBI"; 3 | import { recursiveImport } from "./utils/recursiveImport"; 4 | export { MemoryStore } from "./utils/MemoryStore"; 5 | 6 | import path from "path"; 7 | import { parseCustomId, buildCustomId } from "./utils/customId"; 8 | import { unloadModule } from "./utils/unloadModule"; 9 | import { recursiveUnload } from "./utils/recursiveUnload"; 10 | 11 | export const generatedPath = path.resolve(__dirname, "../generated"); 12 | 13 | export function createDBI = Record>(namespace: TNamespace, cfg: DBIConfigConstructor): DBI { 14 | return new DBI(namespace, cfg); 15 | }; 16 | 17 | export const Utils = { 18 | parseCustomId, 19 | buildCustomId, 20 | recursiveImport, 21 | unloadModule, 22 | recursiveUnload 23 | }; 24 | -------------------------------------------------------------------------------- /src/methods/handleMessageCommands.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ApplicationCommandType, 3 | ChatInputCommandInteraction, 4 | Message, 5 | MessagePayload, 6 | ApplicationCommandOptionType, 7 | } from "discord.js"; 8 | import { NamespaceEnums } from "../../generated/namespaceData"; 9 | import { DBI } from "../DBI"; 10 | import { FakeMessageInteraction } from "../types/other/FakeMessageInteraction"; 11 | import { TDBILocaleString } from "../types/other/Locale"; 12 | 13 | const INTEGER_REGEX = /^-?\d+$/; 14 | const NUMBER_REGEX = /^-?\d+(?:\.\d+)?$/; 15 | 16 | export type TDBIMessageCommandArgumentErrorTypes = 17 | | "MissingRequiredOption" 18 | | "MinLength" 19 | | "MaxLength" 20 | | "InvalidChoice" 21 | | "InvalidInteger" 22 | | "MinInteger" 23 | | "MaxInteger" 24 | | "InvalidNumber" 25 | | "MinNumber" 26 | | "MaxNumber" 27 | | "InvalidBoolean" 28 | | "InvalidUser" 29 | | "InvalidChannel" 30 | | "InvalidRole" 31 | | "InvalidMentionable" 32 | | "InvalidCompleteChoice"; 33 | 34 | export async function handleMessageCommands( 35 | dbi: DBI, 36 | message: Message 37 | ) { 38 | const chatInputs = dbi.data.interactions.filter( 39 | (i) => i.type === "ChatInput" 40 | ); 41 | const prefixes = await dbi.config.messageCommands.prefixes({ message }); 42 | if (!prefixes?.length) return; 43 | const content = message.content; 44 | const usedPrefix = prefixes.find((p) => content.startsWith(p)); 45 | if (!usedPrefix) return; 46 | const contentWithoutPrefix = content.slice(usedPrefix?.length ?? 0); 47 | const contentLower = contentWithoutPrefix.toLowerCase(); 48 | 49 | let locale: string = 50 | message.guild.preferredLocale?.split("-")?.at(0) || 51 | (dbi.config.defaults.locale as any); 52 | let usedAlias: string | undefined; 53 | let chatInput = chatInputs.find((i) => { 54 | let found = contentLower.startsWith(i.name); 55 | if (found) return true; 56 | let alias = i.other?.messageCommand?.aliases?.find((a) => 57 | contentLower.startsWith(a) 58 | ); 59 | if (alias) { 60 | usedAlias = alias; 61 | return true; 62 | } 63 | return false; 64 | }); 65 | let commandName = usedAlias ?? chatInput?.name; 66 | 67 | if (!chatInput) { 68 | fLoop: for (const [localeInterName, localeData] of dbi.data 69 | .interactionLocales) { 70 | for (const [localeName, translation] of Object.entries( 71 | localeData.data || {} 72 | )) { 73 | if (contentLower.startsWith(translation.name)) { 74 | commandName = translation.name; 75 | locale = localeName; 76 | chatInput = chatInputs.find((i) => i.name === localeData.name); 77 | break fLoop; 78 | } 79 | } 80 | } 81 | } 82 | 83 | if (!chatInput || chatInput.other?.messageCommand?.ignore) return; 84 | 85 | const interaction = new FakeMessageInteraction( 86 | dbi, 87 | message, 88 | chatInput as any, 89 | locale, 90 | commandName, 91 | usedPrefix 92 | ); 93 | 94 | const builtLocale = { 95 | user: 96 | dbi.data.locales.get(interaction.locale) || 97 | dbi.data.locales.get(dbi.config.defaults.locale.name), 98 | guild: message.guild?.preferredLocale 99 | ? dbi.data.locales.get( 100 | message.guild?.preferredLocale?.split("-")?.at(0) 101 | ) || dbi.data.locales.get(dbi.config.defaults.locale.name) 102 | : null, 103 | }; 104 | 105 | const { defaultMemberPermissions, directMessages } = chatInput as any; 106 | 107 | if (typeof directMessages !== "undefined" && !directMessages && !message.guild) { 108 | const res = await dbi.events.trigger( 109 | "messageCommandDirectMessageUsageError", { 110 | interaction, 111 | message, 112 | locale: builtLocale, 113 | dbiInteraction: chatInput 114 | }); 115 | if (!res) return; 116 | } 117 | 118 | if (Array.isArray(defaultMemberPermissions) && message.guild && message.member) { 119 | const perms = message.member.permissions.toArray(); 120 | if (!defaultMemberPermissions.every((p) => perms.includes(p))) { 121 | const res = await dbi.events.trigger( 122 | "messageCommandDefaultMemberPermissionsError", { 123 | interaction, 124 | message, 125 | locale: builtLocale, 126 | dbiInteraction: chatInput, 127 | permissions: defaultMemberPermissions 128 | }); 129 | if (!res) return; 130 | } 131 | } 132 | 133 | if (chatInput.options.length) { 134 | let errorType: TDBIMessageCommandArgumentErrorTypes; 135 | let lastOption: any; 136 | let lastValue: any; 137 | let lastExtra: any; 138 | let lastIndex: number; 139 | for (let i = 0; i < chatInput.options.length; i++) { 140 | lastIndex = i; 141 | const option: any = interaction.dbiChatInputOptions[i]; 142 | const value = interaction.parsedArgs.get(option.name)?.value; 143 | 144 | lastOption = option; 145 | lastValue = value; 146 | 147 | switch (option.type) { 148 | case ApplicationCommandOptionType.String: { 149 | if (!option.required && !value) break; 150 | 151 | if (option.autocomplete && option.onComplete) { 152 | let choices = await option.onComplete({ 153 | interaction, 154 | value, 155 | }); 156 | if (!choices.length) 157 | choices = await option.onComplete({ 158 | interaction, 159 | value: "", 160 | }); 161 | if (choices.length > 20) 162 | throw new Error("Autocomplete returned more than 20 choices."); 163 | lastExtra = choices; 164 | if (!choices.find((c) => c.name === value || c.value === value)) { 165 | if (value) { 166 | errorType = "InvalidCompleteChoice"; 167 | break; 168 | } else if (option.required && !value) { 169 | errorType = "MissingRequiredOption"; 170 | break; 171 | } 172 | } 173 | option._choices = choices; 174 | } 175 | 176 | if (option.choices) { 177 | const localeData = dbi.data.interactionLocales.get( 178 | chatInput.name 179 | )?.data; 180 | const choicesLocaleData = 181 | localeData?.[locale as TDBILocaleString]?.options?.[option.name] 182 | ?.choices; 183 | if ( 184 | !option.choices.find( 185 | (c) => 186 | c.name === value || 187 | c.value === value || 188 | (choicesLocaleData?.[c.value] && 189 | choicesLocaleData?.[c.value] === value) 190 | ) 191 | ) { 192 | lastExtra = option.choices.map((c) => ({ 193 | name: choicesLocaleData?.[c.value] ?? c.name, 194 | value: c.value, 195 | })); 196 | if (value) { 197 | errorType = "InvalidChoice"; 198 | break; 199 | } else if (option.required && !value) { 200 | errorType = "MissingRequiredOption"; 201 | break; 202 | } 203 | } 204 | break; 205 | } 206 | 207 | if (option.required && !value) { 208 | errorType = "MissingRequiredOption"; 209 | break; 210 | } 211 | if (option.minLength && value?.length < option.minLength) { 212 | errorType = "MinLength"; 213 | break; 214 | } 215 | if (option.maxLength && value?.length > option.maxLength) { 216 | errorType = "MaxLength"; 217 | break; 218 | } 219 | 220 | break; 221 | } 222 | case ApplicationCommandOptionType.Integer: { 223 | if (!option.required && !value) break; 224 | 225 | let parsedInt = parseInt(value); 226 | 227 | if (option.autocomplete && option.onComplete) { 228 | let choices = await option.onComplete({ 229 | interaction, 230 | value, 231 | }); 232 | if (!choices.length) 233 | choices = await option.onComplete({ 234 | interaction, 235 | value: "", 236 | }); 237 | if (choices.length > 20) 238 | throw new Error("Autocomplete returned more than 20 choices."); 239 | lastExtra = choices; 240 | if ( 241 | !choices.find((c) => c.value === parsedInt || c.name === value) 242 | ) { 243 | if (value) { 244 | errorType = "InvalidCompleteChoice"; 245 | break; 246 | } else if (option.required && !value) { 247 | errorType = "MissingRequiredOption"; 248 | break; 249 | } 250 | } 251 | option._choices = choices; 252 | break; 253 | } 254 | 255 | if (option.choices) { 256 | const localeData = dbi.data.interactionLocales.get( 257 | chatInput.name 258 | )?.data; 259 | const choicesLocaleData = 260 | localeData?.[locale as TDBILocaleString]?.options?.[option.name] 261 | ?.choices; 262 | if ( 263 | !option.choices.find( 264 | (c) => 265 | c.value === parsedInt || 266 | c.name === value || 267 | (choicesLocaleData?.[c.value] && 268 | choicesLocaleData?.[c.value] === value) 269 | ) 270 | ) { 271 | lastExtra = option.choices.map((c) => ({ 272 | name: choicesLocaleData?.[c.value] ?? c.name, 273 | value: c.value, 274 | })); 275 | if (value) { 276 | errorType = "InvalidChoice"; 277 | break; 278 | } else if (option.required && !value) { 279 | errorType = "MissingRequiredOption"; 280 | break; 281 | } 282 | } 283 | break; 284 | } 285 | 286 | if (!INTEGER_REGEX.test(value)) { 287 | errorType = "InvalidInteger"; 288 | break; 289 | } 290 | 291 | if (option.minValue && parsedInt < option.minValue) { 292 | errorType = "MinInteger"; 293 | break; 294 | } 295 | 296 | if (option.maxValue && parsedInt > option.maxValue) { 297 | errorType = "MaxInteger"; 298 | break; 299 | } 300 | 301 | break; 302 | } 303 | case ApplicationCommandOptionType.Number: { 304 | if (!option.required && !value) break; 305 | 306 | let parsedFloat = parseFloat(value); 307 | 308 | if (option.autocomplete && option.onComplete) { 309 | let choices = await option.onComplete({ 310 | interaction, 311 | value, 312 | }); 313 | if (!choices.length) 314 | choices = await option.onComplete({ 315 | interaction, 316 | value: "", 317 | }); 318 | if (choices.length > 20) 319 | throw new Error("Autocomplete returned more than 20 choices."); 320 | lastExtra = choices; 321 | if ( 322 | !choices.find((c) => c.value === parsedFloat || c.name === value) 323 | ) { 324 | if (value) { 325 | errorType = "InvalidCompleteChoice"; 326 | break; 327 | } else if (option.required && !value) { 328 | errorType = "MissingRequiredOption"; 329 | break; 330 | } 331 | } 332 | option._choices = choices; 333 | break; 334 | } 335 | 336 | if (option.choices) { 337 | const localeData = dbi.data.interactionLocales.get( 338 | chatInput.name 339 | )?.data; 340 | const choicesLocaleData = 341 | localeData?.[locale as TDBILocaleString]?.options?.[option.name] 342 | ?.choices; 343 | if ( 344 | !option.choices.find( 345 | (c) => 346 | c.value === parsedFloat || 347 | c.name === value || 348 | (choicesLocaleData?.[c.value] && 349 | choicesLocaleData?.[c.value] === value) 350 | ) 351 | ) { 352 | lastExtra = option.choices.map((c) => ({ 353 | name: choicesLocaleData?.[c.value] ?? c.name, 354 | value: c.value, 355 | })); 356 | if (value) { 357 | errorType = "InvalidChoice"; 358 | break; 359 | } else if (option.required && !value) { 360 | errorType = "MissingRequiredOption"; 361 | break; 362 | } 363 | } 364 | break; 365 | } 366 | 367 | if (!NUMBER_REGEX.test(value)) { 368 | errorType = "InvalidNumber"; 369 | break; 370 | } 371 | 372 | if (option.minValue && parsedFloat < option.minValue) { 373 | errorType = "MinNumber"; 374 | break; 375 | } 376 | 377 | if (option.maxValue && parsedFloat > option.maxValue) { 378 | errorType = "MaxNumber"; 379 | break; 380 | } 381 | break; 382 | } 383 | case ApplicationCommandOptionType.Boolean: { 384 | let boolKeys = Object.keys( 385 | dbi.config.messageCommands.typeAliases.booleans 386 | ); 387 | if (option.required && !boolKeys.includes(value?.toLowerCase?.())) { 388 | errorType = "InvalidBoolean"; 389 | break; 390 | } 391 | break; 392 | } 393 | case ApplicationCommandOptionType.User: { 394 | await message.client.users 395 | .fetch(interaction.options.getUserId(option.name)) 396 | .catch(() => { }); 397 | if (option.required && !interaction.options.getUser(option.name)) { 398 | errorType = "InvalidUser"; 399 | break; 400 | } 401 | break; 402 | } 403 | case ApplicationCommandOptionType.Channel: { 404 | await message.client.channels 405 | .fetch(interaction.options.getChannelId(option.name)) 406 | .catch(() => { }); 407 | if ( 408 | option.required && 409 | !interaction.options.getChannel( 410 | option.name, 411 | null, 412 | option.channelTypes 413 | ) 414 | ) { 415 | errorType = "InvalidChannel"; 416 | break; 417 | } 418 | break; 419 | } 420 | case ApplicationCommandOptionType.Role: { 421 | await message.guild.roles 422 | .fetch(interaction.options.getRoleId(option.name)) 423 | .catch(() => { }); 424 | if (option.required && !interaction.options.getRole(option.name)) { 425 | errorType = "InvalidRole"; 426 | break; 427 | } 428 | break; 429 | } 430 | case ApplicationCommandOptionType.Mentionable: { 431 | let mentionableId = interaction.options.getMentionableId(option.name); 432 | await message.guild.roles.fetch(mentionableId).catch(() => { }); 433 | await message.client.channels.fetch(mentionableId).catch(() => { }); 434 | await message.client.users.fetch(mentionableId).catch(() => { }); 435 | if ( 436 | option.required && 437 | !interaction.options.getMentionable(option.name) 438 | ) { 439 | errorType = "InvalidMentionable"; 440 | break; 441 | } 442 | break; 443 | } 444 | case ApplicationCommandOptionType.Attachment: { 445 | if (option.required && !value) { 446 | errorType = "MissingRequiredOption"; 447 | } 448 | break; 449 | } 450 | } 451 | 452 | if (errorType) { 453 | break; 454 | } else { 455 | lastExtra = null; 456 | lastIndex = null; 457 | lastOption = null; 458 | lastValue = null; 459 | } 460 | } 461 | 462 | if (errorType) { 463 | let res = await dbi.events.trigger("messageCommandArgumentError", { 464 | interaction, 465 | message, 466 | locale: builtLocale, 467 | error: { 468 | type: errorType, 469 | option: lastOption, 470 | extra: lastExtra, 471 | index: lastIndex, 472 | }, 473 | value: lastValue, 474 | dbiInteraction: chatInput, 475 | }); 476 | if (!res) return; 477 | } 478 | } 479 | 480 | interaction.init(); 481 | dbi.data.clients.first().client.emit("interactionCreate", interaction as any); 482 | } 483 | -------------------------------------------------------------------------------- /src/methods/hookEventListeners.ts: -------------------------------------------------------------------------------- 1 | import { Guild } from "discord.js"; 2 | import { NamespaceEnums } from "../../generated/namespaceData"; 3 | import { DBI } from "../DBI"; 4 | import { TDBIEventOrder } from "../types/Event"; 5 | 6 | export function hookEventListeners(dbi: DBI): () => any { 7 | 8 | function getClientByEvent(value) { 9 | return value.triggerType == "OneByOne" 10 | ? dbi.data.clients.next(`Event:${value.name}`) 11 | : value.triggerType == "OneByOneGlobal" 12 | ? dbi.data.clients.next("Event") 13 | : value.triggerType == "Random" 14 | ? dbi.data.clients.random() 15 | : dbi.data.clients.first(); 16 | } 17 | 18 | async function handle(eventName: string, ...args: any[]) { 19 | if (!dbi.data.eventMap[eventName]) return; 20 | 21 | let isDirect = args?.[0]?._DIRECT_ ?? false; 22 | if (isDirect) delete args[0]._DIRECT_; 23 | 24 | let ctxArgs = 25 | isDirect 26 | ? args[0] 27 | : (dbi.data.eventMap[eventName] as any).reduce((all, current, index) => { 28 | all[current] = args[index]; 29 | return all; 30 | }, {}); 31 | 32 | let other = {}; 33 | 34 | let guildLocaleName = args.reduce((all, current) => { 35 | if (current?.guild?.id) return current?.guild?.preferredLocale?.split?.("-")?.[0]; 36 | if (current instanceof Guild) return current?.preferredLocale?.split?.("-")?.[0]; 37 | return all; 38 | }, null); 39 | let guildLocale = guildLocaleName ? (dbi.data.locales.has(guildLocaleName) ? dbi.data.locales.get(guildLocaleName) : dbi.data.locales.get(dbi.config.defaults.locale.name)) : null; 40 | 41 | let locale = guildLocale ? { guild: guildLocale } : null; 42 | 43 | 44 | let awaitedEvents = []; 45 | let unAwaitedEvents = []; 46 | for (let i = 0; i < dbi.data.events.size; i++) { 47 | const value = dbi.data.events.at(i); 48 | if (value.name == eventName) { 49 | if (value.ordered?.await) { 50 | awaitedEvents.push(value); 51 | } else { 52 | unAwaitedEvents.push(value); 53 | } 54 | } 55 | } 56 | 57 | let arg = { eventName, ...ctxArgs, other, locale }; 58 | 59 | for (let i = 0; i < unAwaitedEvents.length; i++) { 60 | const value = unAwaitedEvents[i]; 61 | 62 | if (value?.disabled) continue; 63 | 64 | if (value.ordered?.delayBefore) await new Promise(resolve => setTimeout(resolve, value.ordered.delayBefore)); 65 | 66 | if (!(await dbi.events.trigger("beforeEvent", { ...arg, dbiEvent: value }))) continue; 67 | 68 | if (dbi.config.strict) { 69 | value.onExecute({ ...arg, nextClient: getClientByEvent(value) }); 70 | } else { 71 | try { 72 | value.onExecute({ ...arg, nextClient: getClientByEvent(value) })?.catch(error => { 73 | dbi.events.trigger("eventError", { ...arg, error, dbiEvent: value }); 74 | }); 75 | } catch (error) { 76 | dbi.events.trigger("eventError", { ...arg, error, dbiEvent: value }); 77 | } 78 | } 79 | if (value.ordered?.delayAfter) await new Promise(resolve => setTimeout(resolve, value.ordered.delayAfter)); 80 | } 81 | 82 | for (let i = 0; i < awaitedEvents.length; i++) { 83 | const value = awaitedEvents[i]; 84 | 85 | if (value?.disabled) continue; 86 | 87 | if (!(await dbi.events.trigger("beforeEvent", { ...arg, dbiEvent: value }))) continue; 88 | 89 | if (value.ordered?.delayBefore) await new Promise(resolve => setTimeout(resolve, value.ordered.delayBefore)); 90 | 91 | if (dbi.config.strict) { 92 | await value.onExecute({ ...arg, nextClient: getClientByEvent(value) }); 93 | } else { 94 | try { 95 | await value.onExecute({ ...arg, nextClient: getClientByEvent(value) })?.catch(error => { 96 | dbi.events.trigger("eventError", { ...arg, error, dbiEvent: value }); 97 | }); 98 | } catch (error) { 99 | await dbi.events.trigger("eventError", { ...arg, error, dbiEvent: value }); 100 | } 101 | } 102 | if (value.ordered?.delayAfter) await new Promise(resolve => setTimeout(resolve, value.ordered.delayAfter)); 103 | } 104 | 105 | dbi.events.trigger("afterEvent", arg) 106 | } 107 | 108 | let firstClient = dbi.data.clients.first().client; 109 | let originalEmit = firstClient.emit; 110 | 111 | firstClient.emit = function (eventName, ...args) { 112 | handle(eventName, ...args); 113 | return originalEmit.call(this, eventName, ...args); 114 | } 115 | 116 | return () => { 117 | firstClient.emit = originalEmit; 118 | } 119 | } -------------------------------------------------------------------------------- /src/methods/hookInteractionListeners.ts: -------------------------------------------------------------------------------- 1 | import { DBI } from "../DBI"; 2 | import Discord, { CommandInteractionOption } from "discord.js"; 3 | import { parseCustomId } from "../utils/customId"; 4 | import { NamespaceEnums } from "../../generated/namespaceData"; 5 | 6 | const componentTypes = [ 7 | "Button", 8 | "StringSelectMenu", 9 | "UserSelectMenu", 10 | "RoleSelectMenu", 11 | "ChannelSelectMenu", 12 | "MentionableSelectMenu", 13 | "Modal", 14 | ]; 15 | 16 | export function hookInteractionListeners(dbi: DBI): () => any { 17 | async function handle(inter: Discord.Interaction<"cached">) { 18 | const dbiInter = 19 | (inter as any).dbiChatInput ?? 20 | dbi.data.interactions.find((i) => { 21 | let isUsesCustomId = 22 | inter.isButton() || inter.isAnySelectMenu() || inter.isModalSubmit(); 23 | let parsedId = isUsesCustomId 24 | ? parseCustomId(dbi, (inter as any).customId) 25 | : null; 26 | return ( 27 | (i.type == "ChatInput" && 28 | (inter.isChatInputCommand() || inter.isAutocomplete()) && 29 | i.name == 30 | [ 31 | inter.commandName, 32 | inter.options.getSubcommandGroup(false), 33 | inter.options.getSubcommand(false), 34 | ] 35 | .filter((i) => !!i) 36 | .join(" ")) || 37 | ((i.type == "MessageContextMenu" || i.type == "UserContextMenu") && 38 | (inter.isMessageContextMenuCommand() || 39 | inter.isUserContextMenuCommand()) && 40 | inter.commandName == i.name) || 41 | (componentTypes.includes(i.type) && 42 | isUsesCustomId && 43 | parsedId?.name == i.name) 44 | ); 45 | }); 46 | 47 | if (!dbiInter) return; 48 | 49 | let userLocaleName = inter.locale.split("-")[0]; 50 | let userLocale = dbi.data.locales.has(userLocaleName) 51 | ? dbi.data.locales.get(userLocaleName) 52 | : dbi.data.locales.get(dbi.config.defaults.locale.name); 53 | 54 | let guildLocaleName = inter.guild 55 | ? inter.guild.preferredLocale.split("-")[0] 56 | : null; 57 | let guildLocale = guildLocaleName 58 | ? dbi.data.locales.has(guildLocaleName) 59 | ? dbi.data.locales.get(guildLocaleName) 60 | : dbi.data.locales.get(dbi.config.defaults.locale.name) 61 | : null; 62 | 63 | let locale = { 64 | user: userLocale, 65 | guild: guildLocale, 66 | }; 67 | 68 | let data = 69 | inter.isButton() || inter.isAnySelectMenu() || inter.isModalSubmit() 70 | ? parseCustomId(dbi, inter.customId).data 71 | : undefined; 72 | 73 | let other = {}; 74 | 75 | if ( 76 | !(await dbi.events.trigger("beforeInteraction", { 77 | dbi, 78 | interaction: inter, 79 | locale, 80 | setRateLimit, 81 | data, 82 | other, 83 | dbiInteraction: dbiInter, 84 | })) 85 | ) 86 | return; 87 | 88 | if (inter.isAutocomplete()) { 89 | let focussed = inter.options.getFocused(true); 90 | let option = (dbiInter.options as any[]).find( 91 | (i) => i.name == focussed.name 92 | ); 93 | if (option?.validate) { 94 | const res = await option.validate({ 95 | value: focussed.value, 96 | interaction: inter, 97 | dbiInteraction: dbiInter, 98 | dbi, 99 | data, 100 | other, 101 | locale, 102 | step: "Autocomplete", 103 | }); 104 | 105 | if (Array.isArray(res) && res.length > 0) { 106 | await inter.respond(res); 107 | return; 108 | } 109 | 110 | if (res !== true) return; 111 | } 112 | 113 | if (option?.onComplete) { 114 | let response = await option.onComplete({ 115 | value: focussed.value, 116 | interaction: inter, 117 | dbiInteraction: dbiInter, 118 | dbi, 119 | data, 120 | other, 121 | locale, 122 | }); 123 | await inter.respond(response); 124 | } 125 | return; 126 | } 127 | 128 | let rateLimitKeyMap = { 129 | User: `${dbiInter.name}_${inter.user.id}`, 130 | Channel: `${dbiInter.name}_${inter.channelId || "Channel"}`, 131 | Guild: `${dbiInter.name}_${inter.guildId || "Guild"}`, 132 | Member: `${dbiInter.name}_${inter.user.id}_${inter.guildId || "Guild"}`, 133 | Message: `${dbiInter.name}_${(inter as any)?.message?.id}`, 134 | }; 135 | 136 | for (const type in rateLimitKeyMap) { 137 | // @ts-ignore 138 | let key = `RateLimit["${rateLimitKeyMap[type]}"]`; 139 | let val = await dbi.config.store.get(key); 140 | if (val && Date.now() > val.at + val.duration) { 141 | await dbi.config.store.delete(key); 142 | val = null; 143 | } 144 | if (val) { 145 | if ( 146 | (await dbi.events.trigger("interactionRateLimit", { 147 | dbi, 148 | interaction: inter, 149 | dbiInteraction: dbiInter, 150 | locale, 151 | data, 152 | rateLimit: { 153 | type: key, 154 | ...val, 155 | }, 156 | })) === true 157 | ) 158 | return; 159 | } 160 | } 161 | 162 | async function setRateLimit(type: string, duration: number) { 163 | // @ts-ignore 164 | await dbi.config.store.set(`RateLimit["${rateLimitKeyMap[type]}"]`, { 165 | at: Date.now(), 166 | duration, 167 | }); 168 | } 169 | 170 | for (const rateLimit of dbiInter.rateLimits) { 171 | await setRateLimit(rateLimit.type, rateLimit.duration); 172 | } 173 | 174 | if (inter.isChatInputCommand()) { 175 | let dcOptions = (inter.options as any) 176 | ._hoistedOptions as CommandInteractionOption[]; 177 | let dbiOptions = dbiInter.options as any[]; 178 | for (const dcOption of dcOptions) { 179 | const dbiOption = dbiOptions.find((i) => i.name == dcOption.name); 180 | if (dbiOption?.validate) { 181 | const res = await dbiOption.validate({ 182 | value: 183 | dbiOption.type === Discord.ApplicationCommandOptionType.Attachment 184 | ? dcOption.attachment 185 | : dbiOption.type === 186 | Discord.ApplicationCommandOptionType.Channel 187 | ? dcOption.channel 188 | : dbiOption.type === Discord.ApplicationCommandOptionType.Role 189 | ? dcOption.role 190 | : dbiOption.type === Discord.ApplicationCommandOptionType.User 191 | ? dcOption.user 192 | : dcOption.value, 193 | interaction: inter, 194 | dbiInteraction: dbiInter, 195 | dbi, 196 | data, 197 | other, 198 | locale, 199 | step: "Result", 200 | }); 201 | if (res !== true) return; 202 | } 203 | } 204 | } 205 | 206 | let arg = { 207 | // @ts-ignore 208 | dbi, 209 | // @ts-ignore 210 | interaction: inter as any, 211 | // @ts-ignore 212 | dbiInteraction: dbiInter, 213 | // @ts-ignore 214 | locale, 215 | // @ts-ignore 216 | setRateLimit, 217 | // @ts-ignore 218 | data, 219 | // @ts-ignore 220 | other, 221 | }; 222 | 223 | if (dbi.config.strict) { 224 | // @ts-ignore 225 | await dbiInter.onExecute(arg); 226 | } else { 227 | try { 228 | // @ts-ignore 229 | await dbiInter.onExecute(arg); 230 | } catch (error) { 231 | // @ts-ignore 232 | await dbi.events.trigger( 233 | "interactionError", 234 | Object.assign(arg, { error }) 235 | ); 236 | } 237 | } 238 | 239 | // @ts-ignore 240 | dbi.events.trigger("afterInteraction", { 241 | dbi, 242 | interaction: inter, 243 | dbiInteraction: dbiInter, 244 | locale, 245 | setRateLimit, 246 | data, 247 | other, 248 | }); 249 | } 250 | 251 | dbi.data.clients.forEach((d) => { 252 | d.client.on("interactionCreate", handle); 253 | }); 254 | 255 | return () => { 256 | dbi.data.clients.forEach((d) => { 257 | d.client.off("interactionCreate", handle); 258 | }); 259 | }; 260 | } 261 | -------------------------------------------------------------------------------- /src/methods/publishInteractions.ts: -------------------------------------------------------------------------------- 1 | import Discord from "discord.js"; 2 | import { DBIChatInput } from "../types/ChatInput/ChatInput"; 3 | import { REST } from "@discordjs/rest"; 4 | import { Routes, RESTGetAPIUserResult, RESTPutAPIApplicationCommandsJSONBody, ApplicationCommandType, ApplicationCommandOptionType } from "discord-api-types/v9"; 5 | import { reducePermissions } from "../utils/permissions"; 6 | import snakecaseKeys from "snakecase-keys"; 7 | import { DBI, TDBIClientData } from "../DBI"; 8 | import { DBIInteractionLocale } from "../types/other/InteractionLocale"; 9 | import { NamespaceEnums } from "../../generated/namespaceData"; 10 | import { ApplicationRoleConnectionMetadataType, ApplicationRoleConnectionMetadata } from "../types/ApplicationRoleConnectionMetadata"; 11 | 12 | const PUBLISHABLE_TYPES = ["ChatInput", "UserContextMenu", "MessageContextMenu"]; 13 | const ORIGINAL_LOCALES = ["da", "de", "en-GB", "en-US", "es-ES", "fr", "hr", "it", "lt", "hu", "nl", "no", "pl", "pt-BR", "ro", "fi", "sv-SE", "vi", "tr", "cs", "el", "bg", "ru", "uk", "hi", "th", "zh-CN", "ja", "zh-TW", "ko"]; 14 | 15 | export async function publishInteractions( 16 | clients: TDBIClientData[], 17 | interactions: Discord.Collection>, 18 | interactionsLocales: Discord.Collection, 19 | publishType: "Guild" | "Global", 20 | guildId?: string 21 | ) { 22 | interactions = interactions.filter(i => PUBLISHABLE_TYPES.includes(i.type)); 23 | interactions = interactions.sort((a, b) => b.name.split(" ").length - a.name.split(" ").length); 24 | 25 | let body: { [k: string]: RESTPutAPIApplicationCommandsJSONBody } = 26 | interactions.reduce((all, current) => { 27 | if (current.publish && !all[current.publish]) all[current.publish] = []; 28 | switch (current.type) { 29 | case "ChatInput": { 30 | let nameSplitted = current.name.split(" "); 31 | let localeData = formatLocale(interactionsLocales.get(current.name) ?? {} as any); 32 | switch (nameSplitted.length) { 33 | case 1: { 34 | all[current.publish].push({ 35 | type: ApplicationCommandType.ChatInput, 36 | description: current.description, 37 | name: nameSplitted[0], 38 | default_member_permissions: reducePermissions(current.defaultMemberPermissions).toString(), 39 | dm_permission: current.directMessages, 40 | options: localeifyOptions(current.options || [], localeData.optionsLocales), 41 | name_localizations: localeData.nameLocales(0), 42 | description_localizations: localeData.descriptionLocales, 43 | }); 44 | break; 45 | } 46 | case 2: { 47 | let baseItem = all[current.publish].find(i => i.name == current.name.split(" ")[0] && i.type == ApplicationCommandType.ChatInput); 48 | let localeData = formatLocale(interactionsLocales.get(current.name) ?? {} as any); 49 | let option = { 50 | type: ApplicationCommandOptionType.Subcommand, 51 | name: nameSplitted[1], 52 | description: current.description, 53 | dm_permission: current.directMessages, 54 | options: localeifyOptions(current.options || [], localeData.optionsLocales), 55 | name_localizations: localeData.nameLocales(1), 56 | description_localizations: localeData.descriptionLocales, 57 | }; 58 | if (!baseItem) { 59 | all[current.publish].push({ 60 | type: ApplicationCommandType.ChatInput, 61 | name: nameSplitted[0], 62 | default_member_permissions: reducePermissions(current.defaultMemberPermissions).toString(), 63 | name_localizations: localeData.nameLocales(0), 64 | description: "...", 65 | options: [ 66 | option 67 | ] 68 | }); 69 | } else { 70 | baseItem.options.push(option); 71 | } 72 | break; 73 | } 74 | case 3: { 75 | let level1Item = all[current.publish].find(i => i.name == current.name.split(" ")[0] && i.type == ApplicationCommandType.ChatInput); 76 | let localeData = formatLocale(interactionsLocales.get(current.name) ?? {} as any); 77 | if (!level1Item) { 78 | all[current.publish].push({ 79 | type: ApplicationCommandType.ChatInput, 80 | name: nameSplitted[0], 81 | name_localizations: localeData.nameLocales(0), 82 | default_member_permissions: reducePermissions(current.defaultMemberPermissions).toString(), 83 | description: "...", 84 | options: [ 85 | { 86 | type: ApplicationCommandOptionType.SubcommandGroup, 87 | name: nameSplitted[1], 88 | name_localizations: localeData.nameLocales(1), 89 | description: "...", 90 | options: [ 91 | { 92 | type: ApplicationCommandOptionType.Subcommand, 93 | name: nameSplitted[2], 94 | description: current.description, 95 | dm_permission: current.directMessages, 96 | options: localeifyOptions(current.options || [], localeData.optionsLocales), 97 | name_localizations: localeData.nameLocales(2), 98 | description_localizations: localeData.descriptionLocales, 99 | } 100 | ] 101 | } 102 | ] 103 | }); 104 | } else { 105 | let level2Item = level1Item.options.find(i => i.name == current.name.split(" ")[1]); 106 | if (!level2Item) { 107 | level1Item.options.push({ 108 | type: ApplicationCommandOptionType.SubcommandGroup, 109 | name: nameSplitted[1], 110 | name_localizations: localeData.nameLocales(1), 111 | description: "...", 112 | options: [ 113 | { 114 | type: ApplicationCommandOptionType.Subcommand, 115 | name: nameSplitted[2], 116 | description: current.description, 117 | dm_permission: current.directMessages, 118 | options: localeifyOptions(current.options || [], localeData.optionsLocales), 119 | name_localizations: localeData.nameLocales(2), 120 | description_localizations: localeData.descriptionLocales 121 | } 122 | ] 123 | }) 124 | } else { 125 | level2Item.options.push({ 126 | type: ApplicationCommandOptionType.Subcommand, 127 | name: nameSplitted[2], 128 | description: current.description, 129 | dm_permission: current.directMessages, 130 | options: localeifyOptions(current.options || [], localeData.optionsLocales), 131 | name_localizations: localeData.nameLocales(2), 132 | description_localizations: localeData.descriptionLocales, 133 | }); 134 | } 135 | } 136 | break; 137 | } 138 | } 139 | break; 140 | } 141 | case "MessageContextMenu": { 142 | let localeData = formatLocale(interactionsLocales.get(current.name) ?? {} as any); 143 | all[current.publish].push({ 144 | type: ApplicationCommandType.Message, 145 | name: current.name, 146 | default_member_permissions: reducePermissions(current.defaultMemberPermissions).toString(), 147 | dm_permission: current.directMessages, 148 | name_localizations: localeData.allNameLocales, 149 | description_localizations: localeData.descriptionLocales, 150 | }); 151 | break; 152 | } 153 | case "UserContextMenu": { 154 | let localeData = formatLocale(interactionsLocales.get(current.name) ?? {} as any); 155 | all[current.publish].push({ 156 | type: ApplicationCommandType.User, 157 | name: current.name, 158 | default_member_permissions: reducePermissions(current.defaultMemberPermissions).toString(), 159 | dm_permission: current.directMessages, 160 | name_localizations: localeData.allNameLocales, 161 | description_localizations: localeData.descriptionLocales, 162 | }); 163 | break; 164 | } 165 | } 166 | 167 | return all; 168 | }, {} as { [k: string]: any }); 169 | 170 | 171 | for (let i = 0; i < clients.length; i++) { 172 | const client = clients[i]; 173 | const rest = new REST({ version: "10" }); 174 | rest.setToken(client.token); 175 | 176 | const me: RESTGetAPIUserResult = await rest.get(Routes.user()) as any; 177 | 178 | switch (publishType) { 179 | case "Guild": { 180 | await rest.put(Routes.applicationGuildCommands(me.id, guildId), { body: body[client.namespace] || [] }); 181 | break; 182 | } 183 | case "Global": { 184 | await rest.put(Routes.applicationCommands(me.id), { body: body[client.namespace] || [] }); 185 | break; 186 | } 187 | } 188 | } 189 | 190 | } 191 | 192 | export function localeifyOptions(options: any[], localeData: any): any[] { 193 | return options.map(i => { 194 | let optionData = localeData[i.name]; 195 | return optionData ? Object.assign(i, { 196 | name_localizations: optionData.nameLocales, 197 | description_localizations: optionData.descriptionLocales, 198 | choices: i.choices ? i.choices.map((j) => { 199 | let choiceLocale = optionData.choiceLocales[j.value ?? j.name]; 200 | return choiceLocale ? Object.assign(j, { 201 | name_localizations: choiceLocale 202 | }) : j; 203 | }) : undefined 204 | }) : i; 205 | }) 206 | } 207 | 208 | export function formatLocale(locale: DBIInteractionLocale): any { 209 | let allNameLocales = {}; 210 | let descriptionLocales = {}; 211 | let optionsLocales = {}; 212 | 213 | function nameLocales(index) { 214 | return Object.fromEntries(Object.entries(allNameLocales).map(i => [i[0], (i[1] as string).split(" ").at(index)])); 215 | } 216 | 217 | if (!locale?.data) return { 218 | nameLocales, 219 | allNameLocales, 220 | descriptionLocales, 221 | optionsLocales 222 | }; 223 | 224 | Object.entries(locale.data).forEach(([shortLocale, localeData]) => { 225 | let longAliases = ORIGINAL_LOCALES.filter(i => i.split("-").at(0) == shortLocale); 226 | longAliases.forEach((longLocale) => { 227 | allNameLocales[longLocale] = localeData.name; 228 | descriptionLocales[longLocale] = localeData.description; 229 | Object.entries(localeData?.options || {}).forEach(([optionName, optionData]) => { 230 | if (!optionsLocales[optionName]) optionsLocales[optionName] = {}; 231 | let optionLocale = optionsLocales[optionName]; 232 | if (!optionLocale.nameLocales) optionLocale.nameLocales = {}; 233 | if (!optionLocale.descriptionLocales) optionLocale.descriptionLocales = {}; 234 | if (!optionLocale.choiceLocales) optionLocale.choiceLocales = {}; 235 | 236 | Object.entries(optionData?.choices || {}).forEach(([choiceOriginalName, choiceName]) => { 237 | if (!optionLocale.choiceLocales) optionLocale.choiceLocales = {}; 238 | if (!optionLocale.choiceLocales[choiceOriginalName]) optionLocale.choiceLocales[choiceOriginalName] = {}; 239 | let choiceLocale = optionLocale.choiceLocales[choiceOriginalName]; 240 | 241 | choiceLocale[longLocale] = choiceName; 242 | }); 243 | 244 | optionLocale.nameLocales[longLocale] = optionData.name; 245 | optionLocale.descriptionLocales[longLocale] = optionData.description; 246 | }) 247 | }); 248 | }); 249 | 250 | return { 251 | nameLocales, 252 | allNameLocales, 253 | descriptionLocales, 254 | optionsLocales 255 | } 256 | } -------------------------------------------------------------------------------- /src/types/ApplicationRoleConnectionMetadata.ts: -------------------------------------------------------------------------------- 1 | export enum ApplicationRoleConnectionMetadataType { 2 | INTEGER_LESS_THAN_OR_EQUAL = 1, 3 | INTEGER_GREATER_THAN_OR_EQUAL = 2, 4 | INTEGER_EQUAL = 3, 5 | INTEGER_NOT_EQUAL = 4, 6 | DATETIME_LESS_THAN_OR_EQUAL = 5, 7 | DATETIME_GREATER_THAN_OR_EQUAL = 6, 8 | BOOLEAN_EQUAL = 7, 9 | BOOLEAN_NOT_EQUAL = 8, 10 | } 11 | 12 | export interface ApplicationRoleConnectionMetadata { 13 | type: ApplicationRoleConnectionMetadataType; 14 | key: string; 15 | name: string; 16 | name_localizations?: { [key: string]: string }; 17 | description: string; 18 | description_localizations?: { [key: string]: string }; 19 | } 20 | -------------------------------------------------------------------------------- /src/types/Builders/ButtonBuilder.ts: -------------------------------------------------------------------------------- 1 | import { ButtonComponentData, ButtonStyle } from "discord.js"; 2 | import { defaultify } from "stuffs"; 3 | import { NamespaceEnums } from "../../../generated/namespaceData"; 4 | import { DBIButton } from "../Components/Button"; 5 | import { RecursivePartial } from "../../utils/UtilTypes"; 6 | 7 | export type DBIButtonOverrides = RecursivePartial<{ style?: ButtonStyle } & Omit> 8 | 9 | export class DBIButtonBuilder { 10 | component: DBIButton 11 | overrides: DBIButtonOverrides; 12 | reference: { data: (string | number | object | boolean | null | undefined)[], ttl?: number }; 13 | constructor(arg: { component: DBIButton, overrides?: DBIButtonOverrides, reference?: { data: (string | number | object | boolean | null | undefined)[], ttl?: number } }) { 14 | this.component = arg.component; 15 | this.overrides = arg.overrides ?? {}; 16 | this.reference = arg.reference ?? { data: [] }; 17 | } 18 | 19 | setTTL(ttl: number): DBIButtonBuilder { 20 | this.reference.ttl = ttl; 21 | return this; 22 | } 23 | 24 | addTTL(ttl: number): DBIButtonBuilder { 25 | this.reference.ttl = (this.reference.ttl ?? 0) + ttl; 26 | return this; 27 | } 28 | 29 | setData(...data: (string | number | object | boolean | null | undefined)[]): DBIButtonBuilder { 30 | this.reference.data = data; 31 | return this; 32 | } 33 | 34 | addData(...data: (string | number | object | boolean | null | undefined)[]): DBIButtonBuilder { 35 | this.reference.data = [...this.reference.data, ...data]; 36 | return this; 37 | } 38 | 39 | setOverrides(overrides: DBIButtonOverrides): DBIButtonBuilder { 40 | this.overrides = overrides; 41 | return this; 42 | } 43 | 44 | addOverrides(overrides: DBIButtonOverrides): DBIButtonBuilder { 45 | this.overrides = defaultify(overrides, this.overrides, true); 46 | return this; 47 | } 48 | 49 | toJSON(): ButtonComponentData { 50 | return this.component.toJSON({ overrides: this.overrides, reference: this.reference }); 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /src/types/Builders/ChannelSelectMenuBuilder.ts: -------------------------------------------------------------------------------- 1 | import { BaseSelectMenuComponentData, ChannelSelectMenuComponentData } from "discord.js"; 2 | import { defaultify } from "stuffs"; 3 | import { NamespaceEnums } from "../../../generated/namespaceData"; 4 | import { DBIChannelSelectMenu } from "../Components/ChannelSelectMenu"; 5 | import { RecursivePartial } from "../../utils/UtilTypes"; 6 | 7 | export type DBIChannelSelectMenuOverrides = RecursivePartial> 8 | 9 | export class DBIChannelSelectMenuBuilder { 10 | component: DBIChannelSelectMenu 11 | overrides: DBIChannelSelectMenuOverrides; 12 | reference: { data: (string | number | object | boolean | null | undefined)[], ttl?: number }; 13 | constructor(arg: { component: DBIChannelSelectMenu, overrides?: DBIChannelSelectMenuOverrides, reference?: { data: (string | number | object | boolean | null | undefined)[], ttl?: number } }) { 14 | this.component = arg.component; 15 | this.overrides = arg.overrides ?? {}; 16 | this.reference = arg.reference ?? { data: [] }; 17 | } 18 | 19 | setTTL(ttl: number): DBIChannelSelectMenuBuilder { 20 | this.reference.ttl = ttl; 21 | return this; 22 | } 23 | 24 | addTTL(ttl: number): DBIChannelSelectMenuBuilder { 25 | this.reference.ttl = (this.reference.ttl ?? 0) + ttl; 26 | return this; 27 | } 28 | 29 | setData(...data: (string | number | object | boolean | null | undefined)[]): DBIChannelSelectMenuBuilder { 30 | this.reference.data = data; 31 | return this; 32 | } 33 | 34 | addData(...data: (string | number | object | boolean | null | undefined)[]): DBIChannelSelectMenuBuilder { 35 | this.reference.data = [...this.reference.data, ...data]; 36 | return this; 37 | } 38 | 39 | setOverrides(overrides: DBIChannelSelectMenuOverrides): DBIChannelSelectMenuBuilder { 40 | this.overrides = overrides; 41 | return this; 42 | } 43 | 44 | addOverrides(overrides: DBIChannelSelectMenuOverrides): DBIChannelSelectMenuBuilder { 45 | this.overrides = defaultify(overrides, this.overrides, true); 46 | return this; 47 | } 48 | 49 | toJSON(): BaseSelectMenuComponentData { 50 | return this.component.toJSON({ overrides: this.overrides as any, reference: this.reference }); 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /src/types/Builders/MentionableSelectMenuBuilder.ts: -------------------------------------------------------------------------------- 1 | import { BaseSelectMenuComponentData, MentionableSelectMenuComponentData } from "discord.js"; 2 | import { defaultify } from "stuffs"; 3 | import { NamespaceEnums } from "../../../generated/namespaceData"; 4 | import { DBIMentionableSelectMenu } from "../Components/MentionableSelectMenu"; 5 | import { RecursivePartial } from "../../utils/UtilTypes"; 6 | 7 | export type DBIMentionableSelectMenuOverrides = RecursivePartial> 8 | 9 | export class DBIMentionableSelectMenuBuilder { 10 | component: DBIMentionableSelectMenu 11 | overrides: DBIMentionableSelectMenuOverrides; 12 | reference: { data: (string | number | object | boolean | null | undefined)[], ttl?: number }; 13 | constructor(arg: { component: DBIMentionableSelectMenu, overrides?: DBIMentionableSelectMenuOverrides, reference?: { data: (string | number | object | boolean | null | undefined)[], ttl?: number } }) { 14 | this.component = arg.component; 15 | this.overrides = arg.overrides ?? {}; 16 | this.reference = arg.reference ?? { data: [] }; 17 | } 18 | 19 | setTTL(ttl: number): DBIMentionableSelectMenuBuilder { 20 | this.reference.ttl = ttl; 21 | return this; 22 | } 23 | 24 | addTTL(ttl: number): DBIMentionableSelectMenuBuilder { 25 | this.reference.ttl = (this.reference.ttl ?? 0) + ttl; 26 | return this; 27 | } 28 | 29 | setData(...data: (string | number | object | boolean | null | undefined)[]): DBIMentionableSelectMenuBuilder { 30 | this.reference.data = data; 31 | return this; 32 | } 33 | 34 | addData(...data: (string | number | object | boolean | null | undefined)[]): DBIMentionableSelectMenuBuilder { 35 | this.reference.data = [...this.reference.data, ...data]; 36 | return this; 37 | } 38 | 39 | setOverrides(overrides: DBIMentionableSelectMenuOverrides): DBIMentionableSelectMenuBuilder { 40 | this.overrides = overrides; 41 | return this; 42 | } 43 | 44 | addOverrides(overrides: DBIMentionableSelectMenuOverrides): DBIMentionableSelectMenuBuilder { 45 | this.overrides = defaultify(overrides, this.overrides, true); 46 | return this; 47 | } 48 | 49 | toJSON(): BaseSelectMenuComponentData { 50 | return this.component.toJSON({ overrides: this.overrides as any, reference: this.reference }); 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /src/types/Builders/ModalBuilder.ts: -------------------------------------------------------------------------------- 1 | import { ActionRowData, APIActionRowComponent, APITextInputComponent, JSONEncodable, ModalActionRowComponentData, ModalComponentData } from "discord.js"; 2 | import { defaultify } from "stuffs"; 3 | import { NamespaceEnums } from "../../../generated/namespaceData"; 4 | import { DBIModal } from "../Components/Modal"; 5 | import { RecursivePartial } from "../../utils/UtilTypes"; 6 | 7 | export type DBIModalOverrides = RecursivePartial<{ components?: (JSONEncodable> | ActionRowData)[], title?: string } & Omit> 8 | 9 | export class DBIModalBuilder { 10 | component: DBIModal 11 | overrides: DBIModalOverrides; 12 | reference: { data: (string | number | object | boolean | null | undefined)[], ttl?: number }; 13 | constructor(arg: { component: DBIModal, overrides?: DBIModalOverrides, reference?: { data: (string | number | object | boolean | null | undefined)[], ttl?: number } }) { 14 | this.component = arg.component; 15 | this.overrides = arg.overrides ?? {}; 16 | this.reference = arg.reference ?? { data: [] }; 17 | } 18 | 19 | setTTL(ttl: number): DBIModalBuilder { 20 | this.reference.ttl = ttl; 21 | return this; 22 | } 23 | 24 | addTTL(ttl: number): DBIModalBuilder { 25 | this.reference.ttl = (this.reference.ttl ?? 0) + ttl; 26 | return this; 27 | } 28 | 29 | setData(...data: (string | number | object | boolean | null | undefined)[]): DBIModalBuilder { 30 | this.reference.data = data; 31 | return this; 32 | } 33 | 34 | addData(...data: (string | number | object | boolean | null | undefined)[]): DBIModalBuilder { 35 | this.reference.data = [...this.reference.data, ...data]; 36 | return this; 37 | } 38 | 39 | setOverrides(overrides: DBIModalOverrides): DBIModalBuilder { 40 | this.overrides = overrides; 41 | return this; 42 | } 43 | 44 | addOverrides(overrides: DBIModalOverrides): DBIModalBuilder { 45 | this.overrides = defaultify(overrides, this.overrides, true); 46 | return this; 47 | } 48 | 49 | toJSON(): ModalComponentData { 50 | return this.component.toJSON({ overrides: this.overrides, reference: this.reference }); 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /src/types/Builders/RoleSelectMenuBuilder.ts: -------------------------------------------------------------------------------- 1 | import { BaseSelectMenuComponentData, RoleSelectMenuComponentData } from "discord.js"; 2 | import { defaultify } from "stuffs"; 3 | import { NamespaceEnums } from "../../../generated/namespaceData"; 4 | import { DBIRoleSelectMenu } from "../Components/RoleSelectMenu"; 5 | import { RecursivePartial } from "../../utils/UtilTypes"; 6 | 7 | export type DBIRoleSelectMenuOverrides = RecursivePartial> 8 | 9 | export class DBIRoleSelectMenuBuilder { 10 | component: DBIRoleSelectMenu 11 | overrides: Partial; 12 | reference: { data: (string | number | object | boolean | null | undefined)[], ttl?: number }; 13 | constructor(arg: { component: DBIRoleSelectMenu, overrides?: DBIRoleSelectMenuOverrides, reference?: { data: (string | number | object | boolean | null | undefined)[], ttl?: number } }) { 14 | this.component = arg.component; 15 | this.overrides = arg.overrides ?? {}; 16 | this.reference = arg.reference ?? { data: [] }; 17 | } 18 | 19 | setTTL(ttl: number): DBIRoleSelectMenuBuilder { 20 | this.reference.ttl = ttl; 21 | return this; 22 | } 23 | 24 | addTTL(ttl: number): DBIRoleSelectMenuBuilder { 25 | this.reference.ttl = (this.reference.ttl ?? 0) + ttl; 26 | return this; 27 | } 28 | 29 | setData(...data: (string | number | object | boolean | null | undefined)[]): DBIRoleSelectMenuBuilder { 30 | this.reference.data = data; 31 | return this; 32 | } 33 | 34 | addData(...data: (string | number | object | boolean | null | undefined)[]): DBIRoleSelectMenuBuilder { 35 | this.reference.data = [...this.reference.data, ...data]; 36 | return this; 37 | } 38 | 39 | setOverrides(overrides: DBIRoleSelectMenuOverrides): DBIRoleSelectMenuBuilder { 40 | this.overrides = overrides; 41 | return this; 42 | } 43 | 44 | addOverrides(overrides: DBIRoleSelectMenuOverrides): DBIRoleSelectMenuBuilder { 45 | this.overrides = defaultify(overrides, this.overrides, true); 46 | return this; 47 | } 48 | 49 | toJSON(): BaseSelectMenuComponentData { 50 | return this.component.toJSON({ overrides: this.overrides as any, reference: this.reference }); 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /src/types/Builders/StringSelectMenuBuilder.ts: -------------------------------------------------------------------------------- 1 | import { BaseSelectMenuComponentData, StringSelectMenuComponentData } from "discord.js"; 2 | import { defaultify } from "stuffs"; 3 | import { NamespaceEnums } from "../../../generated/namespaceData"; 4 | import { DBIStringSelectMenu } from "../Components/StringSelectMenu"; 5 | import { RecursivePartial } from "../../utils/UtilTypes"; 6 | 7 | export type DBIStringSelectMenuOverrides = RecursivePartial> 8 | 9 | export class DBIStringSelectMenuBuilder { 10 | component: DBIStringSelectMenu 11 | overrides: DBIStringSelectMenuOverrides; 12 | reference: { data: (string | number | object | boolean | null | undefined)[], ttl?: number }; 13 | constructor(arg: { component: DBIStringSelectMenu, overrides?: DBIStringSelectMenuOverrides, reference?: { data: (string | number | object | boolean | null | undefined)[], ttl?: number } }) { 14 | this.component = arg.component; 15 | this.overrides = arg.overrides ?? {}; 16 | this.reference = arg.reference ?? { data: [] }; 17 | } 18 | 19 | setTTL(ttl: number): DBIStringSelectMenuBuilder { 20 | this.reference.ttl = ttl; 21 | return this; 22 | } 23 | 24 | addTTL(ttl: number): DBIStringSelectMenuBuilder { 25 | this.reference.ttl = (this.reference.ttl ?? 0) + ttl; 26 | return this; 27 | } 28 | 29 | setData(...data: (string | number | object | boolean | null | undefined)[]): DBIStringSelectMenuBuilder { 30 | this.reference.data = data; 31 | return this; 32 | } 33 | 34 | addData(...data: (string | number | object | boolean | null | undefined)[]): DBIStringSelectMenuBuilder { 35 | this.reference.data = [...this.reference.data, ...data]; 36 | return this; 37 | } 38 | 39 | setOverrides(overrides: DBIStringSelectMenuOverrides): DBIStringSelectMenuBuilder { 40 | this.overrides = overrides; 41 | return this; 42 | } 43 | 44 | addOverrides(overrides: DBIStringSelectMenuOverrides): DBIStringSelectMenuBuilder { 45 | this.overrides = defaultify(overrides, this.overrides, true); 46 | return this; 47 | } 48 | 49 | toJSON(): BaseSelectMenuComponentData { 50 | return this.component.toJSON({ overrides: this.overrides as any, reference: this.reference }); 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /src/types/Builders/UserSelectMenuBuilder.ts: -------------------------------------------------------------------------------- 1 | import { BaseSelectMenuComponentData, UserSelectMenuComponentData } from "discord.js"; 2 | import { defaultify } from "stuffs"; 3 | import { NamespaceEnums } from "../../../generated/namespaceData"; 4 | import { DBIUserSelectMenu } from "../Components/UserSelectMenu"; 5 | import { RecursivePartial } from "../../utils/UtilTypes"; 6 | 7 | export type DBIUserSelectMenuOverrides = RecursivePartial> 8 | 9 | export class DBIUserSelectMenuBuilder { 10 | component: DBIUserSelectMenu 11 | overrides: DBIUserSelectMenuOverrides; 12 | reference: { data: (string | number | object | boolean | null | undefined)[], ttl?: number }; 13 | constructor(arg: { component: DBIUserSelectMenu, overrides?: DBIUserSelectMenuOverrides, reference?: { data: (string | number | object | boolean | null | undefined)[], ttl?: number } }) { 14 | this.component = arg.component; 15 | this.overrides = arg.overrides ?? {}; 16 | this.reference = arg.reference ?? { data: [] }; 17 | } 18 | 19 | setTTL(ttl: number): DBIUserSelectMenuBuilder { 20 | this.reference.ttl = ttl; 21 | return this; 22 | } 23 | 24 | addTTL(ttl: number): DBIUserSelectMenuBuilder { 25 | this.reference.ttl = (this.reference.ttl ?? 0) + ttl; 26 | return this; 27 | } 28 | 29 | setData(...data: (string | number | object | boolean | null | undefined)[]): DBIUserSelectMenuBuilder { 30 | this.reference.data = data; 31 | return this; 32 | } 33 | 34 | addData(...data: (string | number | object | boolean | null | undefined)[]): DBIUserSelectMenuBuilder { 35 | this.reference.data = [...this.reference.data, ...data]; 36 | return this; 37 | } 38 | 39 | setOverrides(overrides: DBIUserSelectMenuOverrides): DBIUserSelectMenuBuilder { 40 | this.overrides = overrides; 41 | return this; 42 | } 43 | 44 | addOverrides(overrides: DBIUserSelectMenuOverrides): DBIUserSelectMenuBuilder { 45 | this.overrides = defaultify(overrides, this.overrides, true); 46 | return this; 47 | } 48 | 49 | toJSON(): BaseSelectMenuComponentData { 50 | return this.component.toJSON({ overrides: this.overrides as any, reference: this.reference }); 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /src/types/ChatInput/ChatInput.ts: -------------------------------------------------------------------------------- 1 | import Discord from "discord.js"; 2 | import { NamespaceEnums } from "../../../generated/namespaceData"; 3 | import { DBI } from "../../DBI"; 4 | import { DBIBaseInteraction, IDBIBaseExecuteCtx } from "../Interaction"; 5 | 6 | export interface IDBIChatInputExecuteCtx extends IDBIBaseExecuteCtx { 7 | interaction: Discord.ChatInputCommandInteraction<"cached">; 8 | } 9 | 10 | export type TDBIChatInputOmitted = Omit, "type" | "dbi" | "ttl" | "at" | "toJSON">; 11 | 12 | export class DBIChatInput extends DBIBaseInteraction { 13 | constructor(dbi: DBI, cfg: TDBIChatInputOmitted) { 14 | super(dbi, { 15 | ...(cfg as any), 16 | type: "ChatInput", 17 | name: cfg.name.toLowerCase(), 18 | options: Array.isArray(cfg.options) ? cfg.options : [] 19 | }); 20 | 21 | this.directMessages = cfg.directMessages ?? dbi.config.defaults.directMessages; 22 | this.defaultMemberPermissions = cfg.defaultMemberPermissions ?? dbi.config.defaults.defaultMemberPermissions; 23 | } 24 | directMessages?: boolean; 25 | defaultMemberPermissions?: Discord.PermissionsString[]; 26 | declare options?: any[]; 27 | override onExecute(ctx: IDBIChatInputExecuteCtx) {} 28 | } -------------------------------------------------------------------------------- /src/types/ChatInput/ChatInputOptions.ts: -------------------------------------------------------------------------------- 1 | import Discord from "discord.js"; 2 | import { NamespaceEnums } from "../../../generated/namespaceData"; 3 | import { DBI } from "../../DBI"; 4 | import { IDBIBaseExecuteCtx } from "../Interaction"; 5 | 6 | export type TDBIValueName = { value: T; name: string }; 7 | export type TDBIBaseOption = { 8 | name: string; 9 | description: string; 10 | required?: boolean; 11 | }; 12 | 13 | export type TDBIMinMaxLength = { maxLength?: number; minLength?: number }; 14 | export type TDBIMinMaxValue = { maxValue?: number; minValue?: number }; 15 | 16 | export type TDBIValidator< 17 | TExtends, 18 | TValue, 19 | TStep extends string, 20 | TExpectedResponse = boolean 21 | > = { 22 | validate?( 23 | cfg: TExtends & { value: TValue; step: TStep } 24 | ): Promise | TExpectedResponse; 25 | }; 26 | 27 | export interface IDBIValuedInteraction< 28 | TNamespace extends NamespaceEnums, 29 | TInteractionType extends Discord.Interaction, 30 | TValueType = string | number 31 | > extends IDBIBaseExecuteCtx { 32 | value: TValueType; 33 | interaction: TInteractionType; 34 | } 35 | 36 | export type TDBICompleteCtx< 37 | TNamespace extends NamespaceEnums, 38 | TValueType = string | number 39 | > = IDBIValuedInteraction< 40 | TNamespace, 41 | Discord.AutocompleteInteraction, 42 | TValueType 43 | >; 44 | 45 | export type TDBIValidateCtx< 46 | TNamespace extends NamespaceEnums, 47 | TValueType = string | number 48 | > = IDBIValuedInteraction; 49 | 50 | export type TDBICompleter< 51 | TNamespace extends NamespaceEnums, 52 | TValueType extends number | string 53 | > = { 54 | onComplete( 55 | ctx: TDBICompleteCtx 56 | ): Promise[]> | TDBIValueName[]; 57 | }; 58 | 59 | export class DBIChatInputOptions { 60 | dbi: DBI; 61 | constructor(dbi: DBI) { 62 | this.dbi = dbi; 63 | } 64 | stringAutocomplete( 65 | cfg: TDBIBaseOption & 66 | TDBIMinMaxLength & { 67 | messageCommands?: { rest?: boolean }; 68 | } & TDBIValidator< 69 | TDBIValidateCtx, 70 | string, 71 | "Autocomplete" | "Result", 72 | boolean | TDBIValueName 73 | > & 74 | TDBICompleter 75 | ) { 76 | return { 77 | type: Discord.ApplicationCommandOptionType.String, 78 | name: cfg.name, 79 | autocomplete: true, 80 | onComplete: cfg.onComplete, 81 | description: cfg.description, 82 | maxLength: cfg.maxLength, 83 | max_length: cfg.maxLength, 84 | minLength: cfg.minLength, 85 | min_length: cfg.minLength, 86 | required: cfg.required, 87 | validate: cfg.validate, 88 | }; 89 | } 90 | stringChoices( 91 | cfg: TDBIBaseOption & 92 | TDBIMinMaxLength & { 93 | choices: TDBIValueName[]; 94 | messageCommands?: { rest?: boolean }; 95 | } & TDBIValidator< 96 | TDBIValidateCtx, 97 | string, 98 | "Result", 99 | boolean 100 | > 101 | ) { 102 | return { 103 | type: Discord.ApplicationCommandOptionType.String, 104 | name: cfg.name, 105 | choices: cfg.choices, 106 | description: cfg.description, 107 | maxLength: cfg.maxLength, 108 | max_length: cfg.maxLength, 109 | minLength: cfg.minLength, 110 | min_length: cfg.minLength, 111 | required: cfg.required, 112 | validate: cfg.validate, 113 | }; 114 | } 115 | 116 | string( 117 | cfg: TDBIBaseOption & 118 | TDBIMinMaxLength & { 119 | messageCommands?: { rest?: boolean }; 120 | } & TDBIValidator< 121 | TDBIValidateCtx, 122 | string, 123 | "Result", 124 | boolean 125 | > 126 | ) { 127 | return { 128 | type: Discord.ApplicationCommandOptionType.String, 129 | name: cfg.name, 130 | description: cfg.description, 131 | maxLength: cfg.maxLength, 132 | max_length: cfg.maxLength, 133 | minLength: cfg.minLength, 134 | min_length: cfg.minLength, 135 | required: cfg.required, 136 | messageCommands: cfg.messageCommands, 137 | validate: cfg.validate, 138 | }; 139 | } 140 | 141 | numberAutocomplete( 142 | cfg: TDBIBaseOption & 143 | TDBIMinMaxValue & 144 | TDBIValidator< 145 | TDBIValidateCtx, 146 | number, 147 | "Autocomplete" | "Result", 148 | boolean | TDBIValueName 149 | > & 150 | TDBICompleter 151 | ) { 152 | return { 153 | type: Discord.ApplicationCommandOptionType.Number, 154 | name: cfg.name, 155 | autocomplete: true, 156 | onComplete: cfg.onComplete, 157 | description: cfg.description, 158 | maxValue: cfg.maxValue, 159 | max_value: cfg.maxValue, 160 | minValue: cfg.minValue, 161 | min_value: cfg.minValue, 162 | required: cfg.required, 163 | validate: cfg.validate, 164 | }; 165 | } 166 | 167 | numberChoices( 168 | cfg: TDBIBaseOption & 169 | TDBIMinMaxValue & { choices: TDBIValueName[] } & TDBIValidator< 170 | TDBIValidateCtx, 171 | number, 172 | "Result", 173 | boolean 174 | > 175 | ) { 176 | return { 177 | type: Discord.ApplicationCommandOptionType.Number, 178 | name: cfg.name, 179 | choices: cfg.choices, 180 | description: cfg.description, 181 | maxValue: cfg.maxValue, 182 | max_value: cfg.maxValue, 183 | minValue: cfg.minValue, 184 | min_value: cfg.minValue, 185 | required: cfg.required, 186 | validate: cfg.validate, 187 | }; 188 | } 189 | 190 | number( 191 | cfg: TDBIBaseOption & 192 | TDBIMinMaxValue & 193 | TDBIValidator< 194 | TDBIValidateCtx, 195 | number, 196 | "Result", 197 | boolean 198 | > 199 | ) { 200 | return { 201 | type: Discord.ApplicationCommandOptionType.Number, 202 | name: cfg.name, 203 | description: cfg.description, 204 | maxValue: cfg.maxValue, 205 | max_value: cfg.maxValue, 206 | minValue: cfg.minValue, 207 | min_value: cfg.minValue, 208 | required: cfg.required, 209 | validate: cfg.validate, 210 | }; 211 | } 212 | 213 | integerAutocomplete( 214 | cfg: TDBIBaseOption & 215 | TDBIMinMaxValue & 216 | TDBIValidator< 217 | TDBIValidateCtx, 218 | number, 219 | "Autocomplete" | "Result", 220 | boolean | TDBIValueName 221 | > & 222 | TDBICompleter 223 | ) { 224 | return { 225 | type: Discord.ApplicationCommandOptionType.Integer, 226 | name: cfg.name, 227 | autocomplete: true, 228 | onComplete: cfg.onComplete, 229 | description: cfg.description, 230 | maxValue: cfg.maxValue, 231 | max_value: cfg.maxValue, 232 | minValue: cfg.minValue, 233 | min_value: cfg.minValue, 234 | required: cfg.required, 235 | validate: cfg.validate, 236 | }; 237 | } 238 | 239 | integerChoices( 240 | cfg: TDBIBaseOption & 241 | TDBIMinMaxValue & { choices: TDBIValueName[] } & TDBIValidator< 242 | TDBIValidateCtx, 243 | number, 244 | "Result", 245 | boolean 246 | > 247 | ) { 248 | return { 249 | type: Discord.ApplicationCommandOptionType.Integer, 250 | name: cfg.name, 251 | choices: cfg.choices, 252 | description: cfg.description, 253 | maxValue: cfg.maxValue, 254 | max_value: cfg.maxValue, 255 | minValue: cfg.minValue, 256 | min_value: cfg.minValue, 257 | required: cfg.required, 258 | validate: cfg.validate, 259 | }; 260 | } 261 | 262 | integer( 263 | cfg: TDBIBaseOption & 264 | TDBIMinMaxValue & 265 | TDBIValidator< 266 | TDBIValidateCtx, 267 | number, 268 | "Result", 269 | boolean 270 | > 271 | ) { 272 | return { 273 | type: Discord.ApplicationCommandOptionType.Integer, 274 | name: cfg.name, 275 | description: cfg.description, 276 | maxValue: cfg.maxValue, 277 | max_value: cfg.maxValue, 278 | minValue: cfg.minValue, 279 | min_value: cfg.minValue, 280 | required: cfg.required, 281 | validate: cfg.validate, 282 | }; 283 | } 284 | 285 | boolean(cfg: TDBIBaseOption) { 286 | return { 287 | type: Discord.ApplicationCommandOptionType.Boolean, 288 | name: cfg.name, 289 | description: cfg.description, 290 | required: cfg.required, 291 | }; 292 | } 293 | 294 | attachment( 295 | cfg: TDBIBaseOption & 296 | TDBIValidator< 297 | TDBIValidateCtx, 298 | Discord.Attachment, 299 | "Result", 300 | boolean 301 | > 302 | ) { 303 | return { 304 | type: Discord.ApplicationCommandOptionType.Attachment, 305 | name: cfg.name, 306 | description: cfg.description, 307 | required: cfg.required, 308 | }; 309 | } 310 | 311 | channel( 312 | cfg: TDBIBaseOption & { 313 | channelTypes: Discord.ChannelType[]; 314 | } & TDBIValidator< 315 | TDBIValidateCtx, 316 | Discord.Channel, 317 | "Result", 318 | boolean 319 | > 320 | ) { 321 | return { 322 | type: Discord.ApplicationCommandOptionType.Channel, 323 | name: cfg.name, 324 | description: cfg.description, 325 | channelTypes: cfg.channelTypes, 326 | channel_types: cfg.channelTypes, 327 | required: cfg.required, 328 | validate: cfg.validate, 329 | }; 330 | } 331 | 332 | role( 333 | cfg: TDBIBaseOption & 334 | TDBIValidator< 335 | TDBIValidateCtx, 336 | Discord.Role, 337 | "Result", 338 | boolean 339 | > 340 | ) { 341 | return { 342 | type: Discord.ApplicationCommandOptionType.Role, 343 | name: cfg.name, 344 | description: cfg.description, 345 | required: cfg.required, 346 | validate: cfg.validate, 347 | }; 348 | } 349 | 350 | mentionable( 351 | cfg: TDBIBaseOption & 352 | TDBIValidator< 353 | TDBIValidateCtx< 354 | TNamespace, 355 | Discord.Role | Discord.Channel | Discord.User 356 | >, 357 | Discord.Role | Discord.Channel | Discord.User, 358 | "Result", 359 | boolean 360 | > 361 | ) { 362 | return { 363 | type: Discord.ApplicationCommandOptionType.Mentionable, 364 | name: cfg.name, 365 | description: cfg.description, 366 | required: cfg.required, 367 | validate: cfg.validate, 368 | }; 369 | } 370 | 371 | user( 372 | cfg: TDBIBaseOption & 373 | TDBIValidator< 374 | TDBIValidateCtx, 375 | Discord.User, 376 | "Result", 377 | boolean 378 | > 379 | ) { 380 | return { 381 | type: Discord.ApplicationCommandOptionType.User, 382 | name: cfg.name, 383 | description: cfg.description, 384 | required: cfg.required, 385 | validate: cfg.validate, 386 | }; 387 | } 388 | } 389 | -------------------------------------------------------------------------------- /src/types/Components/Button.ts: -------------------------------------------------------------------------------- 1 | import Discord from "discord.js"; 2 | import { DBI } from "../../DBI"; 3 | import { DBIBaseInteraction, IDBIBaseExecuteCtx, TDBIReferencedData } from "../Interaction"; 4 | import { buildCustomId } from "../../utils/customId"; 5 | import { IDBIToJSONArgs } from "../../utils/UtilTypes"; 6 | import { NamespaceEnums } from "../../../generated/namespaceData"; 7 | import stuffs from "stuffs"; 8 | import { DBIButtonBuilder, DBIButtonOverrides } from "../Builders/ButtonBuilder"; 9 | 10 | export interface IDBIButtonExecuteCtx extends IDBIBaseExecuteCtx { 11 | interaction: Discord.ButtonInteraction<"cached">; 12 | data: TDBIReferencedData[]; 13 | } 14 | 15 | export type TDBIButtonOmitted = Omit, "type" | "description" | "dbi" | "toJSON" | "createBuilder" | "at">; 16 | 17 | export class DBIButton extends DBIBaseInteraction { 18 | constructor(dbi: DBI, args: TDBIButtonOmitted) { 19 | super(dbi, { 20 | ...(args as any), 21 | type: "Button", 22 | }); 23 | } 24 | 25 | declare options?: Omit; 26 | 27 | override onExecute(ctx: IDBIButtonExecuteCtx) { }; 28 | override toJSON(arg: IDBIToJSONArgs = {}): Discord.ButtonComponentData { 29 | return { 30 | ...stuffs.defaultify((arg?.overrides || {}), this.options || {}, true), 31 | customId: buildCustomId(this.dbi as any, this.name, arg?.reference?.data || [], arg?.reference?.ttl), 32 | type: Discord.ComponentType.Button, 33 | } as any; 34 | }; 35 | createBuilder(arg: IDBIToJSONArgs = {}): DBIButtonBuilder { 36 | return new DBIButtonBuilder({ component: this, ...arg }) 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /src/types/Components/ChannelSelectMenu.ts: -------------------------------------------------------------------------------- 1 | import Discord from "discord.js"; 2 | import { DBI } from "../../DBI"; 3 | import { DBIBaseInteraction, IDBIBaseExecuteCtx, TDBIReferencedData } from "../Interaction"; 4 | import { buildCustomId } from "../../utils/customId"; 5 | import { IDBIToJSONArgs } from "../../utils/UtilTypes"; 6 | import { NamespaceEnums } from "../../../generated/namespaceData"; 7 | import stuffs from "stuffs"; 8 | import { DBIChannelSelectMenuBuilder, DBIChannelSelectMenuOverrides } from "../Builders/ChannelSelectMenuBuilder"; 9 | 10 | export interface IDBIChannelSelectMenuExecuteCtx extends IDBIBaseExecuteCtx { 11 | interaction: Discord.ChannelSelectMenuInteraction<"cached">; 12 | data: TDBIReferencedData[]; 13 | } 14 | 15 | export type TDBIChannelSelectMenuOmitted = Omit, "type" | "description" | "dbi" | "toJSON" | "createBuilder" | "at">; 16 | 17 | export type SelectMenuDefaultOptions = Omit; 18 | 19 | export class DBIChannelSelectMenu extends DBIBaseInteraction { 20 | constructor(dbi: DBI, args: TDBIChannelSelectMenuOmitted) { 21 | super(dbi, { 22 | ...(args as any), 23 | type: "ChannelSelectMenu", 24 | }); 25 | } 26 | 27 | declare options?: SelectMenuDefaultOptions; 28 | 29 | override onExecute(ctx: IDBIChannelSelectMenuExecuteCtx) { }; 30 | 31 | override toJSON(arg: IDBIToJSONArgs = {}): Discord.ChannelSelectMenuComponentData { 32 | return { 33 | ...stuffs.defaultify((arg?.overrides || {}), this.options || {}, true), 34 | customId: buildCustomId(this.dbi as any, this.name, arg?.reference?.data || [], arg?.reference?.ttl), 35 | type: Discord.ComponentType.ChannelSelect, 36 | } as any; 37 | }; 38 | 39 | createBuilder(arg: IDBIToJSONArgs = {}): DBIChannelSelectMenuBuilder { 40 | return new DBIChannelSelectMenuBuilder({ component: this, ...arg }) 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /src/types/Components/MentionableSelectMenu.ts: -------------------------------------------------------------------------------- 1 | import Discord from "discord.js"; 2 | import { DBI } from "../../DBI"; 3 | import { DBIBaseInteraction, IDBIBaseExecuteCtx, TDBIReferencedData } from "../Interaction"; 4 | import { buildCustomId } from "../../utils/customId"; 5 | import { IDBIToJSONArgs } from "../../utils/UtilTypes"; 6 | import { NamespaceEnums } from "../../../generated/namespaceData"; 7 | import stuffs from "stuffs"; 8 | import { DBIMentionableSelectMenuBuilder, DBIMentionableSelectMenuOverrides } from "../Builders/MentionableSelectMenuBuilder"; 9 | 10 | export interface IDBIMentionableSelectMenuExecuteCtx extends IDBIBaseExecuteCtx { 11 | interaction: Discord.MentionableSelectMenuInteraction<"cached">; 12 | data: TDBIReferencedData[]; 13 | } 14 | 15 | export type TDBIMentionableSelectMenuOmitted = Omit, "type" | "description" | "dbi" | "toJSON" | "createBuilder" | "at">; 16 | 17 | export type SelectMenuDefaultOptions = Omit; 18 | 19 | export class DBIMentionableSelectMenu extends DBIBaseInteraction { 20 | constructor(dbi: DBI, args: TDBIMentionableSelectMenuOmitted) { 21 | super(dbi, { 22 | ...(args as any), 23 | type: "MentionableSelectMenu", 24 | }); 25 | } 26 | 27 | declare options?: SelectMenuDefaultOptions; 28 | 29 | override onExecute(ctx: IDBIMentionableSelectMenuExecuteCtx) { }; 30 | 31 | override toJSON(arg: IDBIToJSONArgs = {}): Discord.MentionableSelectMenuComponentData { 32 | return { 33 | ...stuffs.defaultify((arg?.overrides || {}), this.options || {}, true), 34 | customId: buildCustomId(this.dbi as any, this.name, arg?.reference?.data || [], arg?.reference?.ttl), 35 | type: Discord.ComponentType.MentionableSelect, 36 | } as any; 37 | }; 38 | 39 | createBuilder(arg: IDBIToJSONArgs = {}): DBIMentionableSelectMenuBuilder { 40 | return new DBIMentionableSelectMenuBuilder({ component: this, ...arg }) 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /src/types/Components/Modal.ts: -------------------------------------------------------------------------------- 1 | import { DBI } from "../../DBI"; 2 | import { DBIBaseInteraction, IDBIBaseExecuteCtx, TDBIReferencedData } from "../Interaction"; 3 | import Discord from "discord.js"; 4 | import { buildCustomId } from "../../utils/customId"; 5 | import { IDBIToJSONArgs } from "../../utils/UtilTypes"; 6 | import { NamespaceEnums } from "../../../generated/namespaceData"; 7 | import stuffs from "stuffs"; 8 | import { DBIModalBuilder, DBIModalOverrides } from "../Builders/ModalBuilder"; 9 | 10 | 11 | export interface IDBIModalExecuteCtx extends IDBIBaseExecuteCtx { 12 | interaction: Discord.ModalSubmitInteraction<"cached">; 13 | data: TDBIReferencedData[]; 14 | } 15 | 16 | export interface ModalComponentData { 17 | title: string; 18 | components: (Discord.ActionRowData)[]; 19 | } 20 | 21 | export type TDBIModalOmitted = Omit, "type" | "description" | "dbi" | "toJSON" | "createBuilder" | "at">; 22 | 23 | export class DBIModal extends DBIBaseInteraction { 24 | constructor(dbi: DBI, args: TDBIModalOmitted) { 25 | super(dbi, { 26 | ...(args as any), 27 | type: "Modal" 28 | }); 29 | } 30 | 31 | declare options?: ModalComponentData; 32 | 33 | override onExecute(ctx: IDBIModalExecuteCtx) { }; 34 | 35 | override toJSON(arg: IDBIToJSONArgs = {}): Discord.ModalComponentData { 36 | return { 37 | ...stuffs.defaultify((arg?.overrides || {}), this.options || {}, true), 38 | customId: buildCustomId(this.dbi as any, this.name, arg?.reference?.data || [], arg?.reference?.ttl) 39 | } as any; 40 | }; 41 | 42 | createBuilder(arg: IDBIToJSONArgs = {}): DBIModalBuilder { 43 | return new DBIModalBuilder({ component: this, ...arg }) 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /src/types/Components/RoleSelectMenu.ts: -------------------------------------------------------------------------------- 1 | import Discord from "discord.js"; 2 | import { DBI } from "../../DBI"; 3 | import { DBIBaseInteraction, IDBIBaseExecuteCtx, TDBIReferencedData } from "../Interaction"; 4 | import { buildCustomId } from "../../utils/customId"; 5 | import { IDBIToJSONArgs } from "../../utils/UtilTypes"; 6 | import { NamespaceEnums } from "../../../generated/namespaceData"; 7 | import stuffs from "stuffs"; 8 | import { DBIRoleSelectMenuBuilder, DBIRoleSelectMenuOverrides } from "../Builders/RoleSelectMenuBuilder"; 9 | 10 | export interface IDBIRoleSelectMenuExecuteCtx extends IDBIBaseExecuteCtx { 11 | interaction: Discord.RoleSelectMenuInteraction<"cached">; 12 | data: TDBIReferencedData[]; 13 | } 14 | 15 | export type TDBIRoleSelectMenuOmitted = Omit, "type" | "description" | "dbi" | "toJSON" | "createBuilder" | "at">; 16 | 17 | export type SelectMenuDefaultOptions = Omit; 18 | 19 | export class DBIRoleSelectMenu extends DBIBaseInteraction { 20 | constructor(dbi: DBI, args: TDBIRoleSelectMenuOmitted) { 21 | super(dbi, { 22 | ...(args as any), 23 | type: "RoleSelectMenu", 24 | }); 25 | } 26 | 27 | declare options?: SelectMenuDefaultOptions; 28 | 29 | override onExecute(ctx: IDBIRoleSelectMenuExecuteCtx) { }; 30 | 31 | override toJSON(arg: IDBIToJSONArgs = {}): Discord.RoleSelectMenuComponentData { 32 | return { 33 | ...stuffs.defaultify((arg?.overrides || {}), this.options || {}, true), 34 | customId: buildCustomId(this.dbi as any, this.name, arg?.reference?.data || [], arg?.reference?.ttl), 35 | type: Discord.ComponentType.RoleSelect, 36 | } as any; 37 | }; 38 | 39 | createBuilder(arg: IDBIToJSONArgs = {}): DBIRoleSelectMenuBuilder { 40 | return new DBIRoleSelectMenuBuilder({ component: this, ...arg }) 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /src/types/Components/StringSelectMenu.ts: -------------------------------------------------------------------------------- 1 | import Discord from "discord.js"; 2 | import { DBI } from "../../DBI"; 3 | import { DBIBaseInteraction, IDBIBaseExecuteCtx, TDBIReferencedData } from "../Interaction"; 4 | import { buildCustomId } from "../../utils/customId"; 5 | import { IDBIToJSONArgs } from "../../utils/UtilTypes"; 6 | import { NamespaceEnums } from "../../../generated/namespaceData"; 7 | import stuffs from "stuffs"; 8 | import { DBIStringSelectMenuBuilder, DBIStringSelectMenuOverrides } from "../Builders/StringSelectMenuBuilder"; 9 | 10 | export interface IDBIStringSelectMenuExecuteCtx extends IDBIBaseExecuteCtx { 11 | interaction: Discord.StringSelectMenuInteraction<"cached">; 12 | data: TDBIReferencedData[]; 13 | } 14 | 15 | export type TDBIStringSelectMenuOmitted = Omit, "type" | "description" | "dbi" | "toJSON" | "createBuilder" | "at">; 16 | 17 | export type SelectMenuDefaultOptions = Required> & Omit; 18 | 19 | export class DBIStringSelectMenu extends DBIBaseInteraction { 20 | constructor(dbi: DBI, args: TDBIStringSelectMenuOmitted) { 21 | super(dbi, { 22 | ...(args as any), 23 | type: "StringSelectMenu", 24 | }); 25 | } 26 | 27 | declare options?: SelectMenuDefaultOptions; 28 | 29 | override onExecute(ctx: IDBIStringSelectMenuExecuteCtx) { }; 30 | 31 | override toJSON(arg: IDBIToJSONArgs = {}): Discord.StringSelectMenuComponentData { 32 | return { 33 | ...stuffs.defaultify((arg?.overrides || {}), this.options || {}, true), 34 | customId: buildCustomId(this.dbi as any, this.name, arg?.reference?.data || [], arg?.reference?.ttl), 35 | type: Discord.ComponentType.StringSelect, 36 | } as any; 37 | }; 38 | 39 | createBuilder(arg: IDBIToJSONArgs = {}): DBIStringSelectMenuBuilder { 40 | return new DBIStringSelectMenuBuilder({ component: this, ...arg }) 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /src/types/Components/UserSelectMenu.ts: -------------------------------------------------------------------------------- 1 | import Discord from "discord.js"; 2 | import { DBI } from "../../DBI"; 3 | import { DBIBaseInteraction, IDBIBaseExecuteCtx, TDBIReferencedData } from "../Interaction"; 4 | import { buildCustomId } from "../../utils/customId"; 5 | import { IDBIToJSONArgs } from "../../utils/UtilTypes"; 6 | import { NamespaceEnums } from "../../../generated/namespaceData"; 7 | import stuffs from "stuffs"; 8 | import { DBIUserSelectMenuBuilder, DBIUserSelectMenuOverrides } from "../Builders/UserSelectMenuBuilder"; 9 | 10 | export interface IDBIUserSelectMenuExecuteCtx extends IDBIBaseExecuteCtx { 11 | interaction: Discord.UserSelectMenuInteraction<"cached">; 12 | data: TDBIReferencedData[]; 13 | } 14 | 15 | export type TDBIUserSelectMenuOmitted = Omit, "type" | "description" | "dbi" | "toJSON" | "createBuilder" | "at">; 16 | 17 | export type SelectMenuDefaultOptions = Omit; 18 | 19 | export class DBIUserSelectMenu extends DBIBaseInteraction { 20 | constructor(dbi: DBI, args: TDBIUserSelectMenuOmitted) { 21 | super(dbi, { 22 | ...(args as any), 23 | type: "UserSelectMenu", 24 | }); 25 | } 26 | 27 | declare options?: SelectMenuDefaultOptions; 28 | 29 | override onExecute(ctx: IDBIUserSelectMenuExecuteCtx) { }; 30 | 31 | override toJSON(arg: IDBIToJSONArgs = {}): Discord.UserSelectMenuComponentData { 32 | return { 33 | ...stuffs.defaultify((arg?.overrides || {}), this.options || {}, true), 34 | customId: buildCustomId(this.dbi as any, this.name, arg?.reference?.data || [], arg?.reference?.ttl), 35 | type: Discord.ComponentType.UserSelect, 36 | } as any; 37 | }; 38 | 39 | createBuilder(arg: IDBIToJSONArgs = {}): DBIUserSelectMenuBuilder { 40 | return new DBIUserSelectMenuBuilder({ component: this, ...arg }) 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /src/types/Event.ts: -------------------------------------------------------------------------------- 1 | import Discord from "discord.js"; 2 | import { NamespaceEnums, NamespaceData } from "../../generated/namespaceData"; 3 | import { DBI, TDBIClientData } from "../DBI"; 4 | import { DBILocale } from "./other/Locale"; 5 | export interface ClientEvents { 6 | applicationCommandPermissionsUpdate: { data: Discord.ApplicationCommandPermissionsUpdateData }; 7 | cacheSweep: { message: string }; 8 | channelCreate: { channel: Discord.NonThreadGuildBasedChannel }; 9 | channelDelete: { channel: Discord.DMChannel | Discord.NonThreadGuildBasedChannel }; 10 | channelPinsUpdate: { channel: Discord.TextBasedChannel, date: Date }; 11 | channelUpdate: { 12 | oldChannel: Discord.DMChannel | Discord.NonThreadGuildBasedChannel, 13 | newChannel: Discord.DMChannel | Discord.NonThreadGuildBasedChannel, 14 | }; 15 | debug: { message: string }; 16 | warn: { message: string }; 17 | emojiCreate: { emoji: Discord.GuildEmoji }; 18 | emojiDelete: { emoji: Discord.GuildEmoji }; 19 | emojiUpdate: { oldEmoji: Discord.GuildEmoji, newEmoji: Discord.GuildEmoji }; 20 | error: { error: Error }; 21 | guildBanAdd: { ban: Discord.GuildBan }; 22 | guildBanRemove: { ban: Discord.GuildBan }; 23 | guildCreate: { guild: Discord.Guild }; 24 | guildDelete: { guild: Discord.Guild }; 25 | guildUnavailable: { guild: Discord.Guild }; 26 | guildIntegrationsUpdate: { guild: Discord.Guild }; 27 | guildMemberAdd: { member: Discord.GuildMember }; 28 | guildMemberAvailable: { member: Discord.GuildMember | Discord.PartialGuildMember }; 29 | guildMemberRemove: { member: Discord.GuildMember | Discord.PartialGuildMember }; 30 | guildMembersChunk: { 31 | members: Discord.Collection, 32 | guild: Discord.Guild, 33 | data: { count: number; index: number; nonce: string | undefined }, 34 | }; 35 | guildMemberUpdate: { oldMember: Discord.GuildMember | Discord.PartialGuildMember, newMember: Discord.GuildMember }; 36 | guildUpdate: { oldGuild: Discord.Guild, newGuild: Discord.Guild }; 37 | inviteCreate: { invite: Discord.Invite }; 38 | inviteDelete: { invite: Discord.Invite }; 39 | messageCreate: { message: Discord.Message }; 40 | messageDelete: { message: Discord.Message | Discord.PartialMessage }; 41 | messageReactionRemoveAll: { 42 | message: Discord.Message | Discord.PartialMessage, 43 | reactions: Discord.Collection, 44 | }; 45 | messageReactionRemoveEmoji: { reaction: Discord.MessageReaction | Discord.PartialMessageReaction }; 46 | messageDeleteBulk: { messages: Discord.Collection, channel: Discord.TextBasedChannel }; 47 | messageReactionAdd: { reaction: Discord.MessageReaction | Discord.PartialMessageReaction, user: Discord.User | Discord.PartialUser }; 48 | messageReactionRemove: { reaction: Discord.MessageReaction | Discord.PartialMessageReaction, user: Discord.User | Discord.PartialUser }; 49 | messageUpdate: { oldMessage: Discord.Message | Discord.PartialMessage, newMessage: Discord.Message | Discord.PartialMessage }; 50 | presenceUpdate: { oldPresence: Discord.Presence | null, newPresence: Discord.Presence }; 51 | ready: { client: Discord.Client }; 52 | invalidated: {}; 53 | roleCreate: { role: Discord.Role }; 54 | roleDelete: { role: Discord.Role }; 55 | roleUpdate: { oldRole: Discord.Role, newRole: Discord.Role }; 56 | threadCreate: { thread: Discord.AnyThreadChannel, newlyCreated: boolean }; 57 | threadDelete: { thread: Discord.AnyThreadChannel }; 58 | threadListSync: { threads: Discord.Collection, guild: Discord.Guild }; 59 | threadMemberUpdate: { oldMember: Discord.ThreadMember, newMember: Discord.ThreadMember }; 60 | threadMembersUpdate: { 61 | addedMembers: Discord.Collection, 62 | removedMembers: Discord.Collection, 63 | thread: Discord.AnyThreadChannel, 64 | }; 65 | threadUpdate: { oldThread: Discord.AnyThreadChannel, newThread: Discord.AnyThreadChannel }; 66 | typingStart: { typing: Discord.Typing }; 67 | userUpdate: { oldUser: Discord.User | Discord.PartialUser, newUser: Discord.User }; 68 | voiceStateUpdate: { oldState: Discord.VoiceState, newState: Discord.VoiceState }; 69 | webhookUpdate: { channel: Discord.TextChannel | Discord.NewsChannel | Discord.VoiceChannel }; 70 | interactionCreate: { interaction: Discord.Interaction }; 71 | shardDisconnect: { closeEvent: Discord.CloseEvent, shardId: number }; 72 | shardError: { error: Error, shardId: number }; 73 | shardReady: { shardId: number, unavailableGuilds: Set | undefined }; 74 | shardReconnecting: { shardId: number }; 75 | shardResume: { shardId: number, replayedEvents: number }; 76 | stageInstanceCreate: { stageInstance: Discord.StageInstance }; 77 | stageInstanceUpdate: { oldStageInstance: Discord.StageInstance | null, newStageInstance: Discord.StageInstance }; 78 | stageInstanceDelete: { stageInstance: Discord.StageInstance }; 79 | stickerCreate: { sticker: Discord.Sticker }; 80 | stickerDelete: { sticker: Discord.Sticker }; 81 | stickerUpdate: { oldSticker: Discord.Sticker, newSticker: Discord.Sticker }; 82 | guildScheduledEventCreate: { guildScheduledEvent: Discord.GuildScheduledEvent }; 83 | guildScheduledEventUpdate: { 84 | oldGuildScheduledEvent: Discord.GuildScheduledEvent | null, 85 | newGuildScheduledEvent: Discord.GuildScheduledEvent, 86 | }; 87 | guildScheduledEventDelete: { guildScheduledEvent: Discord.GuildScheduledEvent }; 88 | guildScheduledEventUserAdd: { guildScheduledEvent: Discord.GuildScheduledEvent, user: Discord.User }; 89 | guildScheduledEventUserRemove: { guildScheduledEvent: Discord.GuildScheduledEvent, user: Discord.User }; 90 | autoModerationRuleCreate: { autoModerationRule: Discord.AutoModerationRule; }; 91 | autoModerationRuleDelete: { autoModerationRule: Discord.AutoModerationRule; }; 92 | autoModerationRuleUpdate: { oldAutoModerationRule: Discord.AutoModerationRule | null; newAutoModerationRule: Discord.AutoModerationRule; }; 93 | guildAuditLogEntryCreate: { auditLogEntry: Discord.GuildAuditLogsEntry, guild: Discord.Guild }; 94 | } 95 | 96 | export type DBIEventCombinations = { 97 | [K in keyof (ClientEvents & NamespaceData[TNamespace]["customEvents"])]: { 98 | name: K, 99 | onExecute: (ctx: (ClientEvents & NamespaceData[TNamespace]["customEvents"])[K] & { other: Record, locale?: { guild: DBILocale }, eventName: string, nextClient: TDBIClientData }) => Promise | any 100 | } 101 | }[keyof (ClientEvents) | keyof NamespaceData[TNamespace]["customEvents"]]; 102 | 103 | export type TDBIEventOmitted = Omit, "type" | "name" | "onExecute" | "client" | "dbi" | "toggle" | "disabled" | "at"> & { disabled?: boolean } & DBIEventCombinations; 104 | 105 | export type TDBIEventOrder = { 106 | await?: boolean; 107 | delayBefore?: number; 108 | delayAfter?: number; 109 | } 110 | 111 | export class DBIEvent { 112 | readonly type: "Event"; 113 | other?: Record; 114 | triggerType?: "OneByOne" | "OneByOneGlobal" | "Random" | "First"; 115 | id?: string; 116 | name: string; 117 | onExecute: (...args: any[]) => any; 118 | ordered?: TDBIEventOrder; 119 | dbi: DBI; 120 | disabled: boolean = false; 121 | flag?: string; 122 | at?: number; 123 | ttl?: number; 124 | constructor(dbi: DBI, cfg: TDBIEventOmitted) { 125 | this.dbi = dbi; 126 | this.type = "Event"; 127 | this.id = cfg.id; 128 | this.other = cfg.other; 129 | this.name = cfg.name as any; 130 | this.onExecute = cfg.onExecute; 131 | this.ordered = cfg.ordered; 132 | this.triggerType = cfg.triggerType ?? "OneByOneGlobal"; 133 | this.disabled ??= cfg.disabled; 134 | this.flag = cfg.flag; 135 | this.at = Date.now(); 136 | this.ttl = cfg.ttl; 137 | } 138 | 139 | toggle(disabled?: boolean) { 140 | if (disabled === undefined) this.disabled = !this.disabled; 141 | else this.disabled = disabled; 142 | return this; 143 | } 144 | 145 | } -------------------------------------------------------------------------------- /src/types/Interaction.ts: -------------------------------------------------------------------------------- 1 | import Discord from "discord.js"; 2 | import { NamespaceEnums, NamespaceData } from "../../generated/namespaceData"; 3 | import { DBI } from "../DBI"; 4 | import { DBIButton } from "./Components/Button"; 5 | import { DBIChatInput } from "./ChatInput/ChatInput"; 6 | import { DBILocale } from "./other/Locale"; 7 | import { DBIMessageContextMenu } from "./other/MessageContextMenu"; 8 | import { DBIModal } from "./Components/Modal"; 9 | import { DBIStringSelectMenu } from "./Components/StringSelectMenu"; 10 | import { DBIUserContextMenu } from "./other/UserContextMenu"; 11 | 12 | export type TDBIInteractions = DBIChatInput | DBIButton | DBIStringSelectMenu | DBIMessageContextMenu | DBIUserContextMenu | DBIModal; 13 | 14 | export interface IDBIBaseExecuteCtx { 15 | interaction: 16 | | Discord.ChatInputCommandInteraction 17 | | Discord.UserContextMenuCommandInteraction 18 | | Discord.MessageContextMenuCommandInteraction 19 | | Discord.ModalSubmitInteraction 20 | | Discord.AutocompleteInteraction 21 | | Discord.AnySelectMenuInteraction 22 | | Discord.ButtonInteraction; 23 | locale: { 24 | user: DBILocale, 25 | guild?: DBILocale 26 | } 27 | dbi: DBI; 28 | dbiInteraction: TDBIInteractions; 29 | setRateLimit(type: TDBIRateLimitTypes, duration: number): Promise; 30 | other: Record; 31 | clientNamespace: NamespaceData[TNamespace]["clientNamespaces"]; 32 | } 33 | 34 | export type TDBIReferencedData = ({ [key: string]: any, $ref: string, $unRef(): boolean } | string | number); 35 | 36 | export type TDBIInteractionTypes = 37 | | "ChatInput" 38 | | "UserContextMenu" 39 | | "MessageContextMenu" 40 | | "Modal" 41 | | "Autocomplete" 42 | | "StringSelectMenu" 43 | | "UserSelectMenu" 44 | | "ChannelSelectMenu" 45 | | "MentionableSelectMenu" 46 | | "RoleSelectMenu" 47 | | "Button"; 48 | 49 | export type TDBIRateLimitTypes = 50 | | "User" 51 | | "Channel" 52 | | "Guild" 53 | | "Member" 54 | | "Message"; 55 | 56 | 57 | export type DBIRateLimit = { 58 | type: TDBIRateLimitTypes; 59 | /** 60 | * Duration in milliseconds. 61 | */ 62 | duration: number; 63 | } 64 | 65 | export class DBIBaseInteraction { 66 | constructor(dbi: DBI, cfg: Omit, "dbi" | "at">) { 67 | this.dbi = dbi; 68 | this.name = cfg.name; 69 | this.description = cfg.description; 70 | this.onExecute = cfg.onExecute; 71 | this.type = cfg.type; 72 | this.options = cfg.options; 73 | this.other = cfg.other; 74 | this.publish = cfg.publish ?? dbi.data.clients.first()?.namespace; 75 | this.rateLimits = cfg.rateLimits ?? []; 76 | this.flag = cfg.flag; 77 | this.at = Date.now(); 78 | this.ttl = cfg.ttl; 79 | } 80 | 81 | publish?: NamespaceData[TNamespace]["clientNamespaces"]; 82 | dbi: DBI; 83 | name: string; 84 | description: string; 85 | readonly type: TDBIInteractionTypes; 86 | options?: any | any[]; 87 | other?: Record & { messageCommand?: { aliases?: string[], ignore?: boolean } }; 88 | rateLimits?: DBIRateLimit[]; 89 | flag?: string; 90 | ttl?: number; 91 | at?: number; 92 | toJSON(overrides: any): any { } 93 | 94 | onExecute(ctx: IDBIBaseExecuteCtx): Promise | void { 95 | 96 | } 97 | } -------------------------------------------------------------------------------- /src/types/other/CustomEvent.ts: -------------------------------------------------------------------------------- 1 | import { NamespaceEnums, NamespaceData } from "../../../generated/namespaceData"; 2 | import { DBI } from "../../DBI"; 3 | 4 | export type TDBICustomEventOmitted = Omit, "type" | "dbi" | "toJSON" | "trigger">; 5 | export class DBICustomEvent { 6 | dbi: DBI; 7 | name: CEventName; 8 | map: {[key: string]: string}; 9 | type: string; 10 | trigger(args: NamespaceData[TNamespace]["customEvents"][CEventName]) { 11 | return this.dbi.data.clients.first().client.emit(this.name as string, { ...args, _DIRECT_: true}); 12 | } 13 | constructor(dbi: DBI, cfg: TDBICustomEventOmitted) { 14 | this.dbi = dbi; 15 | this.name = cfg.name; 16 | this.map = cfg.map; 17 | this.type = "CustomEvent"; 18 | } 19 | } -------------------------------------------------------------------------------- /src/types/other/FakeMessageInteraction.ts: -------------------------------------------------------------------------------- 1 | import { Message, MessagePayload, ApplicationCommandType, ChatInputCommandInteraction, Locale, APIInteractionGuildMember, GuildMember, PermissionsBitField, CacheType, CommandInteractionOptionResolver, CommandOptionDataTypeResolvable, ApplicationCommandOptionType, User, Attachment, InteractionEditReplyOptions, InteractionReplyOptions } from 'discord.js'; 2 | import { TDBIInteractions } from '../Interaction'; 3 | import { plsParseArgs } from "plsargs"; 4 | import { DBI } from '../../DBI'; 5 | import { NamespaceEnums } from "../../../generated/namespaceData"; 6 | import { ChannelType } from "discord-api-types/v10"; 7 | 8 | export class FakeMessageInteraction /* implements ChatInputCommandInteraction */ { 9 | channelId: string; 10 | commandName: string; 11 | appPermissions: any; 12 | applicationId: string; 13 | channel: any; 14 | command: any; 15 | commandGuildId: string; 16 | commandId: any; 17 | commandType: ApplicationCommandType.ChatInput; 18 | // awaitModalSubmit: (...arr: any[]) => any; 19 | // fetchReply: () => Promise; 20 | deferred: boolean = false; 21 | client: any; 22 | createdAt: Date; 23 | ephemeral: boolean = false; 24 | createdTimestamp: number; 25 | guild: any; 26 | guildId: string; 27 | guildLocale: Locale; 28 | id: string; 29 | user: User; 30 | private repliedMessage: Message | undefined; 31 | private lastFollowUp: Message | undefined; 32 | member: GuildMember | APIInteractionGuildMember; 33 | memberPermissions: Readonly; 34 | parsedArgs = new Map(); 35 | args: import("plsargs/src/Result").Result; 36 | usedCommandName: string; 37 | fullCommandName: string; 38 | options: any; 39 | dbiChatInput: TDBIInteractions; 40 | dbiChatInputOptions: any[]; 41 | fake: boolean = true; 42 | _hoistedOptions: any; 43 | _initialized: boolean = false; 44 | _lastAction: string | undefined; 45 | 46 | constructor(public dbi: DBI, public message: Message, chatInput: TDBIInteractions, public locale: string, commandName: string, public usedPrefix: string) { 47 | const self = this; 48 | 49 | this.channelId = message.channel.id; 50 | this.commandName = chatInput.name.split(" ").at(0); 51 | this.appPermissions = message.guild?.members.me.permissionsIn(message.channel as any) ?? new PermissionsBitField(8n); 52 | this.applicationId = message.client.user.id; 53 | this.channel = message.channel as any; 54 | this.commandGuildId = message.guild.id; 55 | this.commandType = ApplicationCommandType.ChatInput; 56 | 57 | this.client = message.client; 58 | this.createdAt = message.createdAt; 59 | this.createdTimestamp = message.createdTimestamp; 60 | this.guild = message.guild; 61 | this.guildId = message.guild?.id; 62 | this.guildLocale = message.guild?.preferredLocale; 63 | this.id = message.guild?.commands.cache.find((cmd) => cmd.name === this.commandName)?.id ?? message.client.application.commands.cache.find((cmd) => cmd.name === this.commandName)?.id ?? "-1"; 64 | this.locale = message.guild?.preferredLocale; 65 | this.member = message.member; 66 | this.memberPermissions = message.member?.permissions; 67 | this.user = message.author; 68 | 69 | this.usedCommandName = commandName; 70 | this.fullCommandName = chatInput.name; 71 | this.dbiChatInput = chatInput; 72 | this.dbiChatInputOptions = chatInput.options ? chatInput.options.map(i => ({ ...i })) : []; 73 | 74 | { 75 | const argContent = message.content.slice(usedPrefix.length + commandName.length).replace(/ +/, " ").trim(); 76 | const args = plsParseArgs(argContent); 77 | 78 | this.args = args; 79 | 80 | const options = chatInput.options ?? []; 81 | const atchs = [...message.attachments.values()]; 82 | for (let i = 0, attachmentIndex = 0, namedValueSize = 0; i < options.length; i++) { 83 | const option = options[i]; 84 | if (!option) break; 85 | if (option.type === ApplicationCommandOptionType.Attachment) { 86 | this.parsedArgs.set(option.name, { 87 | name: option.name, 88 | type: option.type, 89 | value: atchs.at(attachmentIndex)?.url, 90 | attachment: atchs.at(attachmentIndex++) 91 | }) 92 | continue; 93 | } 94 | const value = option.messageCommands?.rest ? args._.slice(i).join(" ") : (args.get(option.name) ?? args.get(i - attachmentIndex - namedValueSize)); 95 | if (args.has(option.name)) namedValueSize++; 96 | 97 | const interactionChoicesLocale = this.dbi.data.interactionLocales.get(self.dbiChatInput.name)?.data?.[locale]?.options[option.name]?.choices; 98 | 99 | const localizedChoices = option.choices?.length ? option.choices.map(choice => ({ 100 | ...choice, 101 | name: interactionChoicesLocale?.[choice.value] ?? choice.name 102 | })) : option._choices; 103 | 104 | this.parsedArgs.set(option.name, { 105 | name: option.name, 106 | type: option.type, 107 | value: 108 | localizedChoices?.find(c => c.value === value || c.name === value)?.value ?? 109 | option.choices?.find(c => c.value === value || c.name === value)?.value ?? 110 | value 111 | }); 112 | } 113 | } 114 | 115 | this.options = { 116 | get(name: string) { 117 | const rawValue = self.getRawOptionValue(name); 118 | if (!rawValue) return null; 119 | return { 120 | value: rawValue, 121 | get boolean() { return self.options.getBoolean(name); }, 122 | get channel() { return self.options.getChannel(name); }, 123 | get string() { return self.options.getString(name); }, 124 | get integer() { return self.options.getInteger(name); }, 125 | get number() { return self.options.getNumber(name); }, 126 | get user() { return self.options.getUser(name); }, 127 | get member() { return self.options.getMember(name); }, 128 | get role() { return self.options.getRole(name); }, 129 | get mentionable() { return self.options.getMentionable(name); }, 130 | get attachment() { return self.options.getAttachment(name); } 131 | }; 132 | }, 133 | get _hoistedOptions() { 134 | return self._hoistedOptions; 135 | }, 136 | getSubcommand() { 137 | let splitted = self.fullCommandName.split(" "); 138 | if (splitted.length === 1) return null; 139 | return splitted.at(-1); 140 | }, 141 | getSubcommandGroup() { 142 | let splitted = self.fullCommandName.split(" "); 143 | if (splitted.length === 3) return splitted[1]; 144 | return null; 145 | }, 146 | getBoolean(name: string) { 147 | const rawValue = self.getRawOptionValue(name); 148 | if (!rawValue) return null; 149 | return !!self.dbi.config.messageCommands.typeAliases.booleans[rawValue.toLowerCase()]; 150 | }, 151 | getChannel(name: string, _: any, channelType?: ChannelType | ChannelType[]) { 152 | const rawValue = self.getRawOptionValue(name); 153 | if (!rawValue) return null; 154 | let value = rawValue.replace(/<#|>/g, ""); 155 | let channel = self.message.client.channels.cache.get(value); 156 | if (!channel) channel = self.message.client.channels.cache.find(c => { 157 | if (self.guildId && (c as any).guildId && (c as any).guildId !== self.guildId) return false; 158 | return (c as any).name === value; 159 | }); 160 | // @ts-ignore 161 | if (channelType && channel?.type !== channelType && !channelType?.includes?.(channel?.type)) return null; 162 | return channel; 163 | }, 164 | getString(name: string) { 165 | const dbiOption = self.getClonedDBIOption(name); 166 | let rawValue = `${self.getRawOptionValue(name)}`; 167 | let choices = dbiOption.choices ?? dbiOption._choices; 168 | if (choices) return choices.find(c => c.value === rawValue || c.name === rawValue)?.value ?? rawValue; 169 | return rawValue; 170 | }, 171 | getInteger(name: string) { 172 | const dbiOption = self.getClonedDBIOption(name); 173 | let rawValue = self.getRawOptionValue(name); 174 | let parsedValue = parseInt(rawValue); 175 | let choices = dbiOption.choices ?? dbiOption._choices; 176 | if (choices) return choices.find(c => c.value === parsedValue || c.name === rawValue)?.value ?? rawValue; 177 | return rawValue; 178 | }, 179 | getNumber(name: string) { 180 | const dbiOption = self.getClonedDBIOption(name); 181 | let rawValue = self.getRawOptionValue(name); 182 | let parsedValue = parseFloat(rawValue); 183 | let choices = dbiOption.choices ?? dbiOption._choices; 184 | if (choices) return choices.find(c => c.value === parsedValue || c.name === rawValue)?.value ?? rawValue; 185 | return rawValue; 186 | }, 187 | getUser(name: string) { 188 | const rawValue = self.getRawOptionValue(name); 189 | if (!rawValue) return null; 190 | let value = rawValue.replace(/<@!?|>/g, ""); 191 | let user = self.message.client.users.cache.get(value); 192 | if (!user) user = self.message.client.users.cache.find(u => u.username === value || u.tag === value); 193 | return user; 194 | }, 195 | getUserId(name: string) { 196 | const rawValue = self.getRawOptionValue(name); 197 | if (!rawValue) return null; 198 | return rawValue.replace(/<@!?|>/g, ""); 199 | }, 200 | getMember(name: string) { 201 | const rawValue = self.getRawOptionValue(name); 202 | if (!rawValue) return null; 203 | let value = rawValue.replace(/<@!?|>/g, ""); 204 | let member = self.message.guild?.members.cache.get(value); 205 | if (!member) member = self.message.guild?.members.cache.find(m => m.user.username === value || m.user.tag === value); 206 | return member; 207 | }, 208 | getMemberId(name: string) { 209 | const rawValue = self.getRawOptionValue(name); 210 | if (!rawValue) return null; 211 | return rawValue.replace(/<@!?|>/g, ""); 212 | }, 213 | getRole(name: string) { 214 | const rawValue = self.getRawOptionValue(name); 215 | if (!rawValue) return null; 216 | let value = rawValue.replace(/<@&|>/g, ""); 217 | let role = self.message.guild?.roles.cache.get(value); 218 | if (!role) role = self.message.guild?.roles.cache.find(r => r.name === value); 219 | return role; 220 | }, 221 | getRoleId(name: string) { 222 | const rawValue = self.getRawOptionValue(name); 223 | if (!rawValue) return null; 224 | return rawValue.replace(/<@&|>/g, ""); 225 | }, 226 | getMentionable(name: string) { 227 | const rawValue = self.getRawOptionValue(name); 228 | if (!rawValue) return null; 229 | let value = rawValue.replace(/<(@|#)(!|&)?|>/g, ""); 230 | let user = self.message.client.users.cache.get(value); 231 | if (!user) user = self.message.client.users.cache.find(u => u.username === value || u.tag === value); 232 | if (user) return user; 233 | let member = self.message.guild?.members.cache.get(value); 234 | if (!member) member = self.message.guild?.members.cache.find(m => m.user.username === value || m.user.tag === value); 235 | if (member) return member; 236 | let role = self.message.guild?.roles.cache.get(value); 237 | if (!role) role = self.message.guild?.roles.cache.find(r => r.name === value); 238 | if (role) return role; 239 | return null; 240 | }, 241 | getMentionableId(name: string) { 242 | const rawValue = self.getRawOptionValue(name); 243 | if (!rawValue) return null; 244 | return rawValue.replace(/<@(!|&)?|>/g, ""); 245 | }, 246 | getMessage() { 247 | return self.message; 248 | }, 249 | getAttachment(name: string) { 250 | let d = self.parsedArgs.get(name); 251 | return d?.attachment ?? null; 252 | }, 253 | getChannelId(name: string) { 254 | const rawValue = self.getRawOptionValue(name); 255 | if (!rawValue) return null; 256 | let value = rawValue.replace(/<#|>/g, ""); 257 | return value; 258 | } 259 | } 260 | } 261 | 262 | init() { 263 | if (this._initialized) return; 264 | this._initialized = true; 265 | this._hoistedOptionsInit(); 266 | } 267 | 268 | _hoistedOptionsInit() { 269 | this._hoistedOptions = [...this.parsedArgs.values()].map(arg => { 270 | switch (arg.type) { 271 | case ApplicationCommandOptionType.String: { 272 | return { ...this.options.get(arg.name), value: this.options.getString(arg.name) }; 273 | } 274 | case ApplicationCommandOptionType.Integer: { 275 | return { ...this.options.get(arg.name), value: this.options.getInteger(arg.name) }; 276 | } 277 | case ApplicationCommandOptionType.Number: { 278 | return { ...this.options.get(arg.name), value: this.options.getNumber(arg.name) }; 279 | } 280 | case ApplicationCommandOptionType.Boolean: { 281 | return { ...this.options.get(arg.name), value: this.options.getBoolean(arg.name) }; 282 | } 283 | case ApplicationCommandOptionType.User: { 284 | return { ...this.options.get(arg.name), value: this.options.getUser(arg.name)?.id }; 285 | } 286 | case ApplicationCommandOptionType.Channel: { 287 | return { ...this.options.get(arg.name), value: this.options.getChannel(arg.name)?.id }; 288 | } 289 | case ApplicationCommandOptionType.Role: { 290 | return { ...this.options.get(arg.name), value: this.options.getRole(arg.name)?.id }; 291 | } 292 | case ApplicationCommandOptionType.Mentionable: { 293 | return { ...this.options.get(arg.name), value: this.options.getMentionable(arg.name)?.id }; 294 | } 295 | case ApplicationCommandOptionType.Attachment: { 296 | return { ...this.options.get(arg.name), value: this.options.getAttachment(arg.name)?.url }; 297 | } 298 | case ApplicationCommandOptionType.Subcommand: { 299 | return { ...this.options.get(arg.name), value: this.options.getSubcommand() }; 300 | } 301 | case ApplicationCommandOptionType.SubcommandGroup: { 302 | return { ...this.options.get(arg.name), value: this.options.getSubcommandGroup() }; 303 | } 304 | } 305 | }); 306 | } 307 | 308 | private getRawOptionValue(name: string): any { 309 | return this.parsedArgs.get(name)?.value; 310 | } 311 | 312 | private getClonedDBIOption(name: string): any { 313 | return this.dbiChatInputOptions.find(o => o.name === name); 314 | } 315 | 316 | inGuild() { 317 | return true; 318 | } 319 | 320 | async deferReply(options: any): Promise { 321 | if (this.repliedMessage) throw new Error("Already deferred reply."); 322 | this.repliedMessage = await this.message.reply(options?.content ?? (await this.dbi.config.defaults.messageCommands.deferReplyContent({ 323 | // @ts-ignore 324 | dbiInteraction: this.dbiChatInput, 325 | interaction: this, 326 | locale: { 327 | user: this.dbi.data.locales.get(this.locale) || 328 | this.dbi.data.locales.get(this.dbi.config.defaults.locale.name), 329 | guild: this.message.guild?.preferredLocale 330 | ? this.dbi.data.locales.get( 331 | this.message.guild?.preferredLocale?.split("-")?.at(0) 332 | ) || this.dbi.data.locales.get(this.dbi.config.defaults.locale.name) 333 | : null, 334 | } 335 | }))); 336 | this.deferred = true; 337 | this._lastAction = "deferReply"; 338 | return this.repliedMessage; 339 | } 340 | 341 | async deleteReply() { 342 | if (!this.repliedMessage) throw new Error("No deferred reply."); 343 | await this.repliedMessage.delete(); 344 | this.repliedMessage = undefined; 345 | this._lastAction = "deleteReply"; 346 | } 347 | 348 | async followUp(content: string | MessagePayload) { 349 | if (!this.repliedMessage) throw new Error("No deferred reply."); 350 | if (!this.lastFollowUp) { 351 | this.lastFollowUp = await this.repliedMessage.reply(content); 352 | } else { 353 | this.lastFollowUp = await this.lastFollowUp.reply(content); 354 | } 355 | this._lastAction = "followUp"; 356 | return this.lastFollowUp; 357 | } 358 | 359 | async editReply(content: string | MessagePayload | InteractionEditReplyOptions) { 360 | if (!this.repliedMessage) throw new Error("No deferred reply."); 361 | if (typeof content !== "string" && this._lastAction === "deferReply" && typeof (content as any).content === "undefined") { 362 | (content as any).content = null; 363 | } 364 | await this.repliedMessage.edit(content); 365 | this._lastAction = "editReply"; 366 | return this.repliedMessage; 367 | } 368 | 369 | async reply(content: string | MessagePayload | InteractionReplyOptions): Promise { 370 | if (this.repliedMessage) throw new Error("Already deferred reply."); 371 | this.repliedMessage = await this.message.reply(content as any); 372 | this._lastAction = "reply"; 373 | return this.repliedMessage; 374 | } 375 | 376 | async awaitModalSubmit() { 377 | throw new Error("Method not implemented."); 378 | }; 379 | 380 | async fetchReply() { 381 | return this.repliedMessage?.id && await this.message.channel.messages.fetch(this.repliedMessage.id); 382 | }; 383 | 384 | isAnySelectMenu() { return false; } 385 | isAutocomplete() { return false; } 386 | isButton() { return false; } 387 | isChannelSelectMenu() { return false; } 388 | isChatInputCommand() { return true; } 389 | isCommand() { return true; } 390 | isContextMenuCommand() { return false; } 391 | isMentionableSelectMenu() { return false; } 392 | isMessageComponent() { return false; } 393 | isMessageContextMenuCommand() { return false; } 394 | isModalSubmit() { return false; } 395 | isRepliable() { return true; } 396 | isRoleSelectMenu() { return false; } 397 | isStringSelectMenu() { return false; } 398 | isUserContextMenuCommand() { return false; } 399 | isUserSelectMenu() { return false; } 400 | isSelectMenu() { return false; } 401 | } 402 | 403 | interface FakeMessageInteractionArgument { 404 | type: ApplicationCommandOptionType, 405 | value: any, 406 | attachment?: Attachment, 407 | name: string 408 | } -------------------------------------------------------------------------------- /src/types/other/InteractionLocale.ts: -------------------------------------------------------------------------------- 1 | import { NamespaceEnums } from "../../../generated/namespaceData"; 2 | import { DBI } from "../../DBI"; 3 | import { TDBILocaleString } from "./Locale"; 4 | 5 | export type TDBIInteractionLocaleData = { 6 | [K in TDBILocaleString]?: { 7 | name: string; 8 | description: string; 9 | options?: { 10 | [k: string]: { 11 | name: string; 12 | description: string; 13 | choices?: { 14 | [k: string]: string 15 | } 16 | } 17 | } 18 | }; 19 | }; 20 | 21 | export type TDBIInteractionLocaleOmitted = Omit; 22 | 23 | export class DBIInteractionLocale { 24 | name: string; 25 | data: TDBIInteractionLocaleData; 26 | dbi: DBI; 27 | flag?: string; 28 | constructor(dbi, cfg: TDBIInteractionLocaleOmitted) { 29 | this.dbi = dbi; 30 | this.name = cfg.name; 31 | this.data = cfg.data; 32 | this.flag = cfg.flag; 33 | } 34 | } -------------------------------------------------------------------------------- /src/types/other/Locale.ts: -------------------------------------------------------------------------------- 1 | import stuffs from "stuffs"; 2 | import { NamespaceData, NamespaceEnums } from "../../../generated/namespaceData"; 3 | import { DBI } from "../../DBI"; 4 | import _ from "lodash"; 5 | import util from "util"; 6 | 7 | export interface DBILangObject { 8 | [property: string]: DBILangObject & ((...args: any[]) => string); 9 | } 10 | 11 | export interface DBILangConstructorObject { 12 | [property: string]: DBILangConstructorObject | string; 13 | } 14 | 15 | export type TDBILocaleString = "en" | "bg" | "zh" | "hr" | "cs" | "da" | "nl" | "fi" | "fr" | "de" | "el" | "hi" | "hu" | "it" | "ja" | "ko" | "no" | "pl" | "pt" | "ro" | "ru" | "es" | "sv" | "th" | "tr" | "uk" | "vi"; 16 | 17 | export type TDBILocaleConstructor = Omit, "data" | "dbi" | "mergeLocale" | "_data" | "get" | "format"> & { data: DBILangConstructorObject }; 18 | 19 | export class DBILocale { 20 | name: TDBILocaleString; 21 | data: NamespaceData[TNamespace]["contentLocale"]; 22 | _data: DBILangConstructorObject; 23 | dbi: DBI; 24 | flag?: string 25 | constructor(dbi: DBI, cfg: TDBILocaleConstructor) { 26 | this.dbi = dbi; 27 | this.name = cfg.name; 28 | this.flag = cfg.flag; 29 | this._data = cfg.data; 30 | this.data = createInfinitePathProxy((path, ...args) => { 31 | return this.format(path.join("."), ...args); 32 | });; 33 | } 34 | mergeLocale(locale: DBILocale): DBILocale { 35 | this._data = stuffs.defaultify(locale._data, this._data, true) as any; 36 | locale._data = this._data; 37 | 38 | return this; 39 | } 40 | get(path: string): string | null { 41 | return _.get(this._data as any, path) as string || null; 42 | } 43 | format(path: string, ...args: any[]): string { 44 | let value = this.get(path); 45 | if (!value) { 46 | const defaultLocale = this.dbi.locale(this.dbi.config.defaults.locale.name as any); 47 | if (!defaultLocale || defaultLocale.name === this.name) return this.dbi.config.defaults.locale.invalidPath({ 48 | locale: this, 49 | path, 50 | }); 51 | value = defaultLocale.get(path); 52 | } 53 | if (!value) return this.dbi.config.defaults.locale.invalidPath({ 54 | locale: this, 55 | path, 56 | }); 57 | return stuffs.mapReplace(value, args.map((t, i) => [new RegExp(`\\{${i}(;[^}]+)?\\}`, "g"), t])); 58 | } 59 | } 60 | 61 | export function createInfinitePathProxy(onApplyPath: (path: string[], ...args: any[]) => string, path: string[] = []): any { 62 | return new Proxy(() => { }, { 63 | get(target, key) { 64 | return createInfinitePathProxy(onApplyPath, [...path, key.toString()]); 65 | }, 66 | apply(target, thisArg, args) { 67 | return onApplyPath(path, ...args); 68 | } 69 | }); 70 | } -------------------------------------------------------------------------------- /src/types/other/MessageContextMenu.ts: -------------------------------------------------------------------------------- 1 | import { DBI } from "../../DBI"; 2 | import { DBIBaseInteraction, IDBIBaseExecuteCtx } from "../Interaction"; 3 | import Discord from "discord.js"; 4 | import { NamespaceEnums } from "../../../generated/namespaceData"; 5 | 6 | export type TDBIMessageContextMenuOmitted = Omit, "type" | "description" | "dbi" | "options" | "toJSON">; 7 | 8 | export interface IDBIMessageContextMenuExecuteCtx extends IDBIBaseExecuteCtx { 9 | interaction: Discord.MessageContextMenuCommandInteraction<"cached">; 10 | } 11 | 12 | 13 | export class DBIMessageContextMenu extends DBIBaseInteraction { 14 | constructor(dbi: DBI, cfg: TDBIMessageContextMenuOmitted) { 15 | super(dbi, { 16 | ...(cfg as any), 17 | type: "MessageContextMenu" 18 | }); 19 | 20 | this.directMessages = cfg.directMessages ?? dbi.config.defaults.directMessages; 21 | this.defaultMemberPermissions = cfg.defaultMemberPermissions ?? dbi.config.defaults.defaultMemberPermissions; 22 | } 23 | 24 | directMessages?: boolean; 25 | defaultMemberPermissions?: Discord.PermissionsString[]; 26 | override onExecute(ctx: IDBIMessageContextMenuExecuteCtx): Promise | void {} 27 | } -------------------------------------------------------------------------------- /src/types/other/UserContextMenu.ts: -------------------------------------------------------------------------------- 1 | import { DBI } from "../../DBI"; 2 | import { DBIBaseInteraction, IDBIBaseExecuteCtx } from "../Interaction"; 3 | import Discord from "discord.js"; 4 | import { NamespaceEnums } from "../../../generated/namespaceData"; 5 | 6 | export type TDBIUserContextMenuOmitted = Omit, "type" | "description" | "dbi" | "options" | "toJSON">; 7 | 8 | export interface IDBIUserContextMenuExecuteCtx extends IDBIBaseExecuteCtx { 9 | interaction: Discord.UserContextMenuCommandInteraction<"cached">; 10 | } 11 | 12 | export class DBIUserContextMenu extends DBIBaseInteraction { 13 | constructor(dbi: DBI, cfg: TDBIUserContextMenuOmitted) { 14 | super(dbi, { 15 | ...(cfg as any), 16 | type: "UserContextMenu" 17 | }); 18 | 19 | this.directMessages = cfg.directMessages ?? dbi.config.defaults.directMessages; 20 | this.defaultMemberPermissions = cfg.defaultMemberPermissions ?? dbi.config.defaults.defaultMemberPermissions; 21 | } 22 | directMessages?: boolean; 23 | defaultMemberPermissions?: Discord.PermissionsString[]; 24 | override onExecute(ctx: IDBIUserContextMenuExecuteCtx): Promise | void {} 25 | } -------------------------------------------------------------------------------- /src/utils/MemoryStore.ts: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | 3 | export class MemoryStore { 4 | store: Record; 5 | constructor() { 6 | this.store = {}; 7 | } 8 | async get(key: string, defaultValue?: any): Promise { 9 | let val = _.get(this.store, key); 10 | if (!val) { 11 | this.set(key, defaultValue); 12 | return defaultValue; 13 | } 14 | return val; 15 | } 16 | 17 | async set(key: string, value: any): Promise { 18 | return this.store = _.set(this.store, key, value); 19 | } 20 | 21 | async delete(key: string): Promise { 22 | return _.unset(this.store, key); 23 | } 24 | 25 | async has(key: string): Promise { 26 | return _.has(this.store, key); 27 | } 28 | } -------------------------------------------------------------------------------- /src/utils/UtilTypes.ts: -------------------------------------------------------------------------------- 1 | export interface IDBIToJSONArgs { 2 | reference?: { 3 | ttl?: number; 4 | data: (string | number | object | boolean | null | undefined)[]; 5 | }; 6 | overrides?: RecursivePartial; 7 | } 8 | 9 | export type RecursivePartial = { 10 | [P in keyof T]?: RecursivePartial; 11 | } -------------------------------------------------------------------------------- /src/utils/customId.ts: -------------------------------------------------------------------------------- 1 | import { DBI } from "../DBI"; 2 | import * as stuffs from "stuffs"; 3 | import { NamespaceEnums } from "../../generated/namespaceData"; 4 | 5 | export function buildCustomId(dbi: DBI, name: string, data: any[], ttl?: number): string { 6 | let customId = [ 7 | name, 8 | ...data.map(value => { 9 | if (typeof value == "string") return value; 10 | if (typeof value == "number") return `π${value}`; 11 | if (typeof value == "bigint") return `ᙖ${value.toString()}`; 12 | if (typeof value == "boolean") return `𝞫${value ? 1 : 0}`; 13 | if (typeof value == "undefined") return "🗶u"; 14 | if (value === null) return "🗶n"; 15 | let id = stuffs.randomString(8); 16 | Object.assign(value, { 17 | $ref: id, 18 | $unRef() { return dbi.data.refs.delete(id); }, 19 | }) 20 | dbi.data.refs.set(id, { at: Date.now(), value, ttl }); 21 | return `¤${id}`; 22 | }) 23 | ].join("—"); 24 | if (!dbi.config.strict) customId = customId.slice(0, 100); 25 | if (customId.length > 100) throw new Error("Custom id cannot be longer than 100 characters.") 26 | return customId; 27 | } 28 | 29 | export function parseCustomId(dbi: DBI, customId: string): { name: string, data: any[] } { 30 | let splitted = customId.split("—"); 31 | let name = splitted.shift(); 32 | let data = splitted.map(value => { 33 | if (value.startsWith("π")) return Number(value.slice(1)); 34 | if (value.startsWith("𝞫")) return !!Number(value.slice(1)); 35 | if (value.startsWith("ᙖ")) return BigInt(value.slice(1)); 36 | if (value.startsWith("¤")) return dbi.data.refs.get(value.slice(1))?.value; 37 | if (value == "🗶u") return undefined; 38 | if (value == "🗶n") return null; 39 | return value; 40 | }); 41 | return { 42 | name, 43 | data 44 | } 45 | } -------------------------------------------------------------------------------- /src/utils/permissions.ts: -------------------------------------------------------------------------------- 1 | import { PermissionFlagsBits, PermissionsString } from "discord.js"; 2 | 3 | export function reducePermissions(permStrings: PermissionsString[] = []): bigint { 4 | return permStrings.reduce((all, curr) => PermissionFlagsBits[curr] | all, 0n); 5 | } -------------------------------------------------------------------------------- /src/utils/recursiveImport.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | 4 | /** 5 | * @example 6 | * await recursiveImport("./src", [".js"], [".d.ts"]) 7 | */ 8 | export async function recursiveImport(folderPath: string, exts: string[] = [".js"], ignore: string[] = [".d.ts",".js.map",".d.ts.map"]): Promise { 9 | let files = await fs.promises.readdir(folderPath, { withFileTypes: true }); 10 | let dirName = __dirname; 11 | 12 | for (const file of files) { 13 | let filePath = path.resolve(folderPath, file.name); 14 | let relative = path.relative(dirName, filePath); 15 | if (!relative.includes(`${path.sep}-`)) { 16 | if (file.isDirectory()) { 17 | await recursiveImport(filePath, exts) 18 | } else if (exts.some(i => file.name.endsWith(i)) && !ignore.some(i => file.name.endsWith(i))) { 19 | await import(filePath); 20 | } 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/utils/recursiveUnload.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | import { unloadModule } from "./unloadModule"; 4 | 5 | /** 6 | * @example 7 | * await recursiveUnload("./src", [".js"], [".d.ts"]) 8 | * await dbi.unload() 9 | */ 10 | export async function recursiveUnload(folderPath: string, exts: string[] = [".js"], ignore: string[] = [".d.ts",".js.map",".d.ts.map"]): Promise { 11 | let files = await fs.promises.readdir(folderPath, { withFileTypes: true }); 12 | let dirName = __dirname; 13 | 14 | for (const file of files) { 15 | let filePath = path.resolve(folderPath, file.name); 16 | let relative = path.relative(dirName, filePath); 17 | if (!relative.includes(`${path.sep}-`)) { 18 | if (file.isDirectory()) { 19 | await recursiveUnload(filePath, exts) 20 | } else if (exts.some(i => file.name.endsWith(i)) && !ignore.some(i => file.name.endsWith(i))) { 21 | unloadModule(filePath) 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/utils/unloadModule.ts: -------------------------------------------------------------------------------- 1 | export function unloadModule(modulePath) { 2 | let nodeModule = require.cache[modulePath]; 3 | if (nodeModule) { 4 | for (let child of nodeModule.children) unloadModule(child.id); 5 | } 6 | delete require.cache[modulePath]; 7 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // Mapped from https://www.typescriptlang.org/tsconfig 3 | "compilerOptions": { 4 | // Type Checking 5 | "allowUnreachableCode": false, 6 | "allowUnusedLabels": false, 7 | "noFallthroughCasesInSwitch": true, 8 | "noImplicitOverride": true, 9 | "noImplicitReturns": false, 10 | "strict": false, 11 | "useUnknownInCatchVariables": true, 12 | "noUncheckedIndexedAccess": true, 13 | // Modules 14 | "module": "commonjs", 15 | "moduleResolution": "Node", 16 | "resolveJsonModule": true, 17 | // Emit 18 | "declaration": true, 19 | "declarationMap": true, 20 | "importHelpers": true, 21 | "inlineSources": true, 22 | "newLine": "lf", 23 | "noEmitHelpers": true, 24 | "outDir": "dist", 25 | "preserveConstEnums": true, 26 | "removeComments": false, 27 | "sourceMap": true, 28 | "esModuleInterop": true, 29 | "forceConsistentCasingInFileNames": true, 30 | // Language and Environment 31 | "emitDecoratorMetadata": true, 32 | "experimentalDecorators": true, 33 | "lib": [ 34 | "ESNext" 35 | ], 36 | "target": "ESNext", 37 | "useDefineForClassFields": true, 38 | "skipLibCheck": true, 39 | "skipDefaultLibCheck": true 40 | } 41 | } --------------------------------------------------------------------------------