diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..63c370c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,60 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + +jobs: + fmt: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Install tooling + uses: CompeyDev/setup-rokit@v0.1.0 + with: + cache: true + - name: Check formatting + run: lune run fmt + + lint: + runs-on: ubuntu-latest + needs: ["fmt"] + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Install tooling + uses: CompeyDev/setup-rokit@v0.1.0 + with: + cache: true + - name: Lint + run: lune run lint + typecheck: + runs-on: ubuntu-latest + needs: ["lint"] + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Install tooling + uses: CompeyDev/setup-rokit@v0.1.0 + with: + cache: true + - name: Setup lune typedefs + run: lune setup + - name: Typecheck + run: lune run analyze + test: + runs-on: ubuntu-latest + needs: ["typecheck"] + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: true + - name: Install tooling + uses: CompeyDev/setup-rokit@v0.1.0 + with: + cache: true + - name: Run unit-tests + run: lune run test diff --git a/.github/workflows/pull-request-moonwave.yml b/.github/workflows/pull-request-moonwave.yml deleted file mode 100644 index 9e41c54..0000000 --- a/.github/workflows/pull-request-moonwave.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Moonwave Documentation - -on: - push: - branches: [ "*" ] - pull_request: - branches: [ "*" ] - - workflow_dispatch: - -jobs: - generate: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - uses: ok-nick/setup-aftman@v0.4.2 - - name: Generate Standard Library - run: selene generate-roblox-std - - name: Run Moonwave Extract - run: moonwave extract Package \ No newline at end of file diff --git a/.github/workflows/pull-request-selene.yml b/.github/workflows/pull-request-selene.yml deleted file mode 100644 index 1f3b181..0000000 --- a/.github/workflows/pull-request-selene.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Selene Linter - -on: - push: - branches: [ "*" ] - pull_request: - branches: [ "*" ] - - workflow_dispatch: - -jobs: - generate: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - uses: ok-nick/setup-aftman@v0.4.2 - - name: Generate Standard Library - run: selene generate-roblox-std - - name: Run Selene - run: selene Package \ No newline at end of file diff --git a/.github/workflows/pull-request-stylua.yml b/.github/workflows/pull-request-stylua.yml deleted file mode 100644 index 1b8c212..0000000 --- a/.github/workflows/pull-request-stylua.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Stylua Formatter - -on: - push: - branches: [ "*" ] - pull_request: - branches: [ "*" ] - - workflow_dispatch: - -jobs: - generate: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - uses: ok-nick/setup-aftman@v0.4.2 - - name: Generate Standard Library - run: selene generate-roblox-std - - name: Run Stylua - run: stylua --check --output-format Summary Package \ No newline at end of file diff --git a/.github/workflows/update-documentation.yml b/.github/workflows/update-documentation.yml index 43e6803..d13f44a 100644 --- a/.github/workflows/update-documentation.yml +++ b/.github/workflows/update-documentation.yml @@ -18,6 +18,6 @@ jobs: uses: peter-evans/repository-dispatch@v3 with: token: ${{ secrets.PAT }} - repository: DiscordLuau/Discord-Luau-Site + repository: DiscordLuau/docs event-type: update-ref client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}' diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..92e3c3a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "extern/frktest"] + path = extern/frktest + url = https://github.com/itsfrank/frktest.git diff --git a/.luaurc b/.luaurc index 120bf34..1b63b82 100644 --- a/.luaurc +++ b/.luaurc @@ -4,13 +4,18 @@ "*": false }, "aliases": { - "Builders": "./Package/Classes/Builders", - "Network": "./Package/Classes/Network", - "Objects": "./Package/Classes/Objects", - "Data": "./Package/Data", - "Enums": "./Package/Enums", - "Std": "./Package/Std", - "Utils": "./Package/Utils", - "Vendor": "./Package/Vendor" + "builders": "./packages/builders/src", + "cdn": "./packages/cdn/src", + "classes": "./packages/classes/src", + "core": "./packages/core/src", + "extensions": "./packages/extensions/src", + "std-polyfills": "./packages/std-polyfills/src", + "api-types": "./packages/api-types/src", + "rest": "./packages/rest/src", + "utils": "./packages/utils/src", + "vendor": "./packages/vendor/src", + "voice": "./packages/voice/src", + "websocket": "./packages/websocket/src", + "frktest": "./extern/frktest/src" } } \ No newline at end of file diff --git a/.lune/analyze.luau b/.lune/analyze.luau new file mode 100644 index 0000000..1e5dfcc --- /dev/null +++ b/.lune/analyze.luau @@ -0,0 +1,10 @@ +--> Task to typecheck packages using luau-lsp + +local task = require("task") + +task.new("luau-lsp") + :addArgument("analyze") + :addArgument("--base-luaurc=.luaurc") + :addArgument("--settings=.vscode/settings.json") + :addArgument("packages") + :execute() diff --git a/.lune/assets/powered-by-lune.svg b/.lune/assets/powered-by-lune.svg new file mode 100644 index 0000000..d071286 --- /dev/null +++ b/.lune/assets/powered-by-lune.svg @@ -0,0 +1 @@ +Powered by: LunePowered byLune diff --git a/.lune/fmt.luau b/.lune/fmt.luau new file mode 100644 index 0000000..a01aa85 --- /dev/null +++ b/.lune/fmt.luau @@ -0,0 +1,10 @@ +--> Task to execute StyLua for checking validity of formatting + +local task = require("task") + +task.new("stylua") + :addArgument("--check") + :addArgument("--output-format") + :addArgument("Summary") + :addArgument("packages") + :execute() diff --git a/.lune/lint.luau b/.lune/lint.luau new file mode 100644 index 0000000..415bdde --- /dev/null +++ b/.lune/lint.luau @@ -0,0 +1,5 @@ +--> Task for linting packages using selene + +local task = require("task") + +task.new("selene"):addArgument("packages"):execute() diff --git a/.lune/task/init.luau b/.lune/task/init.luau new file mode 100644 index 0000000..38017bf --- /dev/null +++ b/.lune/task/init.luau @@ -0,0 +1,65 @@ +--> An internal library for lune scripts to execute tasks in CI/CD + +-- NOTE: We use the `Lune` runtime in the CI/CD environment, so we're not going to be using the `@std-polyfills` +-- package that the discord-luau package uses. + +local process = require("@lune/process") +local stdio = require("@lune/stdio") + +local Task = {} + +Task.Prototype = {} +Task.Interface = {} + +--[[ + Responsible for adding an argument to the executable. +]] +function Task.Prototype.addArgument(self: Task, argument: string): Task + table.insert(self.arguments, argument) + + return self +end + +--[[ + Responsible for executing the task. + + Inherits the standard IO of the process. +]] +function Task.Prototype.execute(self: Task): process.SpawnResult + local spawnResult = process.spawn(self.executable, self.arguments, { + stdio = "forward", + }) + + if not spawnResult.ok then + print( + `\n[{stdio.color("red")}error{stdio.color("reset")}] \`{self.executable}\` exited with code {spawnResult.code} ` + ) + + process.exit(spawnResult.code) + end + + return spawnResult +end + +--[[ + Constructor for the Task object. +]] +function Task.Interface.new(executable: string): Task + return setmetatable( + { + executable = executable, + + arguments = {}, + } :: Task, + { + __index = Task.Prototype, + } + ) +end + +export type Task = typeof(Task.Prototype) & { + executable: string, + arguments: { string }, +} + +return Task.Interface diff --git a/.lune/task/requireTests.luau b/.lune/task/requireTests.luau new file mode 100644 index 0000000..7c9ccc6 --- /dev/null +++ b/.lune/task/requireTests.luau @@ -0,0 +1,36 @@ +--> Runs tests located in the tests/ directory + +-- NOTE: we're using the `Lune` runtime in the CI/CD environment, so we're not going to be using the `@std-polyfills` +-- package that the discord-luau package uses. + +local fs = require("@lune/fs") +local stdio = require("@lune/stdio") +local process = require("@lune/process") + +local requireTests +local requireFile + +function requireFile(fullPath: string) + local success, result = pcall(require, fullPath) + + if not success then + print(`\n{stdio.color("red")}Failed to require unit-test at \`{fullPath}\`, error:`) + print(`{stdio.style("dim")}{result}`) + + process.exit(1) + else + result() + end +end + +function requireTests(path: string) + for _, object in fs.readDir(path) do + if fs.isDir(`{path}/{object}`) then + requireTests(`{path}/{object}`) + else + requireFile(`{path}/{object}`) + end + end +end + +return requireTests diff --git a/.lune/test.luau b/.lune/test.luau new file mode 100644 index 0000000..085ffa2 --- /dev/null +++ b/.lune/test.luau @@ -0,0 +1,15 @@ +--> Task for running unit-tests using frktest + +local frktest = require("@frktest/frktest") +local reporter = require("@frktest/reporters/lune_console_reporter") + +local requireTests = require("task/requireTests") + +requireTests("tests") + +reporter.init() + +if not frktest.run() then + print("--------------------") + error(`See above for test failures.`, -1) +end diff --git a/.nvim.lua b/.nvim.lua new file mode 100644 index 0000000..a0d3a43 --- /dev/null +++ b/.nvim.lua @@ -0,0 +1,40 @@ +require("luau-lsp").config({ + server = { + settings = { + ["luau-lsp"] = { + inlayHints = { + variableTypes = true, + functionReturnTypes = true, + parameterNames = "all", + typeHintMaxLength = 20, + }, + require = { + mode = "relativeToFile", + directoryAliases = { + ["@lune"] = "~/.lune/.typedefs/0.8.6/", + ["@builders"] = "./packages/builders", + ["@cdn"] = "./packages/cdn/src", + ["@classes"] = "./packages/classes/src", + ["@core"] = "./packages/core/src", + ["@extensions"] = "./packages/extensions/src", + ["@std-polyfills"] = "./packages/std-polyfills/src", + ["@api-types"] = "./packages/api-types/src", + ["@utils"] = "./packages/utils/src", + ["@vendor"] = "./packages/vendor/src", + ["@voice"] = "./packages/voice/src", + ["@websocket"] = "./packages/voice/src", + ["@frktest"] = "./extern/frktest/src", + }, + }, + completion = { + imports = { + enabled = true, + }, + }, + }, + }, + }, + platform = { + type = "standard", + }, +}) diff --git a/.vscode/settings.json b/.vscode/settings.json index a31bf88..6bf443e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { + "editor.formatOnSave": true, "luau-lsp.require.mode": "relativeToFile", "luau-lsp.inlayHints.variableTypes": true, "luau-lsp.inlayHints.functionReturnTypes": true, @@ -6,22 +7,19 @@ "luau-lsp.inlayHints.parameterTypes": true, "luau-lsp.inlayHints.typeHintMaxLength": 50, "luau-lsp.require.directoryAliases": { - "@lune/": "~/.lune/.typedefs/0.8.2/", - - "@Builders": "Package/Classes/Builders", - "@Network": "Package/Classes/Network", - "@Objects": "Package/Classes/Objects", - "@Data": "Package/Data", - "@Enums": "Package/Enums", - "@Std": "Package/Std", - "@Utils": "Package/Utils", - "@Vendor": "Package/Vendor", - }, - - "[lua]": { - "editor.formatOnSave": true - }, - "[luau]": { - "editor.formatOnSave": true - }, + "@lune/": "~/.lune/.typedefs/0.8.8/", + "@builders": "./packages/builders/src", + "@cdn": "./packages/cdn/src", + "@classes": "./packages/classes/src", + "@core": "./packages/core/src", + "@extensions": "./packages/extensions/src", + "@std-polyfills": "./packages/std-polyfills/src", + "@api-types": "./packages/api-types/src", + "@rest": "./packages/rest/src", + "@utils": "./packages/utils/src", + "@vendor": "./packages/vendor/src", + "@voice": "./packages/voice/src", + "@websocket": "./packages/websocket/src", + "@frktest": "./extern/frktest/src" + } } \ No newline at end of file diff --git a/Examples/Commands.luau b/Examples/Commands.luau deleted file mode 100644 index 72519c0..0000000 --- a/Examples/Commands.luau +++ /dev/null @@ -1,45 +0,0 @@ -local DiscordLuau = require("..") -local Env = require("../.env") - -local DiscordSettings = DiscordLuau.SettingsBuilder.new(Env.DISCORD_BOT_TOKEN) -local DiscordClient = DiscordLuau.DiscordClient.new(DiscordSettings) - -DiscordClient:setVerbose(true) - -DiscordClient.eventManager.onReady:connect(function() - print(`🎉🎉 {DiscordClient.discordUser.username} is online! 🎉🎉`) - - local permissions = DiscordLuau.PermissionsBuilder.new() - - permissions:addPermission(DiscordLuau.PermissionsBuilder.Permissions.UseApplicationCommands) - - local slashCommand0 = DiscordLuau.CommandBuilder - .new() - :setName("example-0") - :setDescription("...") - :setGuildPermissions(permissions) - :addContext(DiscordLuau.CommandBuilder.Context.Guild) - :addIntegration(DiscordLuau.CommandBuilder.IntegrationType.GuildCommand) - - local slashCommand1 = DiscordLuau.CommandBuilder - .new() - :setName("example-1") - :setDescription("abc!") - :setGuildPermissions(permissions) - :addContext(DiscordLuau.CommandBuilder.Context.Guild) - :addIntegration(DiscordLuau.CommandBuilder.IntegrationType.GuildCommand) - - DiscordClient.discordApplication - :setSlashCommandsAsync({ - slashCommand0, - slashCommand1, - }) - :after(function(data) - print("updated, fetching current commands..") - DiscordClient.discordApplication:fetchSlashCommandsAsync():after(function(...) - print(...) - end) - end) -end) - -DiscordClient:connectAsync() diff --git a/Examples/Components.luau b/Examples/Components.luau deleted file mode 100644 index 31659fe..0000000 --- a/Examples/Components.luau +++ /dev/null @@ -1,49 +0,0 @@ -local DiscordLuau = require("..") -local Env = require("../.env") - -local DiscordSettings = DiscordLuau.SettingsBuilder.new(Env.DISCORD_BOT_TOKEN) -local DiscordClient = DiscordLuau.DiscordClient.new(DiscordSettings) - -DiscordClient.eventManager.onReady:connect(function() - print(`🎉🎉 {DiscordClient.discordUser.username} is online! 🎉🎉`) - - local discordChannel = - DiscordClient:fetchChannelAsync("1048686561685946489"):await() :: DiscordLuau.GuildTextChannel - - discordChannel:sendMessageAsync( - DiscordLuau.MessageBuilder.new():setContent("Hello, I have jus woken up!"):addComponent( - DiscordLuau.ActionRowBuilder - .new() - :addComponent( - DiscordLuau.ButtonBuilder - .new("button-0") - :setDisabled(true) - :setStyle(DiscordLuau.ButtonBuilder.Style.Blurple) - :setLabel("Burple") - ) - :addComponent( - DiscordLuau.ButtonBuilder - .new("button-1") - :setDisabled(true) - :setStyle(DiscordLuau.ButtonBuilder.Style.Green) - :setLabel("Green") - ) - :addComponent( - DiscordLuau.ButtonBuilder - .new("button-2") - :setDisabled(true) - :setStyle(DiscordLuau.ButtonBuilder.Style.Grey) - :setLabel("Grey") - ) - :addComponent( - DiscordLuau.ButtonBuilder - .new("button-3") - :setDisabled(true) - :setStyle(DiscordLuau.ButtonBuilder.Style.Red) - :setLabel("Red") - ) - ) - ) -end) - -DiscordClient:connectAsync() diff --git a/Examples/Development.luau b/Examples/Development.luau deleted file mode 100644 index a661384..0000000 --- a/Examples/Development.luau +++ /dev/null @@ -1,17 +0,0 @@ -local DiscordLuau = require("..") -local Env = require("../.env") - -local DiscordSettings = DiscordLuau.SettingsBuilder.new(Env.DISCORD_BOT_TOKEN) -local DiscordClient = DiscordLuau.DiscordClient.new(DiscordSettings) - -DiscordClient:setVerbose(true) - -DiscordClient.eventManager.onReady:connect(function() - print(`🎉🎉 {DiscordClient.discordUser.username} is online! 🎉🎉`) - - local guild = DiscordClient:fetchGuildAsync("737382889947136000"):await() :: DiscordLuau.DiscordGuild - - print(guild.name) -end) - -return DiscordClient:connectAsync() diff --git a/Examples/Files.luau b/Examples/Files.luau deleted file mode 100644 index 0406bce..0000000 --- a/Examples/Files.luau +++ /dev/null @@ -1,26 +0,0 @@ -local DiscordLuau = require("..") -local Env = require("../.env") - -local DiscordSettings = DiscordLuau.SettingsBuilder.new(Env.DISCORD_BOT_TOKEN) -local DiscordClient = DiscordLuau.DiscordClient.new(DiscordSettings) - --- DiscordClient:setVerbose(true) - -DiscordClient.eventManager.onReady:connect(function() - print(`🎉🎉 {DiscordClient.discordUser.username} is online! 🎉🎉`) - - local textChannel = DiscordClient:fetchChannelAsync("1048686561685946489"):await() :: DiscordLuau.GuildTextChannel - local responseMessage = DiscordLuau.MessageBuilder.new() - - responseMessage:addFile( - DiscordLuau.AttachmentBuilder - .new() - :setData("Hello, World") - :setDescription("Example File") - :setName("Example.txt") - ) - - textChannel:sendMessageAsync(responseMessage) -end) - -DiscordClient:connectAsync() diff --git a/Examples/Message.luau b/Examples/Message.luau deleted file mode 100644 index e5bc33f..0000000 --- a/Examples/Message.luau +++ /dev/null @@ -1,23 +0,0 @@ -local DiscordLuau = require("..") -local Env = require("../.env") - -local DiscordSettings = DiscordLuau.SettingsBuilder.new(Env.DISCORD_BOT_TOKEN) -local DiscordClient = DiscordLuau.DiscordClient.new(DiscordSettings) - -DiscordClient.eventManager.onMessage:connect(function(message) - print(`DiscordUser '{message.author.username}' has said; {message.content}`) - - if string.find(string.lower(message.content), "hello") then - if message.author.id == DiscordClient.discordUser.id then - return - end - - message:replyAsync(DiscordLuau.MessageBuilder.new():setContent(`Hello, from Discord-Luau!`)) - end -end) - -DiscordClient.eventManager.onReady:connect(function() - print(`🎉🎉 {DiscordClient.discordUser.username} is online! 🎉🎉`) -end) - -DiscordClient:connectAsync() diff --git a/Examples/Modal.luau b/Examples/Modal.luau deleted file mode 100644 index eb7f231..0000000 --- a/Examples/Modal.luau +++ /dev/null @@ -1,98 +0,0 @@ -local Task = require("@Std/Task") - -local DiscordLuau = require("..") -local Env = require("../.env") - -local DiscordSettings = DiscordLuau.SettingsBuilder.new(Env.DISCORD_BOT_TOKEN) -local DiscordClient = DiscordLuau.DiscordClient.new(DiscordSettings) - -DiscordClient.eventManager.onInteraction:connect(function(interaction) - if interaction.data.name == "example-command" then - local discordModal = DiscordLuau.ModalBuilder.new("response-modal") - - discordModal:setTitle("Modal Test") - - discordModal:addComponent( - DiscordLuau.ActionRowBuilder.new():addComponent( - DiscordLuau.TextInputBuilder - .new("text-input-0") - :setLabel("Label #0") - :setMaxLength(1000) - :setMinLength(10) - :setRequired(true) - :setStyle(DiscordLuau.TextInputBuilder.Style.Paragraph) - ) - ) - - interaction:sendModalAsync(discordModal) - else - interaction:deferAsync():await() - - Task.delay(1, function() - interaction:sendMessageAsync( - DiscordLuau.MessageBuilder.new():setContent("Feedback noted, thanks!"):addComponent( - DiscordLuau.ActionRowBuilder - .new() - :addComponent( - DiscordLuau.ButtonBuilder - .new("button-0") - :setDisabled(true) - :setStyle(DiscordLuau.ButtonBuilder.Style.Blurple) - :setLabel("Burple") - ) - :addComponent( - DiscordLuau.ButtonBuilder - .new("button-1") - :setDisabled(true) - :setStyle(DiscordLuau.ButtonBuilder.Style.Green) - :setLabel("Green") - ) - :addComponent( - DiscordLuau.ButtonBuilder - .new("button-2") - :setDisabled(true) - :setStyle(DiscordLuau.ButtonBuilder.Style.Grey) - :setLabel("Grey") - ) - :addComponent( - DiscordLuau.ButtonBuilder - .new("button-3") - :setDisabled(true) - :setStyle(DiscordLuau.ButtonBuilder.Style.Red) - :setLabel("Red") - ) - ) - ) - end) - end -end) - -DiscordClient.eventManager.onReady:connect(function() - print(`🎉🎉 {DiscordClient.discordUser.username} is online! 🎉🎉`) - - local permissions = DiscordLuau.PermissionsBuilder.new() - - permissions:addPermission(DiscordLuau.PermissionsBuilder.Permissions.SendMessages) - - local slashCommand = DiscordLuau.CommandBuilder - .new() - :setName("example-command") - :setDescription("Example Description") - :setGuildPermissions(permissions) - :addContext(DiscordLuau.CommandBuilder.Context.Guild) - :addIntegration(DiscordLuau.CommandBuilder.IntegrationType.GuildCommand) - - DiscordClient.discordApplication - :setSlashCommandsAsync({ - slashCommand, - }) - :after(function(data) - print("updated, fetching current commands..") - - DiscordClient.discordApplication:fetchSlashCommandsAsync():after(function(...) - print(...) - end) - end) -end) - -DiscordClient:connectAsync() diff --git a/Examples/Precence.luau b/Examples/Precence.luau deleted file mode 100644 index 1d5b847..0000000 --- a/Examples/Precence.luau +++ /dev/null @@ -1,25 +0,0 @@ -local DiscordLuau = require("..") -local Env = require("../.env") - -local DiscordSettings = DiscordLuau.SettingsBuilder.new(Env.DISCORD_BOT_TOKEN) -local DiscordClient = DiscordLuau.DiscordClient.new(DiscordSettings) - -DiscordClient.eventManager.onReady:connect(function() - print(`🎉🎉 {DiscordClient.discordUser.username} is online! 🎉🎉`) - - local discordPresence = DiscordLuau.PresenceBuilder.new() - local discordActivity = DiscordLuau.ActivityBuilder.new() - - discordActivity:setActivityName("AsyncMatrix program at 3AM") - discordActivity:setActivityType(DiscordLuau.ActivityBuilder.Type.Watching) - - discordPresence:setStatus(DiscordLuau.PresenceBuilder.Status.Idle) - discordPresence:addActivity(discordActivity) - discordPresence:setSince(0) - - DiscordClient:updatePresenceAsync(discordPresence):after(function() - print(`Updated '{DiscordClient.discordUser.username}' preasence!`) - end) -end) - -DiscordClient:connectAsync() diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e8cc4e1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Discord-Luau + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Package/Classes/Builders/ActivityBuilder.luau b/Package/Classes/Builders/ActivityBuilder.luau deleted file mode 100644 index 06820dc..0000000 --- a/Package/Classes/Builders/ActivityBuilder.luau +++ /dev/null @@ -1,134 +0,0 @@ -local Enumerate = require("@Utils/Enumerate") -local Construct = require("@Utils/Construct") - -local Resolvable = require("@Network/Resolvable") - -local ResolvableType = require("@Enums/ResolvableType") - ---[=[ - @class Builders.ActivityBuilder - - ActivityBuilder is used to construct an activity object for a Discord bot - including name, type, and streaming URL. - - Usage: - ```lua - local activity = ActivityBuilder.new() - :setActivityName("Playing a game") - :setActivityType(ActivityBuilder.Type.Game) - :setStreamingURL("https://twitch.tv/example") - ``` -]=] -local ActivityBuilder = {} - -ActivityBuilder.Interface = {} -ActivityBuilder.Prototype = {} - -ActivityBuilder.Prototype.type = "ActivityBuilder" - ---[=[ - @prop Type table - @within Builders.ActivityBuilder - - An enumeration of activity types. - - - Game: 0 - - Streaming: 1 - - Listening: 2 - - Watching: 3 - - Competing: 5 -]=] -ActivityBuilder.Interface.Type = Enumerate.new({ - Game = 0, - Streaming = 1, - Listening = 2, - Watching = 3, - Competing = 5, -}) - ---[=[ - Sets the name of the activity. - - @method setActivityName - @param activityName string -- The name of the activity. - @within Builders.ActivityBuilder - @return Builders.ActivityBuilder -- Returns the ActivityBuilder instance for method chaining. -]=] -function ActivityBuilder.Prototype.setActivityName(self: ActivityBuilder, activityName: string) - self.name = activityName - - return self -end - ---[=[ - Sets the type of the activity. - - @method setActivityType - @param activityType number -- The type of the activity. - @within Builders.ActivityBuilder - @return Builders.ActivityBuilder -- Returns the ActivityBuilder instance for method chaining. -]=] -function ActivityBuilder.Prototype.setActivityType(self: ActivityBuilder, activityType: number) - ActivityBuilder.Interface.Type:Assert(activityType) - - self.activityType = activityType - - return self -end - ---[=[ - Sets the streaming URL of the activity. Only YouTube and Twitch URLs are allowed. - - @method setStreamingURL - @param streamURL string -- The streaming URL. - @within Builders.ActivityBuilder - @return Builders.ActivityBuilder -- Returns the ActivityBuilder instance for method chaining. -]=] -function ActivityBuilder.Prototype.setStreamingURL(self: ActivityBuilder, streamURL: string) - if not string.find(streamURL, "youtube.com") and not string.find(streamURL, "twitch.tv") then - return - end - - self.streamingURL = streamURL - - return self -end - ---[=[ - Converts the activity to a JSON object that can be sent to the Discord API. - - @method toPayloadObject - @within Builders.ActivityBuilder - @return Network.Resolvable -]=] -function ActivityBuilder.Prototype.toPayloadObject(self: ActivityBuilder) - return Resolvable.new(ResolvableType.JSON, { - name = self.name or "", - type = self.activityType or 0, - url = self.streamingURL, - }) -end - ---[=[ - Creates a new instance of ActivityBuilder. - - @function new - @within Builders.ActivityBuilder - @return Builders.ActivityBuilder -- A new instance of ActivityBuilder. -]=] -function ActivityBuilder.Interface.new() - return ( - Construct({ - name = "Discord-Luau", - activityType = 0, - streamingURL = nil, - }, ActivityBuilder.Prototype) :: unknown - ) :: ActivityBuilder -end - -export type ActivityBuilder = typeof(ActivityBuilder.Prototype) & { - streamingURL: string?, - activityType: number, - name: string, -} - -return ActivityBuilder.Interface diff --git a/Package/Classes/Builders/AttachmentBuilder.luau b/Package/Classes/Builders/AttachmentBuilder.luau deleted file mode 100644 index f07a9cf..0000000 --- a/Package/Classes/Builders/AttachmentBuilder.luau +++ /dev/null @@ -1,104 +0,0 @@ -local Construct = require("@Utils/Construct") - -local Resolvable = require("@Network/Resolvable") - -local ResolvableType = require("@Enums/ResolvableType") - ---[=[ - @class Builders.AttachmentBuilder - - AttachmentBuilder allows you to build attachments that you can send files through to the discord API. - - Usage: - ```lua - local DiscordLuau = require("../Submodules/DiscordLuau") - - local AttachmentBuilder = DiscordLuau.AttachmentBuilder.new() - :setName("Example.txt") - :setData("My Text Document!") - ``` -]=] -local AttachmentBuilder = {} - -AttachmentBuilder.Interface = {} -AttachmentBuilder.Prototype = {} - -AttachmentBuilder.Prototype.type = "AttachmentBuilder" - ---[=[ - Set the name for this Attachment - - @method setName - @param name string - @within Builders.AttachmentBuilder - @return Builders.AttachmentBuilder -]=] -function AttachmentBuilder.Prototype.setName(self: AttachmentBuilder, name: string) - self.fileName = name - - return self -end - ---[=[ - Set the description for this Attachment - - @method setDescription - @param description string - @within Builders.AttachmentBuilder - @return Builders.AttachmentBuilder -]=] -function AttachmentBuilder.Prototype.setDescription(self: AttachmentBuilder, description: string) - self.fileDescription = description - - return self -end - ---[=[ - Set the data for this Attachment - - @method setData - @param data string - @within Builders.AttachmentBuilder - @return Builders.AttachmentBuilder -]=] -function AttachmentBuilder.Prototype.setData(self: AttachmentBuilder, data: string) - self.fileData = data - - return self -end - ---[=[ - Converts the message to a JSON object that can be sent to the Discord API. - - @method toPayloadObject - @within Builders.MessageBuilder - @return Network.Resolvable -]=] -function AttachmentBuilder.Prototype.toPayloadObject(self: AttachmentBuilder): Resolvable.Resolvable - return Resolvable.new(ResolvableType.FORMDATA, { - fileName = self.fileName, - fileDescription = self.fileDescription, - fileData = self.fileDescription, - }) -end - ---[=[ - - Creates a new instance of AttachmentBuilder with the specified intents. - - @function new - @param intentList { string } -- A list of intents to include. - @within Builders.AttachmentBuilder - @return Builders.AttachmentBuilder -- A new instance of AttachmentBuilder. -]=] -function AttachmentBuilder.Interface.new() - return (Construct({}, AttachmentBuilder.Prototype) :: unknown) :: AttachmentBuilder -end - -export type AttachmentBuilder = typeof(AttachmentBuilder.Prototype) & { - fileName: string?, - fileDescription: string?, - fileData: string?, -} - -return AttachmentBuilder.Interface diff --git a/Package/Classes/Builders/AutomoderationRuleBuilder.luau b/Package/Classes/Builders/AutomoderationRuleBuilder.luau deleted file mode 100644 index 196a8e8..0000000 --- a/Package/Classes/Builders/AutomoderationRuleBuilder.luau +++ /dev/null @@ -1,319 +0,0 @@ -local Construct = require("@Utils/Construct") -local Enumerate = require("@Utils/Enumerate") - -local Resolvable = require("@Network/Resolvable") - -local ResolvableType = require("@Enums/ResolvableType") - ---[=[ - @class Builders.AutomoderationRuleBuilder - - AutomoderationRuleBuilder is used to construct an auto-moderation rule for a Discord guild, including trigger types, actions, and exempt roles or channels. - - Usage: - ```lua - local rule = AutomoderationRuleBuilder.new() - :setName("No Profanity") - :setEventType(AutomoderationRuleBuilder.EventType.MessageSend) - :setTriggerType(AutomoderationRuleBuilder.TriggerType.Keyword) - :setTriggerMetadata({ keywordFilter = {"badword1", "badword2"} }) - :addAction(AutomoderationRuleBuilder.ActionType.BlockMessage, { customMessage = "Profanity is not allowed!" }) - :setEnabled(true) - ``` -]=] -local AutomoderationRuleBuilder = {} - -AutomoderationRuleBuilder.Prototype = {} -AutomoderationRuleBuilder.Interface = {} - -AutomoderationRuleBuilder.Prototype.type = "AutomoderationRuleBuilder" - ---[=[ - @prop EventType table - @within Builders.AutomoderationRuleBuilder - - An enumeration of event types. - - - MessageSend: 1 -]=] -AutomoderationRuleBuilder.Interface.EventType = Enumerate.new({ - MessageSend = 1, -}) - ---[=[ - @prop TriggerType table - @within Builders.AutomoderationRuleBuilder - - An enumeration of trigger types. - - - Keyword: 1 - - Spam: 3 - - KeywordPreset: 4 - - MentionSpam: 5 -]=] -AutomoderationRuleBuilder.Interface.TriggerType = Enumerate.new({ - Keyword = 1, - Spam = 3, - KeywordPreset = 4, - MentionSpam = 5, -}) - ---[=[ - @prop KeywordPresets table - @within Builders.AutomoderationRuleBuilder - - An enumeration of keyword presets. - - - Profanity: 1 - - SexualContent: 2 - - Slurs: 3 -]=] -AutomoderationRuleBuilder.Interface.KeywordPresets = Enumerate.new({ - Profanity = 1, - SexualContent = 2, - Slurs = 3, -}) - ---[=[ - @prop ActionType table - @within Builders.AutomoderationRuleBuilder - - An enumeration of action types. - - - BlockMessage: 1 - - SendAlertMessage: 2 - - Timeout: 3 -]=] -AutomoderationRuleBuilder.Interface.ActionType = Enumerate.new({ - BlockMessage = 1, - SendAlertMessage = 2, - Timeout = 3, -}) - ---[=[ - Sets the name of the rule. - - @method setName - @param name string -- The name of the rule. - @within Builders.AutomoderationRuleBuilder - @return Builders.AutomoderationRuleBuilder -- Returns the AutomoderationRuleBuilder instance for method chaining. -]=] -function AutomoderationRuleBuilder.Prototype.setName(self: AutomoderationRuleBuilder, name: string) - self.ruleName = name - - return self -end - ---[=[ - Sets the event type for the rule. - - @method setEventType - @param eventType number -- The event type of the rule. - @within Builders.AutomoderationRuleBuilder - @return Builders.AutomoderationRuleBuilder -- Returns the AutomoderationRuleBuilder instance for method chaining. -]=] -function AutomoderationRuleBuilder.Prototype.setEventType(self: AutomoderationRuleBuilder, eventType: number) - self.roleEventType = eventType - - return self -end - ---[=[ - Sets the trigger type for the rule. - - @method setTriggerType - @param triggerType number -- The trigger type of the rule. - @within Builders.AutomoderationRuleBuilder - @return Builders.AutomoderationRuleBuilder -- Returns the AutomoderationRuleBuilder instance for method chaining. -]=] -function AutomoderationRuleBuilder.Prototype.setTriggerType(self: AutomoderationRuleBuilder, triggerType: number) - self.ruleTriggerType = triggerType - - return self -end - ---[=[ - Sets the trigger metadata for the rule. - - @method setTriggerMetadata - @param triggerMetadata table -- The metadata for the trigger. - @within Builders.AutomoderationRuleBuilder - @return Builders.AutomoderationRuleBuilder -- Returns the AutomoderationRuleBuilder instance for method chaining. -]=] -function AutomoderationRuleBuilder.Prototype.setTriggerMetadata( - self: AutomoderationRuleBuilder, - triggerMetadata: { - keywordFilter: { string }?, - regexPattern: { string }?, - presets: { number }?, - allowList: { string }?, - mentionTotalLimit: number?, - mentionRaidProtectionEnabled: boolean?, - } -) - self.triggerMetadata = { - keywordFilter = triggerMetadata.keywordFilter or {}, - regexPattern = triggerMetadata.regexPattern or {}, - presets = triggerMetadata.presets or {}, - allowList = triggerMetadata.allowList or {}, - mentionTotalLimit = triggerMetadata.mentionTotalLimit or 1, - mentionRaidProtectionEnabled = triggerMetadata.mentionRaidProtectionEnabled or false, - } - - return self -end - ---[=[ - Adds an action to the rule. - - @method addAction - @param actionType number -- The type of action. - @param actionMetadata table -- The metadata for the action. - @within Builders.AutomoderationRuleBuilder - @return Builders.AutomoderationRuleBuilder -- Returns the AutomoderationRuleBuilder instance for method chaining. -]=] -function AutomoderationRuleBuilder.Prototype.addAction( - self: AutomoderationRuleBuilder, - actionType: number, - actionMetadata: { - channelId: string?, - durationSeconds: number?, - customMessage: string?, - } -) - table.insert(self.actions, { - type = actionType, - metadata = { - channelId = actionMetadata.channelId, - durationSeconds = actionMetadata.durationSeconds, - customMessage = actionMetadata.customMessage, - }, - }) - - return self -end - ---[=[ - Sets whether the rule is enabled. - - @method setEnabled - @param enabled boolean -- Whether the rule is enabled. - @within Builders.AutomoderationRuleBuilder - @return Builders.AutomoderationRuleBuilder -- Returns the AutomoderationRuleBuilder instance for method chaining. -]=] -function AutomoderationRuleBuilder.Prototype.setEnabled(self: AutomoderationRuleBuilder, enabled: boolean) - self.ruleEnabled = enabled - - return self -end - ---[=[ - Adds an exempt role to the rule. - - @method addExemptRole - @param roleId string -- The ID of the role to exempt. - @within Builders.AutomoderationRuleBuilder - @return Builders.AutomoderationRuleBuilder -- Returns the AutomoderationRuleBuilder instance for method chaining. -]=] -function AutomoderationRuleBuilder.Prototype.addExemptRole(self: AutomoderationRuleBuilder, roleId: string) - table.insert(self.exemptRoles, roleId) - - return self -end - ---[=[ - Adds an exempt channel to the rule. - - @method addExemptChannel - @param channelId string -- The ID of the channel to exempt. - @within Builders.AutomoderationRuleBuilder - @return Builders.AutomoderationRuleBuilder -- Returns the AutomoderationRuleBuilder instance for method chaining. -]=] -function AutomoderationRuleBuilder.Prototype.addExemptChannel(self: AutomoderationRuleBuilder, channelId: string) - table.insert(self.exemptChannels, channelId) - - return self -end - ---[=[ - Converts the rule to a JSON object that can be sent to the Discord API. - - @method toPayloadObject - @within Builders.AutomoderationRuleBuilder - @return Network.Resolvable -]=] -function AutomoderationRuleBuilder.Prototype.toPayloadObject(self: AutomoderationRuleBuilder) - local actions = {} - - for _, actionObject in self.actions do - table.insert(actions, { - type = actionObject.type, - metadata = { - channel_id = actionObject.metadata.channelId, - duration_seconds = actionObject.metadata.durationSeconds, - custom_message = actionObject.metadata.customMessage, - }, - }) - end - - return Resolvable.new(ResolvableType.JSON, { - { - name = self.ruleName, - event_type = self.roleEventType, - trigger_type = self.ruleTriggerType, - trigger_metadata = self.triggerMetadata, - actions = actions, - enabled = self.ruleEnabled, - exempt_roles = self.exemptRoles, - exempt_channels = self.exemptChannels, - }, - }) -end - ---[=[ - Creates a new instance of AutomoderationRuleBuilder. - - @function new - @within Builders.AutomoderationRuleBuilder - @return Builders.AutomoderationRuleBuilder -- A new instance of AutomoderationRuleBuilder. -]=] -function AutomoderationRuleBuilder.Interface.new() - return ( - Construct({ - actions = {}, - exemptRoles = {}, - exemptChannels = {}, - }, AutomoderationRuleBuilder.Prototype) :: unknown - ) :: AutomoderationRuleBuilder -end - -export type AutomoderationRuleBuilder = typeof(AutomoderationRuleBuilder.Prototype) & { - exemptChannels: { string }, - exemptRoles: { string }, - actions: { - { - type: number, - metadata: { - channelId: string?, - durationSeconds: number?, - customMessage: string?, - }, - } - }, - - triggerMetadata: { - keywordFilter: { string }, - regexPattern: { string }, - presets: { number }, - allowList: { string }, - mentionTotalLimit: number, - mentionRaidProtectionEnabled: boolean, - }, - - ruleEnabled: boolean, - ruleTriggerType: number, - ruleName: string, - roleEventType: number, -} - -return AutomoderationRuleBuilder.Interface diff --git a/Package/Classes/Builders/ChannelBuilder.luau b/Package/Classes/Builders/ChannelBuilder.luau deleted file mode 100644 index e15a938..0000000 --- a/Package/Classes/Builders/ChannelBuilder.luau +++ /dev/null @@ -1,426 +0,0 @@ -local Enumerate = require("@Utils/Enumerate") -local Construct = require("@Utils/Construct") - -local ResolvableType = require("@Enums/ResolvableType") -local Resolvable = require("@Network/Resolvable") - -local PermissionsBuilder = require("@Builders/PermissionsBuilder") - ---[=[ - @class Builders.ChannelBuilder -]=] -local ChannelBuilder = {} - -ChannelBuilder.Interface = {} -ChannelBuilder.Prototype = {} - -ChannelBuilder.Prototype.type = "ChannelBuilder" - ---[=[ - @prop Type table - @within Builders.ChannelBuilder - - An enumeration of activity types. - - - Game: 0 - - Streaming: 1 - - Listening: 2 - - Watching: 3 - - Competing: 5 -]=] -ChannelBuilder.Interface.Type = Enumerate.new({ - GuildTextChannel = 0, - GuildVoiceChannel = 2, - GuildCategoryChannel = 4, - GuildAnnouncementChannel = 5, - GuildForumChannel = 15, - GuildMediaChannel = 16, -}) - ---[=[ - @prop VideoQualityMode table - @within Builders.ChannelBuilder - - An enumeration of activity types. - - - QualityAuto: 1 - - Quality720p: 2 -]=] -ChannelBuilder.Interface.VideoQualityMode = Enumerate.new({ - QualityAuto = 1, - Quality720p = 2, -}) - ---[=[ - @prop SortOrder table - @within Builders.ChannelBuilder - - An enumeration of activity types. - - - LatestActivity: 0 - - CreationDate: 1 -]=] -ChannelBuilder.Interface.SortOrder = Enumerate.new({ - LatestActivity = 0, - CreationDate = 1, -}) - ---[=[ - @prop ForumLayout table - @within Builders.ChannelBuilder - - An enumeration of activity types. - - - NotSet: 0 - - ListView: 1 - - GalleryView: 2 -]=] -ChannelBuilder.Interface.ForumLayout = Enumerate.new({ - NotSet = 0, - ListView = 1, - GalleryView = 2, -}) - ---[=[ - Sets the type of the current discord channel. - - @method setType - @within Builders.ChannelBuilder - @return Builders.ChannelBuilder -]=] -function ChannelBuilder.Prototype.setType(self: ChannelBuilder, type: number) - ChannelBuilder.Interface.Type:Assert(type) - - self.channelType = type :: any - - return self -end - ---[=[ - Sets the bitrate of the current discord voice channel. - - @method setBitrate - @within Builders.ChannelBuilder - @return Builders.ChannelBuilder -]=] -function ChannelBuilder.Prototype.setBitrate(self: ChannelBuilder, bitrate: number) - self.bitrate = bitrate - - return self -end - ---[=[ - Sets the region of the current discord voice channel. - - @method setRegion - @within Builders.ChannelBuilder - @return Builders.ChannelBuilder -]=] -function ChannelBuilder.Prototype.setRegion(self: ChannelBuilder, regionId: number) - self.region = regionId - - return self -end - ---[=[ - Sets the Video Quality Mode of a discord voice channel - - @method setVideoQualityMode - @within Builders.ChannelBuilder - @return Builders.ChannelBuilder -]=] -function ChannelBuilder.Prototype.setVideoQualityMode(self: ChannelBuilder, videoQualityMode: number) - ChannelBuilder.Interface.VideoQualityMode:Assert(videoQualityMode) - - self.videoQualityMode = videoQualityMode - - return self -end - ---[=[ - Sets an overwrite for a Member, allowing you to define allowed permissions, alongside dissallowed permissions. - - @method addMemberOverwrite - @within Builders.ChannelBuilder - @return Builders.ChannelBuilder -]=] -function ChannelBuilder.Prototype.addMemberOverwrite( - self: ChannelBuilder, - roleId: string, - allowedPermissions: PermissionsBuilder.PermissionsBuilder, - deniedPermissions: PermissionsBuilder.PermissionsBuilder -) - table.insert(self.overwrites, { - id = roleId, - type = 1, - allow = allowedPermissions, - deny = deniedPermissions, - }) - - return self -end - ---[=[ - Sets an overwrite for a Role, allowing you to define allowed permissions, alongside dissallowed permissions. - - @method addRoleOverwrite - @within Builders.ChannelBuilder - @return Builders.ChannelBuilder -]=] -function ChannelBuilder.Prototype.addRoleOverwrite( - self: ChannelBuilder, - roleId: string, - allowedPermissions: PermissionsBuilder.PermissionsBuilder, - deniedPermissions: PermissionsBuilder.PermissionsBuilder -) - table.insert(self.overwrites, { - id = roleId, - type = 0, - allow = allowedPermissions, - deny = deniedPermissions, - }) - - return self -end - ---[=[ - Sets the default reaction for Forum and Media channels. - - @method setDefaultReaction - @within Builders.ChannelBuilder - @return Builders.ChannelBuilder -]=] -function ChannelBuilder.Prototype.setDefaultReaction(self: ChannelBuilder, emojiId: string, emojiName: string) - self.defaultReaction = { - emojiId = emojiId, - emojiName = emojiName, - } - - return self -end - ---[=[ - Sets the default sort order for Forum and Media channels. - - @method setDefaultSortOrder - @within Builders.ChannelBuilder - @return Builders.ChannelBuilder -]=] -function ChannelBuilder.Prototype.setDefaultSortOrder(self: ChannelBuilder, sortOrder: number) - ChannelBuilder.Interface.SortOrder:Assert(sortOrder) - - self.sortOrder = sortOrder - - return self -end - ---[=[ - Sets the Default layout for Forum channels. - - @method setDefaultForumLayout - @within Builders.ChannelBuilder - @return Builders.ChannelBuilder -]=] -function ChannelBuilder.Prototype.setDefaultForumLayout(self: ChannelBuilder, layoutOrder: number) - ChannelBuilder.Interface.ForumLayout:Assert(layoutOrder) - - self.forumLayout = layoutOrder - - return self -end - ---[=[ - Sets the name of the Discord Channel - - @method setPosition - @within Builders.ChannelBuilder - @return Builders.ChannelBuilder -]=] -function ChannelBuilder.Prototype.setName(self: ChannelBuilder, name: string) - self.name = name - - return self -end - ---[=[ - Sets the position of a Discord Channel, the position determines the position the channel appears in the channel hierarchy. - - @method setPosition - @within Builders.ChannelBuilder - @return Builders.ChannelBuilder -]=] -function ChannelBuilder.Prototype.setPosition(self: ChannelBuilder, position: number) - self.position = position - - return self -end - ---[=[ - Sets the topic of a Discord Channel. - - @method setTopic - @within Builders.ChannelBuilder - @return Builders.ChannelBuilder -]=] -function ChannelBuilder.Prototype.setTopic(self: ChannelBuilder, topic: string) - self.topic = topic - - return self -end - ---[=[ - Locks the channel to be NSFW only. - - @method setNSFW - @within Builders.ChannelBuilder - @return Builders.ChannelBuilder -]=] -function ChannelBuilder.Prototype.setNSFW(self: ChannelBuilder, nsfw: boolean) - self.isNsfw = nsfw - - return self -end - ---[=[ - Sets the rate limit per user for a discord channel. - - @method setRateLimitPerUser - @within Builders.ChannelBuilder - @return Builders.ChannelBuilder -]=] -function ChannelBuilder.Prototype.setRateLimitPerUser(self: ChannelBuilder, limit: number) - self.rateLimitPerUser = limit - - return self -end - ---[=[ - Sets the parent category of a discord channel. - - @method setParentCategoryId - @within Builders.ChannelBuilder - @return Builders.ChannelBuilder -]=] -function ChannelBuilder.Prototype.setParentCategoryId(self: ChannelBuilder, parentId: string) - self.parentCategoryId = parentId - - return self -end - ---[=[ - Sets the default archive duration for a discord channel. - - @method setDefaultAutoArchiveDuration - @within Builders.ChannelBuilder - @return Builders.ChannelBuilder -]=] -function ChannelBuilder.Prototype.setDefaultAutoArchiveDuration(self: ChannelBuilder, duration: number) - self.defaultAutoArchiveDuration = duration - - return self -end - ---[=[ - Sets the default thread limit for each user in a discord channel. - - @method setDefaultThreadRateLimitPerUser - @within Builders.ChannelBuilder - @return Builders.ChannelBuilder -]=] -function ChannelBuilder.Prototype.setDefaultThreadRateLimitPerUser(self: ChannelBuilder, limit: number) - self.threadRateLimitPerUser = limit - - return self -end - ---[=[ - Converts the activity to a JSON object that can be sent to the Discord API. - - @method toPayloadObject - @within Builders.ChannelBuilder - @return Network.Resolvable -]=] -function ChannelBuilder.Prototype.toPayloadObject(self: ChannelBuilder) - local overwrites = {} - - for _, object in self.overwrites do - table.insert(overwrites, { - id = object.id, - type = object.type, - allow = object.allow:getValue(), - deny = object.deny:getValue(), - }) - end - - return Resolvable.new(ResolvableType.JSON, { - name = self.name, - bitrate = self.bitrate, - type = self.channelType, - rtc_region = self.region, - video_quality_mode = self.videoQualityMode, - sort_order = self.sortOrder, - forum_layout = self.forumLayout, - position = self.position, - topic = self.topic, - is_nsfw = self.isNsfw, - rate_limit_per_user = self.rateLimitPerUser, - parent_id = self.parentCategoryId, - default_auto_archive_duration = self.defaultAutoArchiveDuration, - thread_rate_limit_per_user = self.threadRateLimitPerUser, - - permission_overwrites = #overwrites > 0 and overwrites or nil, - - default_reaction_emoji = self.defaultReaction and { - emoji_id = self.defaultReaction.emojiId, - emoji_name = self.defaultReaction.emojiName, - } or nil, - }) -end - ---[=[ - Creates a new instance of ChannelBuilder. - - @function new - @within Builders.ChannelBuilder - @return Builders.ChannelBuilder -- A new instance of ChannelBuilder. -]=] -function ChannelBuilder.Interface.new() - return (Construct({ - overwrites = {}, - }, ChannelBuilder.Prototype) :: unknown) :: ChannelBuilder -end - -export type ChannelBuilder = typeof(ChannelBuilder.Prototype) & { - name: string?, - bitrate: number?, - channelType: number?, - region: number?, - -- flags: number?, -- to-do: add channel flag support! - -- tags: { ? } -- to-do: add channel tags support! - videoQualityMode: number?, - sortOrder: number?, - forumLayout: number?, - position: number?, - topic: string?, - isNsfw: boolean?, - rateLimitPerUser: number?, - parentCategoryId: string?, - defaultAutoArchiveDuration: number?, - threadRateLimitPerUser: number?, - - defaultReaction: { - emojiId: string, - emojiName: string, - }?, - - overwrites: { - { - id: string, - type: number, - allow: PermissionsBuilder.PermissionsBuilder, - deny: PermissionsBuilder.PermissionsBuilder, - } - }, -} - -return ChannelBuilder.Interface diff --git a/Package/Classes/Builders/CommandBuilder.luau b/Package/Classes/Builders/CommandBuilder.luau deleted file mode 100644 index 446c226..0000000 --- a/Package/Classes/Builders/CommandBuilder.luau +++ /dev/null @@ -1,291 +0,0 @@ -local Enumerate = require("@Utils/Enumerate") -local Construct = require("@Utils/Construct") - -local Resolvable = require("@Network/Resolvable") - -local CommandOptionBuilder = require("@Builders/CommandOptionBuilder") -local PermissionsBuilder = require("@Builders/PermissionsBuilder") - -local ResolvableType = require("@Enums/ResolvableType") - ---[=[ - @class Builders.CommandBuilder - - CommandBuilder is used to construct a command for a Discord application, including type, name, description, and options. - - Usage: - ```lua - local command = CommandBuilder.new() - :setType(CommandBuilder.Type.ChatInput) - :setName("example_command") - :setDescription("This is an example command.") - :setNSFW(false) - :setDMPermission(true) - :setDefaultPermissionEnabled(true) - :addOption(CommandOptionBuilder.new():setType(CommandOptionBuilder.Type.String):setName("option1"):setDescription("An option")) - ``` -]=] -local CommandBuilder = {} - -CommandBuilder.Interface = {} -CommandBuilder.Prototype = {} - -CommandBuilder.Prototype.type = "CommandBuilder" - ---[=[ - @prop Type table - @within Builders.CommandBuilder - - An enumeration of command types. - - - ChatInput: 1 - - UserContextAction: 2 - - MessageContextAction: 3 -]=] -CommandBuilder.Interface.Type = Enumerate.new({ - ChatInput = 1, - UserContextAction = 2, - MessageContextAction = 3, -}) - ---[=[ - @prop Context table - @within Builders.CommandBuilder - - An enumeration of command contexts. - - - Guild: 0 - - BotDM: 1 - - PrivateChannel: 2 -]=] -CommandBuilder.Interface.Context = Enumerate.new({ - Guild = 0, - BotDM = 1, - PrivateChannel = 2, -}) - ---[=[ - @prop IntegrationType table - @within Builders.CommandBuilder - - An enumeration of command integration contexts. - - - GuildCommand: 0 - - UserCommand: 1 -]=] -CommandBuilder.Interface.IntegrationType = Enumerate.new({ - GuildCommand = 0, - UserCommand = 1, -}) - ---[=[ - Sets the type of the command. - - @method setType - @param commandType number -- The type of the command. - @within Builders.CommandBuilder - @return Builders.CommandBuilder -- Returns the CommandBuilder instance for method chaining. -]=] -function CommandBuilder.Prototype.setType(self: CommandBuilder, commandType: number) - CommandBuilder.Interface.Type:Assert(commandType) - - self.commandType = commandType - - return self -end - ---[=[ - Sets the localization code for the command. - - @method setLocalization - @param localizationCode string -- The localization code. - @within Builders.CommandBuilder - @return Builders.CommandBuilder -- Returns the CommandBuilder instance for method chaining. -]=] -function CommandBuilder.Prototype.setLocalization(self: CommandBuilder, localizationCode: string) - self.commandLocalization = localizationCode - - return self -end - ---[=[ - Sets the description of the command. - - @method setDescription - @param description string -- The description of the command. - @within Builders.CommandBuilder - @return Builders.CommandBuilder -- Returns the CommandBuilder instance for method chaining. -]=] -function CommandBuilder.Prototype.setDescription(self: CommandBuilder, description: string) - self.commandDescription = description - - return self -end - ---[=[ - Sets the name of the command. - - @method setName - @param name string -- The name of the command. - @within Builders.CommandBuilder - @return Builders.CommandBuilder -- Returns the CommandBuilder instance for method chaining. -]=] -function CommandBuilder.Prototype.setName(self: CommandBuilder, name: string) - self.commandName = name - - return self -end - ---[=[ - - Sets whether the command is NSFW. - - @method setNSFW - @param isNSFW boolean -- Whether the command is NSFW. - @within Builders.CommandBuilder - @return Builders.CommandBuilder -- Returns the CommandBuilder instance for method chaining. -]=] -function CommandBuilder.Prototype.setNSFW(self: CommandBuilder, isNSFW: boolean) - self.commandNSFW = isNSFW - - return self -end - ---[=[ - Sets the required permissions for the command in a guild. - - @method setGuildPermissions - @param permissionObject PermissionsBuilder -- The permissions required for the command. - @within Builders.CommandBuilder - @return Builders.CommandBuilder -- Returns the CommandBuilder instance for method chaining. -]=] -function CommandBuilder.Prototype.setGuildPermissions( - self: CommandBuilder, - permissionObject: PermissionsBuilder.PermissionsBuilder -) - self.commandPermissions = permissionObject - - return self -end - ---[=[ - Adds an option to the command. - - @method addOption - @param commandObject CommandOptionBuilder -- The option to add. - @within Builders.CommandBuilder - @return Builders.CommandBuilder -- Returns the CommandBuilder instance for method chaining. -]=] -function CommandBuilder.Prototype.addOption( - self: CommandBuilder, - commandObject: CommandOptionBuilder.CommandOptionBuilder -) - table.insert(self.options, commandObject) - - return self -end - ---[=[ - Adds a context to the command. - - @method addContext - @param context number -- The context to add. - @within Builders.CommandBuilder - @return Builders.CommandBuilder -- Returns the CommandBuilder instance for method chaining. -]=] -function CommandBuilder.Prototype.addContext(self: CommandBuilder, context: number) - table.insert(self.contexts, context) - - return self -end - ---[=[ - Adds an intergration to the command. - - @method addIntegration - @param integration IntegrationType - @within Builders.CommandBuilder - @return Builders.CommandBuilder -- Returns the CommandBuilder instance for method chaining. -]=] -function CommandBuilder.Prototype.addIntegration(self: CommandBuilder, integration: number) - table.insert(self.integrations, integration) - - return self -end - ---[=[ - Converts the command to a JSON object that can be sent to the Discord API. - - @method toPayloadObject - @within Builders.CommandBuilder - @return Network.Resolvable -]=] -function CommandBuilder.Prototype.toPayloadObject(self: CommandBuilder) - local permissions = "0" - local options = {} - - for _, commandOption in self.options do - table.insert(options, (commandOption:toPayloadObject() :: any):resolve()) - end - - if self.commandPermissions then - permissions = self.commandPermissions:getValue() - end - - return Resolvable.new(ResolvableType.JSON, { - type = self.commandType, - name = self.commandName, - description = self.commandDescription, - - nsfw = self.commandNSFW, - - default_permission = self.defaultPermissionEnabled, - default_member_permissions = permissions, - dm_permission = self.commandDM, - - integration_types = self.integrations, - contexts = self.contexts, - - name_localizations = self.commandLocalization, - description_localizations = self.commandLocalization, - - options = (#options > 0 and options) or nil, - }) -end - ---[=[ - Creates a new instance of CommandBuilder. - - @function new - @within Builders.CommandBuilder - @return Builders.CommandBuilder -- A new instance of CommandBuilder. -]=] -function CommandBuilder.Interface.new() - return ( - Construct({ - choices = {}, - options = {}, - contexts = {}, - integrations = {}, - }, CommandBuilder.Prototype) :: unknown - ) :: CommandBuilder -end - -export type CommandBuilder = typeof(CommandBuilder.Prototype) & { - commandLocalization: string, - commandDM: boolean, - - commandDescription: string, - commandName: string, - commandType: number, - commandNSFW: boolean, - - defaultPermissionEnabled: boolean?, - - contexts: { number }, - integrations: { number }, - commandPermissions: PermissionsBuilder.PermissionsBuilder, - options: { CommandOptionBuilder.CommandOptionBuilder }, -} - -return CommandBuilder.Interface diff --git a/Package/Classes/Builders/CommandOptionBuilder.luau b/Package/Classes/Builders/CommandOptionBuilder.luau deleted file mode 100644 index 9c8986d..0000000 --- a/Package/Classes/Builders/CommandOptionBuilder.luau +++ /dev/null @@ -1,373 +0,0 @@ -local Enumerate = require("@Utils/Enumerate") -local Construct = require("@Utils/Construct") - -local Resolvable = require("@Network/Resolvable") - -local ResolvableType = require("@Enums/ResolvableType") - ---[=[ - @class Builders.CommandOptionBuilder - - CommandOptionBuilder is used to construct options for a Discord application command, including type, name, description, and choices. - - Usage: - ```lua - local option = CommandOptionBuilder.new() - :setType(CommandOptionBuilder.Type.String) - :setName("example_option") - :setDescription("This is an example option.") - :setRequired(true) - :addChoice("Choice 1", "value1") - :addChoice("Choice 2", "value2") - ``` -]=] -local CommandOptionBuilder = {} - -CommandOptionBuilder.Interface = {} -CommandOptionBuilder.Prototype = {} - -CommandOptionBuilder.Prototype.type = "CommandOptionBuilder" - ---[=[ - @prop Type table - @within Builders.CommandOptionBuilder - - An enumeration of option types. - - - SubCommand: 1 - - SubCommandGroup: 2 - - String: 3 - - Integer: 4 - - Boolean: 5 - - User: 6 - - Channel: 7 - - Role: 8 - - Mentionable: 9 - - Number: 10 - - Attachment: 11 -]=] -CommandOptionBuilder.Interface.Type = Enumerate.new({ - SubCommand = 1, - SubCommandGroup = 2, - String = 3, - Integer = 4, - Boolean = 5, - User = 6, - Channel = 7, - Role = 8, - Mentionable = 9, - Number = 10, - Attachment = 11, -}) - ---[=[ - @prop ChannelType table - @within Builders.CommandOptionBuilder - - An enumeration of channel types. - - - GuildText: 0 - - DirectMessage: 1 - - GuildVoice: 2 - - GroupDirectMessage: 3 - - GuildCategory: 4 - - GuildAnnouncement: 5 - - AnnouncementThread: 10 - - PublicThread: 11 - - PrivateThread: 12 - - GuildStageVoice: 13 - - GuildDirectory: 14 - - GuildForum: 15 -]=] -CommandOptionBuilder.Interface.ChannelType = Enumerate.new({ - GuildText = 0, - DirectMessage = 1, - GuildVoice = 2, - GroupDirectMessage = 3, - GuildCategory = 4, - GuildAnnouncement = 5, - AnnouncementThread = 10, - PublicThread = 11, - PrivateThread = 12, - GuildStageVoice = 13, - GuildDirectory = 14, - GuildForum = 15, -}) - ---[=[ - Sets the type of the option. - - @method setType - @param optionType number -- The type of the option. - @within Builders.CommandOptionBuilder - @return Builders.CommandOptionBuilder -- Returns the CommandOptionBuilder instance for method chaining. -]=] -function CommandOptionBuilder.Prototype.setType(self: CommandOptionBuilder, optionType: number) - CommandOptionBuilder.Interface.Type:Assert(optionType) - - self.optionType = optionType - - return self -end - ---[=[ - Sets the name of the option. - - @method setName - @param optionName string -- The name of the option. - @within Builders.CommandOptionBuilder - @return Builders.CommandOptionBuilder -- Returns the CommandOptionBuilder instance for method chaining. -]=] -function CommandOptionBuilder.Prototype.setName(self: CommandOptionBuilder, optionName: string) - self.optionName = optionName - - return self -end - ---[=[ - Sets the localization code for the option. - - @method setLocalization - @param localizationCode string -- The localization code. - @within Builders.CommandOptionBuilder - @return Builders.CommandOptionBuilder -- Returns the CommandOptionBuilder instance for method chaining. -]=] -function CommandOptionBuilder.Prototype.setLocalization(self: CommandOptionBuilder, localizationCode: string) - self.optionLocalization = localizationCode - - return self -end - ---[=[ - Sets the description of the option. - - @method setDescription - @param optionDescription string -- The description of the option. - @within Builders.CommandOptionBuilder - @return Builders.CommandOptionBuilder -- Returns the CommandOptionBuilder instance for method chaining. -]=] -function CommandOptionBuilder.Prototype.setDescription(self: CommandOptionBuilder, optionDescription: string) - self.optionDescription = optionDescription - - return self -end - ---[=[ - Sets whether the option is required. - - @method setRequired - @param isRequired boolean -- Whether the option is required. - @within Builders.CommandOptionBuilder - @return Builders.CommandOptionBuilder -- Returns the CommandOptionBuilder instance for method chaining. -]=] -function CommandOptionBuilder.Prototype.setRequired(self: CommandOptionBuilder, isRequired: boolean) - self.optionRequired = isRequired - - return self -end - ---[=[ - Sets the channel types for the option. - - @method setChannelTypes - @param channelTypes ... -- The channel types. - @within Builders.CommandOptionBuilder - @return Builders.CommandOptionBuilder -- Returns the CommandOptionBuilder instance for method chaining. -]=] -function CommandOptionBuilder.Prototype.setChannelTypes(self: CommandOptionBuilder, ...) - for _, channelType in { ... } do - CommandOptionBuilder.Interface.ChannelType:Assert(channelType) - end - - self.optionChannelTypes = { ... } - - return self -end - ---[=[ - Sets the minimum value for the option. - - @method setMinValue - @param minValue number -- The minimum value. - @within Builders.CommandOptionBuilder - @return Builders.CommandOptionBuilder -- Returns the CommandOptionBuilder instance for method chaining. -]=] -function CommandOptionBuilder.Prototype.setMinValue(self: CommandOptionBuilder, minValue: number) - self.optionMinValue = minValue - - return self -end - ---[=[ - Sets the maximum value for the option. - - @method setMaxValue - @param maxValue number -- The maximum value. - @within Builders.CommandOptionBuilder - @return Builders.CommandOptionBuilder -- Returns the CommandOptionBuilder instance for method chaining. -]=] -function CommandOptionBuilder.Prototype.setMaxValue(self: CommandOptionBuilder, maxValue: number) - self.optionMaxValue = maxValue - - return self -end - ---[=[ - Sets the minimum length for the option. - - @method setMinLength - @param minLength number -- The minimum length. - @within Builders.CommandOptionBuilder - @return Builders.CommandOptionBuilder -- Returns the CommandOptionBuilder instance for method chaining. -]=] -function CommandOptionBuilder.Prototype.setMinLength(self: CommandOptionBuilder, minLength: number) - self.optionMinLength = minLength - - return self -end - ---[=[ - Sets the maximum length for the option. - - @method setMaxLength - @param maxLength number -- The maximum length. - @within Builders.CommandOptionBuilder - @return Builders.CommandOptionBuilder -- Returns the CommandOptionBuilder instance for method chaining. -]=] -function CommandOptionBuilder.Prototype.setMaxLength(self: CommandOptionBuilder, maxLength: number) - self.optionMaxLength = maxLength - - return self -end - ---[=[ - Sets whether autocomplete is enabled for the option. - - @method setAutocompleteEnabled - @param autocomplete boolean -- Whether autocomplete is enabled. - @within Builders.CommandOptionBuilder - @return Builders.CommandOptionBuilder -- Returns the CommandOptionBuilder instance for method chaining. -]=] -function CommandOptionBuilder.Prototype.setAutocompleteEnabled(self: CommandOptionBuilder, autocomplete: boolean) - self.optionAutocomplete = autocomplete - - return self -end - ---[=[ - Adds a sub-option to the option. - - @method addOption - @param commandOption CommandOptionBuilder -- The sub-option to add. - @within Builders.CommandOptionBuilder - @return Builders.CommandOptionBuilder -- Returns the CommandOptionBuilder instance for method chaining. -]=] -function CommandOptionBuilder.Prototype.addOption(self: CommandOptionBuilder, commandOption: CommandOptionBuilder) - table.insert(self.options, commandOption) - - return self -end - ---[=[ - Adds a choice to the option. - - @method addChoice - @param choiceName string -- The name of the choice. - @param choiceValue any -- The value of the choice. - @within Builders.CommandOptionBuilder - @return Builders.CommandOptionBuilder -- Returns the CommandOptionBuilder instance for method chaining. -]=] -function CommandOptionBuilder.Prototype.addChoice(self: CommandOptionBuilder, choiceName: string, choiceValue: any) - self.choices[choiceName] = choiceValue - - return self -end - ---[=[ - Converts the option to a JSON object that can be sent to the Discord API. - - @method toPayloadObject - @within Builders.CommandOptionBuilder - @return Network.Resolvable -]=] -function CommandOptionBuilder.Prototype.toPayloadObject(self: CommandOptionBuilder) - local options = {} - local choices = {} - - for _, commandOption in self.options do - table.insert(options, commandOption:toPayloadObject():resolve()) - end - - for choiceName, choiceValue in self.choices do - table.insert(choices, { - name = choiceName, - value = choiceValue, - name_localizations = self.optionLocalization, - }) - end - - return Resolvable.new(ResolvableType.JSON, { - type = self.optionType, - name = self.optionName, - description = self.optionDescription, - - required = self.optionRequired, - - name_localizations = self.optionLocalization, - description_localizations = self.optionLocalization, - - options = options, - choices = choices, - - channel_types = self.optionChannelTypes, - - min_value = self.optionMinValue, - max_value = self.optionMaxValue, - - min_length = self.optionMinLength, - max_length = self.optionMaxLength, - - autocomplete = self.optionAutocomplete, - }) -end - ---[=[ - Creates a new instance of CommandOptionBuilder. - - @function new - @within Builders.CommandOptionBuilder - @return Builders.CommandOptionBuilder -- A new instance of CommandOptionBuilder. -]=] -function CommandOptionBuilder.Interface.new() - return ( - Construct({ - choices = {}, - options = {}, - }, CommandOptionBuilder.Prototype) :: unknown - ) :: CommandOptionBuilder -end - -export type CommandOptionBuilder = typeof(CommandOptionBuilder.Prototype) & { - optionAutocomplete: boolean, - - optionMaxLength: number, - optionMinLength: number, - - optionMaxValue: number, - optionMinValue: number, - - optionLocalization: string, - optionRequired: boolean, - - optionDescription: string, - optionName: string, - - optionType: number, - - optionChannelTypes: { number }, - - choices: { [string]: string }, - options: { CommandOptionBuilder }, -} - -return CommandOptionBuilder.Interface diff --git a/Package/Classes/Builders/EmbedBuilder.luau b/Package/Classes/Builders/EmbedBuilder.luau deleted file mode 100644 index 82b8ee9..0000000 --- a/Package/Classes/Builders/EmbedBuilder.luau +++ /dev/null @@ -1,439 +0,0 @@ -local Datetime = require("@Std/Datetime") - -local Construct = require("@Utils/Construct") -local Enumerate = require("@Utils/Enumerate") - -local Resolvable = require("@Network/Resolvable") - -local ResolvableType = require("@Enums/ResolvableType") - ---[=[ - @class Builders.EmbedBuilder - - EmbedBuilder is used to construct an embed object for a Discord message. - - Usage: - ```lua - local embed = EmbedBuilder.new() - :setTitle("Embed Title") - :setDescription("This is an embed description.") - :setColor(0x00FF00) - :addField({name = "Field 1", value = "Value 1", inline = true}) - ``` -]=] -local EmbedBuilder = {} - -EmbedBuilder.Interface = {} -EmbedBuilder.Prototype = {} - -EmbedBuilder.Prototype.type = "EmbedBuilder" - ---[=[ - @prop Type table - @within Builders.EmbedBuilder - - An enumeration of embed types. - - - Rich: "rich" - - Image: "image" - - Video: "video" - - Gif: "gifv" - - Article: "article" - - Link: "link" -]=] -EmbedBuilder.Interface.Type = Enumerate.new({ - Rich = "rich", - Image = "image", - Video = "video", - Gif = "gifv", - Article = "article", - Link = "link", -}) - ---[=[ - Sets the title of the embed. - - @method setTitle - @param title string -- The title of the embed. - @within Builders.EmbedBuilder - @return Builders.EmbedBuilder -- Returns the EmbedBuilder instance for method chaining. -]=] -function EmbedBuilder.Prototype.setTitle(self: EmbedBuilder, title: string) - self.title = title - - return self -end - ---[=[ - Sets the type of the embed. - - @method setType - @param type string -- The type of the embed. - @within Builders.EmbedBuilder - @return Builders.EmbedBuilder -- Returns the EmbedBuilder instance for method chaining. -]=] -function EmbedBuilder.Prototype.setType(self: EmbedBuilder, type: string) - self.type = type - - return self -end - ---[=[ - Sets the description of the embed. - - @method setDescription - @param description string -- The description of the embed. - @within Builders.EmbedBuilder - @return Builders.EmbedBuilder -- Returns the EmbedBuilder instance for method chaining. -]=] -function EmbedBuilder.Prototype.setDescription(self: EmbedBuilder, description: string) - self.description = description - - return self -end - ---[=[ - Sets the URL of the embed. - - @method setUrl - @param url string -- The URL of the embed. - @within Builders.EmbedBuilder - @return Builders.EmbedBuilder -- Returns the EmbedBuilder instance for method chaining. -]=] -function EmbedBuilder.Prototype.setUrl(self: EmbedBuilder, url: string) - self.url = url - - return self -end - ---[=[ - Sets the timestamp of the embed. - - @method setTimestamp - @param epoch number -- The timestamp of the embed in epoch time. - @within Builders.EmbedBuilder - @return Builders.EmbedBuilder -- Returns the EmbedBuilder instance for method chaining. -]=] -function EmbedBuilder.Prototype.setTimestamp(self: EmbedBuilder, epoch: number) - self.timestamp = Datetime.fromUniversalTime(epoch :: any):toIsoDate() - - return self -end - -type HexLike = string | number -type RGB = { number } | { r: number, g: number, b: number } ---[=[ - Sets the color of the embed. - - @method setColor - @param colorHex HexLike | RGB -- The color of the embed in as a hex or a RGB. - @within Builders.EmbedBuilder - @return Builders.EmbedBuilder -- Returns the EmbedBuilder instance for method chaining. -]=] -function EmbedBuilder.Prototype.setColor(self: EmbedBuilder, color: HexLike | RGB) - local colorHex: number - - if typeof(color) == "number" then - colorHex = color - end - - if typeof(color) == "string" then - local HEX_PAT = "#(.%x*)" - colorHex = tonumber(string.match(color, HEX_PAT), 16) or error("Invalid hex string provided") - end - - if typeof(color) == "table" then - -- FIXME: Maybe don't use any here - local anyColor = color :: any - local rgbColor = { - r = anyColor.r or anyColor[1], - g = anyColor.g or anyColor[2], - b = anyColor.b or anyColor[3], - } - - if rgbColor.r and rgbColor.g and rgbColor.b then - local function toHex(num: number): string - local hex = string.format("%x", num) - if #hex == 1 then - hex = "0" .. hex - end - - return hex - end - - return EmbedBuilder.Prototype.setColor( - self, - "#" .. toHex(rgbColor.r) .. toHex(rgbColor.g) .. toHex(rgbColor.b) - ) - else - error("Not all required RGB color components provided") - end - end - - self.color = colorHex - - return self -end - ---[=[ - Sets the footer of the embed. - - @method setFooter - @param footerSettings { text: string, icon: string } -- The settings for the footer. - @within Builders.EmbedBuilder - @return Builders.EmbedBuilder -- Returns the EmbedBuilder instance for method chaining. -]=] -function EmbedBuilder.Prototype.setFooter( - self: EmbedBuilder, - footerSettings: { - text: string, - icon: string, - } -) - self.footer = footerSettings - - return self -end - ---[=[ - Sets the image of the embed. - - @method setImage - @param imageSettings { imageUrl: string, height: number, width: number } -- The settings for the image. - @within Builders.EmbedBuilder - @return Builders.EmbedBuilder -- Returns the EmbedBuilder instance for method chaining. -]=] -function EmbedBuilder.Prototype.setImage( - self: EmbedBuilder, - imageSettings: { - imageUrl: string, - height: number, - width: number, - } -) - self.image = imageSettings - - return self -end - ---[=[ - Sets the thumbnail of the embed. - - @method setThumbnail - @param thumbnailSettings { thumbnailUrl: string, height: number, width: number } -- The settings for the thumbnail. - @within Builders.EmbedBuilder - @return Builders.EmbedBuilder -- Returns the EmbedBuilder instance for method chaining. -]=] -function EmbedBuilder.Prototype.setThumbnail( - self: EmbedBuilder, - thumbnailSettings: { - thumbnailUrl: string, - height: number, - width: number, - } -) - self.thumbnail = thumbnailSettings - - return self -end - ---[=[ - Sets the video of the embed. - - @method setVideo - @param videoSettings { videoUrl: string, height: number, width: number } -- The settings for the video. - @within Builders.EmbedBuilder - @return Builders.EmbedBuilder -- Returns the EmbedBuilder instance for method chaining. -]=] -function EmbedBuilder.Prototype.setVideo( - self: EmbedBuilder, - videoSettings: { - videoUrl: string, - height: number, - width: number, - } -) - self.video = videoSettings - - return self -end - ---[=[ - Sets the provider of the embed. - - @method setProvider - @param providerSettings { name: string, url: string } -- The settings for the provider. - @within Builders.EmbedBuilder - @return Builders.EmbedBuilder -- Returns the EmbedBuilder instance for method chaining. -]=] -function EmbedBuilder.Prototype.setProvider( - self: EmbedBuilder, - providerSettings: { - name: string, - url: string, - } -) - self.provider = providerSettings - - return self -end - ---[=[ - Sets the author of the embed. - - @method setAuthor - @param authorSettings { name: string, url: string, iconUrl: string } -- The settings for the author. - @within Builders.EmbedBuilder - @return Builders.EmbedBuilder -- Returns the EmbedBuilder instance for method chaining. -]=] -function EmbedBuilder.Prototype.setAuthor( - self: EmbedBuilder, - authorSettings: { - iconUrl: string, - name: string, - url: string, - } -) - self.author = authorSettings - - return self -end - ---[=[ - Adds a field to the embed. - - @method addField - @param fieldSettings { name: string, value: string, inline: boolean? } -- The settings for the field. - @within Builders.EmbedBuilder - @return Builders.EmbedBuilder -- Returns the EmbedBuilder instance for method chaining. -]=] -function EmbedBuilder.Prototype.addField( - self: EmbedBuilder, - fieldSettings: { - name: string, - value: string, - inline: boolean?, - } -) - table.insert(self.fields, fieldSettings) - - return self -end - ---[=[ - Converts the embed to a JSON object that can be sent to the Discord API. - - @method toPayloadObject - @within Builders.EmbedBuilder - @return Network.Resolvable -]=] -function EmbedBuilder.Prototype.toPayloadObject(self: EmbedBuilder) - local fields = {} - - for _, fieldObject in self.fields do - table.insert(fields, { - name = fieldObject.name, - value = fieldObject.value, - inline = fieldObject.inline, - }) - end - - return Resolvable.new(ResolvableType.JSON, { - fields = #fields ~= 0 and fields or nil, - - title = self.title, - type = self.type, - description = self.description, - url = self.url, - timestamp = self.timestamp, - color = self.color, - - footer = self.footer and { - text = self.footer.text, - icon_url = self.footer.icon, - } or nil, - image = self.image and { - url = self.image.imageUrl, - height = self.image.height, - width = self.image.width, - } or nil, - thumbnail = self.thumbnail and { - url = self.thumbnail.thumbnailUrl, - height = self.thumbnail.height, - width = self.thumbnail.width, - } or nil, - video = self.video and { - url = self.video.videoUrl, - height = self.video.height, - width = self.video.width, - } or nil, - provider = self.provider and { - name = self.provider.name, - url = self.provider.url, - } or nil, - author = self.author and { - name = self.author.name, - url = self.author.url, - icon_url = self.author.iconUrl, - } or nil, - }) -end - ---[=[ - Creates a new instance of EmbedBuilder. - - @function new - @within Builders.EmbedBuilder - @return Builders.EmbedBuilder -- A new instance of EmbedBuilder. -]=] -function EmbedBuilder.Interface.new() - return (Construct({ - fields = {}, - }, EmbedBuilder.Prototype) :: unknown) :: EmbedBuilder -end - -export type EmbedBuilder = typeof(EmbedBuilder.Prototype) & { - fields: { - { - name: string, - value: string, - inline: boolean?, - } - }, - title: string?, - type: string?, - description: string?, - url: string?, - timestamp: string?, - color: number?, - footer: { - text: string, - icon: string, - }?, - image: { - imageUrl: string, - height: number, - width: number, - }?, - thumbnail: { - thumbnailUrl: string, - height: number, - width: number, - }?, - video: { - videoUrl: string, - height: number, - width: number, - }?, - provider: { - name: string, - url: string, - }?, - author: { - iconUrl: string, - name: string, - url: string, - }?, -} - -return EmbedBuilder.Interface diff --git a/Package/Classes/Builders/GuildBuilder.luau b/Package/Classes/Builders/GuildBuilder.luau deleted file mode 100644 index 4c75061..0000000 --- a/Package/Classes/Builders/GuildBuilder.luau +++ /dev/null @@ -1,558 +0,0 @@ -local Enumerate = require("@Utils/Enumerate") -local Construct = require("@Utils/Construct") - -local Resolvable = require("@Network/Resolvable") - -local GuildRoleBuilder = require("@Builders/GuildRoleBuilder") -local ChannelBuilder = require("@Builders/ChannelBuilder") - -local ResolvableType = require("@Enums/ResolvableType") - ---[=[ - @class Builders.GuildBuilder -]=] -local GuildBuilder = {} - -GuildBuilder.Interface = {} -GuildBuilder.Prototype = {} - -GuildBuilder.Prototype.type = "GuildBuilder" - ---[=[ - @prop MFALevel table - @within Builders.GuildBuilder - - An enumeration of MFA levels. - - - None: 0 - - Elevated: 1 -]=] -GuildBuilder.Interface.MFALevel = Enumerate.new({ - None = 0, - Elevated = 1, -}) - ---[=[ - @prop VerificationLevel table - @within Builders.GuildBuilder - - An enumeration of filter levels. - - - None: 0 - - Low: 1 - - Medium: 2 - - High: 2 - - VeryHigh: 2 -]=] -GuildBuilder.Interface.VerificationLevel = Enumerate.new({ - None = 0, - Low = 1, - Medium = 2, - High = 3, - VeryHigh = 4, -}) - ---[=[ - @prop NotificationLevel table - @within Builders.GuildBuilder - - An enumeration of filter levels. - - - AllMessages: 0 - - OnlyMentions: 1 -]=] -GuildBuilder.Interface.NotificationLevel = Enumerate.new({ - AllMessages = 0, - OnlyMentions = 1, -}) - ---[=[ - @prop ExplicitContentFilterLevel table - @within Builders.GuildBuilder - - An enumeration of filter levels. - - - Disabled: 0 - - MembersWithoutRoles: 1 - - AllMembers: 2 -]=] -GuildBuilder.Interface.ExplicitContentFilterLevel = Enumerate.new({ - Disabled = 0, - MembersWithoutRoles = 1, - AllMembers = 2, -}) - ---[=[ - @prop GuildFeature table - @within Builders.GuildBuilder - - An enumeration of guild features. - - - AnimatedBanner: "ANIMATED_BANNER" - - AnimatedIcon: "ANIMATED_ICON" - - ApplicationCommandPermissionsV2: "APPLICATION_COMMAND_PERMISSIONS_V2" - - AutoModeration: "AUTO_MODERATION" - - Banner: "BANNER" - - Community: "COMMUNITY" - - CreatorMonetizableProvisional: "CREATOR_MONETIZABLE_PROVISIONAL" - - CreatorStorePage: "CREATOR_STORE_PAGE" - - DeveloperSupportServer: "DEVELOPER_SUPPORT_SERVER" - - Discoverable: "DISCOVERABLE" - - Featurable: "FEATURABLE" - - InvitesDisabled: "INVITES_DISABLED" - - InviteSplash: "INVITE_SPLASH" - - MemberVerificationGateEnabled: "MEMBER_VERIFICATION_GATE_ENABLED" - - MoreStickers: "MORE_STICKERS" - - News: "NEWS" - - Partnered: "PARTNERED" - - PreviewEnabled: "PREVIEW_ENABLED" - - RaidAlertsDisabled: "RAID_ALERTS_DISABLED" - - RoleIcons: "ROLE_ICONS" - - RoleSubscriptionsAvailableForPurchase: "ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE" - - RoleSubscriptionsEnabled: "ROLE_SUBSCRIPTIONS_ENABLED" - - TicketedEventsEnabled: "TICKETED_EVENTS_ENABLED" - - VanityUrl: "VANITY_URL" - - Verified: "VERIFIED" - - VipRegions: "VIP_REGIONS" - - WelcomeScreenEnabled: "WELCOME_SCREEN_ENABLED" -]=] -GuildBuilder.Interface.GuildFeature = Enumerate.new({ - AnimatedBanner = "ANIMATED_BANNER", - AnimatedIcon = "ANIMATED_ICON", - ApplicationCommandPermissionsV2 = "APPLICATION_COMMAND_PERMISSIONS_V2", - AutoModeration = "AUTO_MODERATION", - Banner = "BANNER", - Community = "COMMUNITY", - CreatorMonetizableProvisional = "CREATOR_MONETIZABLE_PROVISIONAL", - CreatorStorePage = "CREATOR_STORE_PAGE", - DeveloperSupportServer = "DEVELOPER_SUPPORT_SERVER", - Discoverable = "DISCOVERABLE", - Featurable = "FEATURABLE", - InvitesDisabled = "INVITES_DISABLED", - InviteSplash = "INVITE_SPLASH", - MemberVerificationGateEnabled = "MEMBER_VERIFICATION_GATE_ENABLED", - MoreStickers = "MORE_STICKERS", - News = "NEWS", - Partnered = "PARTNERED", - PreviewEnabled = "PREVIEW_ENABLED", - RaidAlertsDisabled = "RAID_ALERTS_DISABLED", - RoleIcons = "ROLE_ICONS", - RoleSubscriptionsAvailableForPurchase = "ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE", - RoleSubscriptionsEnabled = "ROLE_SUBSCRIPTIONS_ENABLED", - TicketedEventsEnabled = "TICKETED_EVENTS_ENABLED", - VanityUrl = "VANITY_URL", - Verified = "VERIFIED", - VipRegions = "VIP_REGIONS", - WelcomeScreenEnabled = "WELCOME_SCREEN_ENABLED", -}) - ---[=[ - Sets the name of this discord guild. - - @method setName - @param name string - @within Builders.GuildBuilder - @return Objects.GuildBuilder -]=] -function GuildBuilder.Prototype.setName(self: GuildBuilder, name: string) - self.name = name - - return self -end - ---[=[ - Sets the verification level for this guild. - - @method setVerificationLevel - @param verificationLevel number - @within Builders.GuildBuilder - @return Objects.GuildBuilder -]=] -function GuildBuilder.Prototype.setVerificationLevel(self: GuildBuilder, verificationLevel: number) - GuildBuilder.Interface.VerificationLevel:Assert(verificationLevel) - - self.verificationLevel = verificationLevel - - return self -end - ---[=[ - Sets the default message notification level for this guild. - - @method setDefaultMessageNotificationLevel - @param notificationLevel number - @within Builders.GuildBuilder - @return Objects.GuildBuilder -]=] -function GuildBuilder.Prototype.setDefaultMessageNotificationLevel(self: GuildBuilder, notificationLevel: number) - GuildBuilder.Interface.NotificationLevel:Assert(notificationLevel) - - self.defaultMessageNotifications = notificationLevel - - return self -end - ---[=[ - Sets the explicit content filter level for this guild. - - @method setExplicitContentFilterLevel - @param notificationLevel number - @within Builders.GuildBuilder - @return Objects.GuildBuilder -]=] -function GuildBuilder.Prototype.setExplicitContentFilterLevel(self: GuildBuilder, contentFilterLevel: number) - GuildBuilder.Interface.ExplicitContentFilterLevel:Assert(contentFilterLevel) - - self.explicitContentFilter = contentFilterLevel - - return self -end - ---[=[ - Sets the discord Afk Voice Channel for this guild. - - @method setAfkChannelId - @param channelId string - @within Builders.GuildBuilder - @return Objects.GuildBuilder -]=] -function GuildBuilder.Prototype.setAfkChannelId(self: GuildBuilder, channelId: string) - self.afkChannelId = channelId - - return self -end - ---[=[ - Sets the timeout for members in discord voice channels. Valid inputs are: - - - 60 - - 300 - - 900 - - 1800 - - 3600 - - @method setAfkTimeout - @param timeout number - @within Builders.GuildBuilder - @return Objects.GuildBuilder -]=] -function GuildBuilder.Prototype.setAfkTimeout(self: GuildBuilder, timeout: number) - self.afkTimeout = timeout - - return self -end - ---[=[ - Sets the icon of this guild. imageData must be a base64, 1024x1024, encoded binary data for either a png/jpeg/gif - - @method setIcon - @param imageData string - @within Builders.GuildBuilder - @return Objects.GuildBuilder -]=] -function GuildBuilder.Prototype.setIcon(self: GuildBuilder, imageData: string) - self.icon = imageData - - return self -end - ---[=[ - Sets the owner of this guild. - - @method setOwnerId - @param ownerId string - @within Builders.GuildBuilder - @return Objects.GuildBuilder -]=] -function GuildBuilder.Prototype.setOwnerId(self: GuildBuilder, ownerId: string) - self.ownerId = ownerId - - return self -end - ---[=[ - Sets the splash for this guild. imageData must be a 16:9, base64, encoded binary data for either a png/jpeg - - @method setSplash - @param imageData string - @within Builders.GuildBuilder - @return Objects.GuildBuilder -]=] -function GuildBuilder.Prototype.setSplash(self: GuildBuilder, imageData: string) - self.splash = imageData - - return self -end - ---[=[ - Sets the discovery splash for this guild. imageData must be a 16:9, base64, encoded binary data for either a png/jpeg - - @method setDiscoverySplash - @param imageData string - @within Builders.GuildBuilder - @return Objects.GuildBuilder -]=] -function GuildBuilder.Prototype.setDiscoverySplash(self: GuildBuilder, imageData: string) - self.discoverySplash = imageData - - return self -end - ---[=[ - Sets the banner for this guild. imageData must be a 16:9, base64, encoded binary data for either a png/jpeg - - @method setSysetBannerstemChannelId - @param imageData string - @within Builders.GuildBuilder - @return Objects.GuildBuilder -]=] -function GuildBuilder.Prototype.setBanner(self: GuildBuilder, imageData: string) - self.banner = imageData - - return self -end - ---[=[ - Sets the System Channel for this guild. - - @method setSystemChannelId - @param channelId string - @within Builders.GuildBuilder - @return Objects.GuildBuilder -]=] -function GuildBuilder.Prototype.setSystemChannelId(self: GuildBuilder, channelId: string) - self.systemChannelId = channelId - - return self -end - ---[=[ - Sets the flags for the System Channel in this guild. - - @method setSystemChannelFlags - @param flags number - @within Builders.GuildBuilder - @return Objects.GuildBuilder -]=] -function GuildBuilder.Prototype.setSystemChannelFlags(self: GuildBuilder, flags: number) - self.systemChannelFlags = flags - - return self -end - ---[=[ - Sets the rules channel for this guild. - - @method setRulesChannelId - @param channelId string - @within Builders.GuildBuilder - @return Objects.GuildBuilder -]=] -function GuildBuilder.Prototype.setRulesChannelId(self: GuildBuilder, channelId: string) - self.rulesChannelId = channelId - - return self -end - ---[=[ - Sets the public update channel for this guild - - @method setPublicUpdateChannelId - @param channelId string - @within Builders.GuildBuilder - @return Objects.GuildBuilder -]=] -function GuildBuilder.Prototype.setPublicUpdateChannelId(self: GuildBuilder, channelId: string) - self.publicUpdateChannelId = channelId - - return self -end - ---[=[ - Sets the preferred locale of this guild - - @method setPreferredLocale - @param locale string - @within Builders.GuildBuilder - @return Objects.GuildBuilder -]=] -function GuildBuilder.Prototype.setPreferredLocale(self: GuildBuilder, locale: string) - self.preferredLocale = locale - - return self -end - ---[=[ - Enables developers to specify features that are apart of this guild. - - @method addFeature - @param feature string - @within Builders.GuildBuilder - @return Objects.GuildBuilder -]=] -function GuildBuilder.Prototype.addFeature(self: GuildBuilder, featureName: string) - GuildBuilder.Interface.GuildFeature:Assert(featureName) - - table.insert(self.features, featureName) - - return self -end - ---[=[ - Sets the description of this guild - - @method setDescription - @param description string - @within Builders.GuildBuilder - @return Objects.GuildBuilder -]=] -function GuildBuilder.Prototype.setDescription(self: GuildBuilder, description: string) - self.description = description - - return self -end - ---[=[ - Sets if the Premium Progress bar is enabled - - @method setPremiumProgressBarEnabled - @param enabled boolean - @within Builders.GuildBuilder - @return Objects.GuildBuilder -]=] -function GuildBuilder.Prototype.setPremiumProgressBarEnabled(self: GuildBuilder, enabled: boolean) - self.premiumProgressBarEnabled = enabled - - return self -end - ---[=[ - Sets the Safety Alert channel id - - @method setSafetyAlertsChannelId - @param channelId string - @within Builders.GuildBuilder - @return Objects.GuildBuilder -]=] -function GuildBuilder.Prototype.setSafetyAlertsChannelId(self: GuildBuilder, channelId: string) - self.safetyAlertsChannelId = channelId - - return self -end - ---[=[ - Enables developers to add a Role to a discord Guild channel upon creation. - - @method addRole - @param role Objects.GuildRoleBuilder - @within Builders.GuildBuilder - @return Objects.GuildBuilder -]=] -function GuildBuilder.Prototype.addRole(self: GuildBuilder, role: GuildRoleBuilder.GuildRoleBuilder) - table.insert(self.roles, role) - - return self -end - ---[=[ - Enables developers to add a Channel to a discord Guild channel upon creation. - - @method addChannel - @param channel Objects.ChannelBuilder - @within Builders.GuildBuilder - @return Objects.GuildBuilder -]=] -function GuildBuilder.Prototype.addChannel(self: GuildBuilder, channel: ChannelBuilder.ChannelBuilder) - table.insert(self.channels, channel) - - return self -end - ---[=[ - Converts the guild to a JSON object that can be sent to the Discord API. - - @method toPayloadObject - @within Builders.GuildBuilder - @return Network.Resolvable -]=] -function GuildBuilder.Prototype.toPayloadObject(self: GuildBuilder) - local roleObjects = {} - local channelObjects = {} - - for _, role in self.roles do - table.insert(roleObjects, role:toPayloadObject():resolve()) - end - - for _, channel in self.channels do - table.insert(channelObjects, channel:toPayloadObject():resolve()) - end - - return Resolvable.new(ResolvableType.JSON, { - name = self.name, - description = self.description, - verification_level = self.verificationLevel, - default_message_notifications = self.defaultMessageNotifications, - explicit_content_filter = self.explicitContentFilter, - afk_channel_id = self.afkChannelId, - afk_timeout = self.afkTimeout, - icon = self.icon, - owner_id = self.ownerId, - splash = self.splash, - discovery_splash = self.discoverySplash, - banner = self.banner, - system_channel_id = self.systemChannelId, - system_channel_flags = self.systemChannelFlags, - rules_channel_id = self.rulesChannelId, - public_updates_channel_id = self.publicUpdateChannelId, - preferred_locale = self.preferredLocale, - features = self.features, - premium_progress_bar_enabled = self.premiumProgressBarEnabled, - safety_alerts_channel_id = self.safetyAlertsChannelId, - roles = #roleObjects > 0 and roleObjects or nil, - channels = #channelObjects > 0 and channelObjects or nil, - }) -end - ---[=[ - Creates a new instance of GuildBuilder. - - @function new - @within Builders.GuildBuilder - @return Builders.GuildBuilder -- A new instance of GuildBuilder. -]=] -function GuildBuilder.Interface.new() - return ( - Construct({ - features = {}, - roles = {}, - channels = {}, - }, GuildBuilder.Prototype) :: unknown - ) :: GuildBuilder -end - -export type GuildBuilder = typeof(GuildBuilder.Prototype) & { - name: string, - description: string, - verificationLevel: number, - defaultMessageNotifications: number, - explicitContentFilter: number, - afkChannelId: string, - afkTimeout: number, - icon: string, - ownerId: string, - splash: string, - discoverySplash: string, - banner: string, - systemChannelId: string, - systemChannelFlags: number, - rulesChannelId: string, - publicUpdateChannelId: string, - preferredLocale: string, - features: { string }, - premiumProgressBarEnabled: boolean, - safetyAlertsChannelId: string, - roles: { GuildRoleBuilder.GuildRoleBuilder }, - channels: { ChannelBuilder.ChannelBuilder }, -} - -return GuildBuilder.Interface diff --git a/Package/Classes/Builders/GuildRoleBuilder.luau b/Package/Classes/Builders/GuildRoleBuilder.luau deleted file mode 100644 index 828727f..0000000 --- a/Package/Classes/Builders/GuildRoleBuilder.luau +++ /dev/null @@ -1,185 +0,0 @@ -local Construct = require("@Utils/Construct") - -local Resolvable = require("@Network/Resolvable") - -local PermissionsBuilder = require("@Builders/PermissionsBuilder") - -local ResolvableType = require("@Enums/ResolvableType") - ---[=[ - @class Builders.GuildRoleBuilder - - Allows a developer to build a Guild Role - - ```lua - local DiscordGuild = GuildRoleBuilder.new() - :setName("my-discord-role") - :setColor(0xFF0000) - :setHoisted(true) - :setMentionable(false) - ``` -]=] -local GuildRoleBuilder = {} - -GuildRoleBuilder.Interface = {} -GuildRoleBuilder.Prototype = {} - -GuildRoleBuilder.Prototype.type = "GuildRoleBuilder" - ---[=[ - Sets the name of the Role - - @method setName - @param name string - @within Builders.GuildRoleBuilder - @return Objects.GuildRoleBuilder -]=] -function GuildRoleBuilder.Prototype.setName(self: GuildRoleBuilder, name: string) - self.name = name - - return self -end - ---[=[ - Sets the color of the Role. - - @method setColor - @param color number - @within Builders.GuildRoleBuilder - @return Objects.GuildRoleBuilder -]=] -function GuildRoleBuilder.Prototype.setColor(self: GuildRoleBuilder, color: number) - self.color = color - - return self -end - ---[=[ - Sets weather this role will split users in the roles hierarchy in the guild. - - @method setHoisted - @param hoisted boolean - @within Builders.GuildRoleBuilder - @return Objects.GuildRoleBuilder -]=] -function GuildRoleBuilder.Prototype.setHoisted(self: GuildRoleBuilder, hoisted: boolean) - self.hoist = hoisted - - return self -end - ---[=[ - Sets the Icon for this role, the imageData should be a base64 encoded, 128x128 jpg file. - - @method setIcon - @param imageData string - @within Builders.GuildRoleBuilder - @return Objects.GuildRoleBuilder -]=] -function GuildRoleBuilder.Prototype.setIcon(self: GuildRoleBuilder, imageData: string) - self.icon = imageData - - return self -end - ---[=[ - Sets the Emoji for this Role - - @method setUnicodeEmoji - @param unicodeEmoji string - @within Builders.GuildRoleBuilder - @return Objects.GuildRoleBuilder -]=] -function GuildRoleBuilder.Prototype.setUnicodeEmoji(self: GuildRoleBuilder, unicodeEmoji: string) - self.unicodeEmoji = unicodeEmoji - - return self -end - ---[=[ - Set the position of this Role, this will update the roles hierarchy in the guild. - - @method setPosition - @param position number - @within Builders.GuildRoleBuilder - @return Objects.GuildRoleBuilder -]=] -function GuildRoleBuilder.Prototype.setPosition(self: GuildRoleBuilder, position: number) - self.position = position - - return self -end - ---[=[ - Set if discord members can mention this Role. - - @method setMentionable - @param mentionable boolean - @within Builders.GuildRoleBuilder - @return Objects.GuildRoleBuilder -]=] -function GuildRoleBuilder.Prototype.setMentionable(self: GuildRoleBuilder, mentionable: boolean) - self.mentionable = mentionable - - return self -end - ---[=[ - Set the flags for the Guild - - @method setFlags - @param flags number - @within Builders.GuildRoleBuilder - @return Objects.GuildRoleBuilder -]=] -function GuildRoleBuilder.Prototype.setFlags(self: GuildRoleBuilder, flags: number) - self.flags = flags - - return self -end - ---[=[ - Converts the role to a JSON object that can be sent to the Discord API. - - @method toPayloadObject - @within Builders.GuildRoleBuilder - @return Network.Resolvable -]=] -function GuildRoleBuilder.Prototype.toPayloadObject(self: GuildRoleBuilder) - return Resolvable.new(ResolvableType.JSON, { - name = self.name, - color = self.color, - hoist = self.hoist, - icon = self.icon, - unicode_emoji = self.unicodeEmoji, - position = self.position, - permission = self.permissions:getValue(), - mentionable = self.mentionable, - flags = self.flags, - }) -end - ---[=[ - Creates a new instance of GuildRoleBuilder. - - @function new - @within Builders.GuildRoleBuilder - @return Builders.GuildRoleBuilder -- A new instance of GuildRoleBuilder. -]=] -function GuildRoleBuilder.Interface.new() - return (Construct({}, GuildRoleBuilder.Prototype) :: unknown) :: GuildRoleBuilder -end - -export type GuildRoleBuilder = typeof(GuildRoleBuilder.Prototype) & { - name: string, - color: number, - hoist: boolean, - icon: string, - unicodeEmoji: string, - position: number, - permissions: PermissionsBuilder.PermissionsBuilder, - mentionable: boolean, - flags: number, -} - -return GuildRoleBuilder.Interface diff --git a/Package/Classes/Builders/IntentsBuilder.luau b/Package/Classes/Builders/IntentsBuilder.luau deleted file mode 100644 index e576399..0000000 --- a/Package/Classes/Builders/IntentsBuilder.luau +++ /dev/null @@ -1,104 +0,0 @@ -local Construct = require("@Utils/Construct") -local FetchKeys = require("@Utils/Dictionary/FetchKeys") - -local Intents = require("@Enums/Intents") - ---[=[ - @class Builders.IntentsBuilder - - IntentsBuilder is used to construct intents for a Discord bot, specifying which events the bot will receive. - - Usage: - ```lua - local DiscordLuau = require("../Submodules/DiscordLuau") - - local IntentsBuilder = DiscordLuau.IntentsBuilder.fromAll() - local DiscordSettings = DiscordLuau.DiscordSettings.new("BOT TOKEN", IntentsBuilder) - ``` -]=] -local IntentsBuilder = {} - -IntentsBuilder.Interface = {} -IntentsBuilder.Prototype = {} - -IntentsBuilder.Prototype.type = "IntentsBuilder" - -IntentsBuilder.DispositionMap = { - [Intents.GuildMembers] = 1, - [Intents.GuildModeration] = 2, - [Intents.GuildEmojisAndStickers] = 3, - [Intents.GuildIntegrations] = 4, - [Intents.GuildWebhooks] = 5, - [Intents.GuildInvites] = 6, - [Intents.GuildVoiceState] = 7, - [Intents.GuildPresences] = 8, - [Intents.GuildMessage] = 9, - [Intents.GuildMessageReactions] = 10, - [Intents.GuildMessageTyping] = 11, - [Intents.DirectMessage] = 12, - [Intents.DirectMessageReactions] = 13, - [Intents.DirectMessageTyping] = 14, - [Intents.GuildMessageContent] = 15, - [Intents.GuildScheduledEvents] = 16, - [Intents.GuildModerationConfiguration] = 20, - [Intents.GuildModerationExecution] = 21, -} - ---[=[ - - Creates a new instance of IntentsBuilder with the specified intents. - - @function new - @param intentList { string } -- A list of intents to include. - @within Builders.IntentsBuilder - @return Builders.IntentsBuilder -- A new instance of IntentsBuilder. -]=] -function IntentsBuilder.Interface.new(intentList: { string }) - local intentsValue = 0 - - for _, intentEnum in intentList do - local intentDisposition = IntentsBuilder.DispositionMap[intentEnum] - - assert(intentDisposition, `Unexpected intent '{intentEnum}'`) - - intentsValue += bit32.lshift(1, intentDisposition) - end - - return Construct({ - intents = intentsValue, - }, IntentsBuilder.Prototype) -end - ---[=[ - Creates a new instance of IntentsBuilder with the default intents. - - @function fromDefault - @within Builders.IntentsBuilder - @return Builders.IntentsBuilder -- A new instance of IntentsBuilder. -]=] -function IntentsBuilder.Interface.fromDefault() - local defaultIntents = table.clone(Intents) :: { [string]: any } - - defaultIntents.GuildPresences = nil - defaultIntents.GuildMembers = nil - defaultIntents.GuildMessageContent = nil - - return IntentsBuilder.Interface.new(FetchKeys(defaultIntents)) -end - ---[=[ - Creates a new instance of IntentsBuilder with all available intents. - - @function fromAll - @within Builders.IntentsBuilder - @return Builders.IntentsBuilder -- A new instance of IntentsBuilder. -]=] -function IntentsBuilder.Interface.fromAll() - local allIntents = table.clone(Intents) - - return IntentsBuilder.Interface.new(FetchKeys(allIntents)) -end - -export type IntentsBuilder = typeof(IntentsBuilder.Prototype) & {} - -return IntentsBuilder.Interface diff --git a/Package/Classes/Builders/Interface/ActionRowBuilder.luau b/Package/Classes/Builders/Interface/ActionRowBuilder.luau deleted file mode 100644 index 72f2a6e..0000000 --- a/Package/Classes/Builders/Interface/ActionRowBuilder.luau +++ /dev/null @@ -1,87 +0,0 @@ -local Construct = require("@Utils/Construct") - -local Resolvable = require("@Network/Resolvable") - -local ButtonBuilder = require("@Builders/Interface/ButtonBuilder") -local SelectionBuilder = require("@Builders/Interface/SelectionBuilder") -local TextInputBuilder = require("@Builders/Interface/TextInputBuilder") - -local ResolvableType = require("@Enums/ResolvableType") - ---[=[ - @class Builders.Interface.ActionRowBuilder - - ActionRowBuilder is used to construct an action row, which can contain up to five components like buttons, selections, or text inputs. -]=] -local ActionRowBuilder = {} - -ActionRowBuilder.Interface = {} -ActionRowBuilder.Prototype = {} - -ActionRowBuilder.Prototype.type = "ActionRowBuilder" - ---[=[ - Adds a component to the action row. The action row can contain up to five components. - - @private - @method addComponent - @param componentObject ActionRowBuilder | ButtonBuilder | SelectionBuilder | TextInputBuilder -- The component to be added. - @within Builders.Interface.ActionRowBuilder - @return Builders.Interface.ActionRowBuilder -- Returns the ActionRowBuilder instance for method chaining. -]=] -function ActionRowBuilder.Prototype.addComponent( - self: ActionRowBuilder, - componentObject: ActionRowBuilder | ButtonBuilder.ButtonBuilder | SelectionBuilder.SelectionBuilder | TextInputBuilder.TextInputBuilder -): ActionRowBuilder - assert(#self.components + 1 <= 5, "Action Row objects can only contain up to five components!") - - table.insert(self.components, componentObject) - - return self -end - ---[=[ - Converts the action row to a JSON object that can be sent to the Discord API. - - @private - @method toPayloadObject - @within Builders.Interface.ActionRowBuilder - @return Network.Resolvable -]=] -function ActionRowBuilder.Prototype.toPayloadObject(self: ActionRowBuilder) - local components = {} - - for index, componentObject in self.components :: { any } do - components[index] = componentObject:toPayloadObject():resolve() - end - - return Resolvable.new(ResolvableType.JSON, { - type = 1, - components = components, - }) -end - ---[=[ - Creates a new instance of ActionRowBuilder. - - @private - @function new - @within Builders.Interface.ActionRowBuilder - @return Builders.Interface.ActionRowBuilder -- A new instance of ActionRowBuilder. -]=] -function ActionRowBuilder.Interface.new(): ActionRowBuilder - return Construct({ - components = {}, - }, ActionRowBuilder.Prototype) -end - -export type ActionRowBuilder = typeof(ActionRowBuilder.Prototype) & { - components: { - ActionRowBuilder - | ButtonBuilder.ButtonBuilder - | SelectionBuilder.SelectionBuilder - | TextInputBuilder.TextInputBuilder - }, -} - -return ActionRowBuilder.Interface diff --git a/Package/Classes/Builders/Interface/ButtonBuilder.luau b/Package/Classes/Builders/Interface/ButtonBuilder.luau deleted file mode 100644 index 38b28be..0000000 --- a/Package/Classes/Builders/Interface/ButtonBuilder.luau +++ /dev/null @@ -1,170 +0,0 @@ -local Enumerate = require("@Utils/Enumerate") -local Construct = require("@Utils/Construct") - -local Resolvable = require("@Network/Resolvable") - -local ResolvableType = require("@Enums/ResolvableType") - ---[=[ - @class Builders.Interface.ButtonBuilder - - ButtonBuilder is used to construct a button component for a Discord message. -]=] -local ButtonBuilder = {} - -ButtonBuilder.Interface = {} -ButtonBuilder.Prototype = {} - -ButtonBuilder.Prototype.type = "ButtonBuilder" - ---[=[ - @prop Style table - @within Builders.Interface.ButtonBuilder - - An enumeration of button styles. - - - Blurple: 1 - - Grey: 2 - - Green: 3 - - Red: 4 - - Link: 5 -]=] -ButtonBuilder.Interface.Style = Enumerate.new({ - Blurple = 1, - Grey = 2, - Green = 3, - Red = 4, - Link = 5, -}) - ---[=[ - Sets the style of the button. - - @private - @method setStyle - @param buttonStyle number -- The style of the button. - @within Builders.Interface.ButtonBuilder - @return Builders.Interface.ButtonBuilder -- Returns the ButtonBuilder instance for method chaining. -]=] -function ButtonBuilder.Prototype.setStyle(self: ButtonBuilder, buttonStyle: number) - self.buttonStyle = buttonStyle - - return self -end - ---[=[ - Sets the label of the button. - - @private - @method setLabel - @param buttonLabel string -- The label of the button. - @within Builders.Interface.ButtonBuilder - @return Builders.Interface.ButtonBuilder -- Returns the ButtonBuilder instance for method chaining. -]=] -function ButtonBuilder.Prototype.setLabel(self: ButtonBuilder, buttonLabel: string) - self.buttonLabel = buttonLabel - - return self -end - ---[=[ - Sets the emoji for the button. - - @private - @method setEmoji - @param emojiId string -- The ID of the emoji. - @param emojiName string -- The name of the emoji. - @within Builders.Interface.ButtonBuilder - @return Builders.Interface.ButtonBuilder -- Returns the ButtonBuilder instance for method chaining. -]=] -function ButtonBuilder.Prototype.setEmoji(self: ButtonBuilder, emojiId: string, emojiName: string) - self.buttonEmoji = { - id = emojiId, - name = emojiName, - } - - return self -end - ---[=[ - Sets the URL for the button. This should only be used if the button style is set to `Link`. - - @private - @method setLinkUrl - @param url string -- The URL to set. - @within Builders.Interface.ButtonBuilder - @return Builders.Interface.ButtonBuilder -- Returns the ButtonBuilder instance for method chaining. -]=] -function ButtonBuilder.Prototype.setLinkUrl(self: ButtonBuilder, url: string) - self.buttonUrl = url - - return self -end - ---[=[ - Sets whether the button is disabled. - - @private - @method setDisabled - @param isDisabled boolean -- Whether the button is disabled. - @within Builders.Interface.ButtonBuilder - @return Builders.Interface.ButtonBuilder -- Returns the ButtonBuilder instance for method chaining. -]=] -function ButtonBuilder.Prototype.setDisabled(self: ButtonBuilder, isDisabled: boolean) - self.buttonDisabled = isDisabled - - return self -end - ---[=[ - Converts the button to a JSON object that can be sent to the Discord API. - - @private - @method toPayloadObject - @within Builders.Interface.ButtonBuilder - @return Network.Resolvable -]=] -function ButtonBuilder.Prototype.toPayloadObject(self: ButtonBuilder) - return Resolvable.new(ResolvableType.JSON, { - type = 2, - style = self.buttonStyle, - label = self.buttonLabel, - custom_id = (self.buttonStyle ~= ButtonBuilder.Interface.Style.Link and self.buttonId) or nil, - url = (self.buttonStyle == ButtonBuilder.Interface.Style.Link and self.buttonUrl) or nil, - disabled = self.buttonDisabled, - emoji = (self.buttonEmoji and { - id = self.buttonEmoji.id, - name = self.buttonEmoji.name, - }) or nil, - }) -end - ---[=[ - - Creates a new instance of ButtonBuilder. - - @private - @function new - @param buttonId string -- The ID of the button. - @within Builders.Interface.ButtonBuilder - @return Builders.Interface.ButtonBuilder -- A new instance of ButtonBuilder. -]=] -function ButtonBuilder.Interface.new(buttonId: string): ButtonBuilder - return Construct({ - buttonId = buttonId, - }, ButtonBuilder.Prototype) :: any -end - -export type ButtonBuilder = typeof(ButtonBuilder.Prototype) & { - buttonStyle: number?, - buttonLabel: string?, - buttonId: string?, - buttonUrl: string?, - buttonDisabled: boolean?, - buttonEmoji: { - id: string, - name: string, - }?, -} - -return ButtonBuilder.Interface diff --git a/Package/Classes/Builders/Interface/SelectionBuilder.luau b/Package/Classes/Builders/Interface/SelectionBuilder.luau deleted file mode 100644 index 6200dca..0000000 --- a/Package/Classes/Builders/Interface/SelectionBuilder.luau +++ /dev/null @@ -1,288 +0,0 @@ -local Enumerate = require("@Utils/Enumerate") -local Construct = require("@Utils/Construct") - -local Resolvable = require("@Network/Resolvable") - -local ResolvableType = require("@Enums/ResolvableType") - ---[=[ - @class Builders.Interface.SelectionBuilder - - SelectionBuilder is used to construct a selection (dropdown) component for a Discord message. -]=] -local SelectionBuilder = {} - -SelectionBuilder.Interface = {} -SelectionBuilder.Prototype = {} - -SelectionBuilder.Prototype.type = "SelectionBuilder" - ---[=[ - @prop Type table - @within Builders.Interface.SelectionBuilder - - An enumeration of selection types. - - - TextSelection: 3 - - UserSelection: 5 - - RoleSelection: 6 - - MentionableSelection: 7 - - ChannelSelection: 8 -]=] -SelectionBuilder.Interface.Type = Enumerate.new({ - TextSelection = 3, - UserSelection = 5, - RoleSelection = 6, - MentionableSelection = 7, - ChannelSelection = 8, -}) - ---[=[ - @prop ChannelType table - @within Builders.Interface.SelectionBuilder - - An enumeration of channel types. - - - GuildText: 0 - - DirectMessage: 1 - - GuildVoice: 2 - - GroupDirectMessage: 3 - - GuildCategory: 4 - - GuildAnnouncement: 5 - - AnnouncementThread: 10 - - PublicThread: 11 - - PrivateThread: 12 - - GuildStageVoice: 13 - - GuildDirectory: 14 - - GuildForum: 15 -]=] -SelectionBuilder.Interface.ChannelType = Enumerate.new({ - GuildText = 0, - DirectMessage = 1, - GuildVoice = 2, - GroupDirectMessage = 3, - GuildCategory = 4, - GuildAnnouncement = 5, - AnnouncementThread = 10, - PublicThread = 11, - PrivateThread = 12, - GuildStageVoice = 13, - GuildDirectory = 14, - GuildForum = 15, -}) - ---[=[ - Sets the type of the selection. - - @method setType - @param selectionType number -- The type of the selection. - @within Builders.Interface.SelectionBuilder - @return Builders.Interface.SelectionBuilder -- Returns the SelectionBuilder instance for method chaining. -]=] -function SelectionBuilder.Prototype.setType(self: SelectionBuilder, selectionType: number): SelectionBuilder - self.selectionType = selectionType - - return self -end - ---[=[ - Sets the channel types for the selection. - - @method setChannelTypes - @param ... number -- The channel types to set. - @within Builders.Interface.SelectionBuilder - @return Builders.Interface.SelectionBuilder -- Returns the SelectionBuilder instance for method chaining. -]=] -function SelectionBuilder.Prototype.setChannelTypes(self: SelectionBuilder, ...): SelectionBuilder - self.selectionChannelTypes = { ... } - - return self -end - ---[=[ - Sets the placeholder text for the selection. - - @method setPlaceholder - @param placeholderText string -- The placeholder text. - @within Builders.Interface.SelectionBuilder - @return Builders.Interface.SelectionBuilder -- Returns the SelectionBuilder instance for method chaining. -]=] -function SelectionBuilder.Prototype.setPlaceholder(self: SelectionBuilder, placeholderText: string): SelectionBuilder - self.selectionPlaceholder = placeholderText - - return self -end - ---[=[ - Sets whether the selection is disabled. - - @method setDisabled - @param isDisabled boolean -- Whether the selection is disabled. - @within Builders.Interface.SelectionBuilder - @return Builders.Interface.SelectionBuilder -- Returns the SelectionBuilder instance for method chaining. -]=] -function SelectionBuilder.Prototype.setDisabled(self: SelectionBuilder, isDisabled: boolean): SelectionBuilder - self.selectionDisabled = isDisabled - - return self -end - ---[=[ - Sets the minimum number of values that must be selected. - - @method setMinValues - @param minValue number -- The minimum number of values. - @within Builders.Interface.SelectionBuilder - @return Builders.Interface.SelectionBuilder -- Returns the SelectionBuilder instance for method chaining. -]=] -function SelectionBuilder.Prototype.setMinValues(self: SelectionBuilder, minValue: number): SelectionBuilder - self.selectionMinValue = minValue - - return self -end - ---[=[ - Sets the maximum number of values that can be selected. - - @method setMaxValues - @param maxValue number -- The maximum number of values. - @within Builders.Interface.SelectionBuilder - @return Builders.Interface.SelectionBuilder -- Returns the SelectionBuilder instance for method chaining. -]=] -function SelectionBuilder.Prototype.setMaxValues(self: SelectionBuilder, maxValue: number): SelectionBuilder - self.selectionMaxValue = maxValue - - return self -end - ---[=[ - Adds a choice to the selection. - - @method addChoice - @param choiceName string -- The name of the choice. - @param choiceValue string -- The value of the choice. - @param choiceDescription string -- The description of the choice. - @param isDefault boolean -- Whether the choice is the default selection. - @param emojiId string? -- The ID of the emoji. - @param emojiName string? -- The name of the emoji. - @within Builders.Interface.SelectionBuilder - @return Builders.Interface.SelectionBuilder -- Returns the SelectionBuilder instance for method chaining. -]=] -function SelectionBuilder.Prototype.addChoice( - self: SelectionBuilder, - choiceName: string, - choiceValue: string, - choiceDescription: string, - isDefault: boolean, - emojiId: string?, - emojiName: string? -): SelectionBuilder - self.choices[choiceName] = { - value = choiceValue, - description = choiceDescription, - default = isDefault, - } - - if emojiId and emojiName then - self.choices[choiceName].emoji = { - id = emojiId, - name = emojiName, - } - end - - return self -end - ---[=[ - Removes a choice from the selection. - - @method destroyChoice - @param choiceName string -- The name of the choice to remove. - @within Builders.Interface.SelectionBuilder - @return Builders.Interface.SelectionBuilder -- Returns the SelectionBuilder instance for method chaining. -]=] -function SelectionBuilder.Prototype.destroyChoice(self: SelectionBuilder, choiceName: string): SelectionBuilder - self.choices[choiceName] = nil - - return self -end - ---[=[ - Converts the selection to a JSON object that can be sent to the Discord API. - - @method toPayloadObject - @within Builders.Interface.SelectionBuilder - @return Network.Resolvable -]=] -function SelectionBuilder.Prototype.toPayloadObject(self: SelectionBuilder) - local selectionOptions = {} - - for optionName, optionData in self.choices do - table.insert(selectionOptions, { - label = optionName, - - value = optionData.value, - description = optionData.description, - emoji = (optionData.emoji and { - id = optionData.emoji.id, - name = optionData.emoji.name, - }) or nil, - default = optionData.default, - }) - end - - return Resolvable.new(ResolvableType.JSON, { - type = self.selectionType, - custom_id = self.selectionId, - - channel_types = self.selectionChannelTypes, - placeholder = self.selectionPlaceholder, - - min_value = self.selectionMinValue, - max_value = self.selectionMaxValue, - - disabled = self.selectionDisabled, - - options = selectionOptions, - }) -end - ---[=[ - - Creates a new instance of SelectionBuilder. - - @function new - @param selectionId string -- The ID of the selection. - @within Builders.Interface.SelectionBuilder - @return Builders.Interface.SelectionBuilder -- A new instance of SelectionBuilder. -]=] -function SelectionBuilder.Interface.new(selectionId: string): SelectionBuilder - return Construct({ - selectionId = selectionId, - choices = {}, - }, SelectionBuilder.Prototype) :: any -end - -export type SelectionBuilder = typeof(SelectionBuilder.Prototype) & { - selectionType: number?, - selectionId: string?, - selectionChannelTypes: { number }?, - selectionPlaceholder: string?, - selectionMinValue: number?, - selectionMaxValue: number?, - selectionDisabled: boolean?, - - choices: { - [string]: { - value: any?, - default: boolean?, - description: string?, - emoji: { - id: string, - name: string, - }?, - }, - }, -} - -return SelectionBuilder.Interface diff --git a/Package/Classes/Builders/Interface/TextInputBuilder.luau b/Package/Classes/Builders/Interface/TextInputBuilder.luau deleted file mode 100644 index 3644fc7..0000000 --- a/Package/Classes/Builders/Interface/TextInputBuilder.luau +++ /dev/null @@ -1,184 +0,0 @@ -local Enumerate = require("@Utils/Enumerate") -local Construct = require("@Utils/Construct") - -local Resolvable = require("@Network/Resolvable") - -local ResolvableType = require("@Enums/ResolvableType") - ---[=[ - @class Builders.Interface.TextInputBuilder - - TextInputBuilder is used to construct a text input component for a Discord message. -]=] -local TextInputBuilder = {} - -TextInputBuilder.Interface = {} -TextInputBuilder.Prototype = {} - -TextInputBuilder.Prototype.type = "TextInputBuilder" - ---[=[ - @prop Style table - @within Builders.Interface.TextInputBuilder - - An enumeration of text input styles. - - - Short: 1 - - Paragraph: 2 -]=] -TextInputBuilder.Interface.Style = Enumerate.new({ - Short = 1, - Paragraph = 2, -}) - ---[=[ - - Sets the style of the text input. - - @method setStyle - @param inputStyle number -- The style of the text input. - @within Builders.Interface.TextInputBuilder - @return Builders.Interface.TextInputBuilder -- Returns the TextInputBuilder instance for method chaining. -]=] -function TextInputBuilder.Prototype.setStyle(self: TextInputBuilder, inputStyle: number): TextInputBuilder - self.textInputStyle = inputStyle - - return self -end - ---[=[ - Sets the label of the text input. - - @method setLabel - @param label string -- The label of the text input. - @within Builders.Interface.TextInputBuilder - @return Builders.Interface.TextInputBuilder -- Returns the TextInputBuilder instance for method chaining. -]=] -function TextInputBuilder.Prototype.setLabel(self: TextInputBuilder, label: string): TextInputBuilder - self.textInputLabel = label - - return self -end - ---[=[ - Sets the minimum length of the text input. - - @method setMinLength - @param length number -- The minimum length of the text input. - @within Builders.Interface.TextInputBuilder - @return Builders.Interface.TextInputBuilder -- Returns the TextInputBuilder instance for method chaining. -]=] -function TextInputBuilder.Prototype.setMinLength(self: TextInputBuilder, length: number): TextInputBuilder - self.textInputMinLength = length - - return self -end - ---[=[ - Sets the maximum length of the text input. - - @method setMaxLength - @param length number -- The maximum length of the text input. - @within Builders.Interface.TextInputBuilder - @return Builders.Interface.TextInputBuilder -- Returns the TextInputBuilder instance for method chaining. -]=] -function TextInputBuilder.Prototype.setMaxLength(self: TextInputBuilder, length: number): TextInputBuilder - self.textInputMaxLength = length - - return self -end - ---[=[ - Sets whether the text input is required. - - @method setRequired - @param isRequired boolean -- Whether the text input is required. - @within Builders.Interface.TextInputBuilder - @return Builders.Interface.TextInputBuilder -- Returns the TextInputBuilder instance for method chaining. -]=] -function TextInputBuilder.Prototype.setRequired(self: TextInputBuilder, isRequired: boolean): TextInputBuilder - self.textInputRequired = isRequired - - return self -end - ---[=[ - Sets the default value of the text input. - - @method setDefaultValue - @param value string -- The default value of the text input. - @within Builders.Interface.TextInputBuilder - @return Builders.Interface.TextInputBuilder -- Returns the TextInputBuilder instance for method chaining. -]=] -function TextInputBuilder.Prototype.setDefaultValue(self: TextInputBuilder, value: string): TextInputBuilder - self.textInputDefaultValue = value - - return self -end - ---[=[ - Sets the placeholder value of the text input. - - @method setPlaceholder - @param value string -- The placeholder value of the text input. - @within Builders.Interface.TextInputBuilder - @return Builders.Interface.TextInputBuilder -- Returns the TextInputBuilder instance for method chaining. -]=] -function TextInputBuilder.Prototype.setPlaceholder(self: TextInputBuilder, value: string): TextInputBuilder - self.textInputPlaceholderValue = value - - return self -end - ---[=[ - Converts the text input to a JSON object that can be sent to the Discord API. - - @method toPayloadObject - @within Builders.Interface.TextInputBuilder - @return Network.Resolvable -]=] -function TextInputBuilder.Prototype.toPayloadObject(self: TextInputBuilder) - return Resolvable.new(ResolvableType.JSON, { - type = 4, - custom_id = self.textInputId, - - style = self.textInputStyle, - label = self.textInputLabel, - - min_length = self.textInputMinLength, - max_length = self.textInputMaxLength, - - required = self.textInputRequired, - - value = self.textInputDefaultValue, - placeholder = self.textInputPlaceholderValue, - }) -end - ---[=[ - - Creates a new instance of TextInputBuilder. - - @function new - @param textInputId string -- The ID of the text input. - @within Builders.Interface.TextInputBuilder - @return Builders.Interface.TextInputBuilder -- A new instance of TextInputBuilder. -]=] -function TextInputBuilder.Interface.new(textInputId: string): TextInputBuilder - return Construct({ - textInputId = textInputId, - }, TextInputBuilder.Prototype) :: any -end - -export type TextInputBuilder = typeof(TextInputBuilder.Prototype) & { - textInputStyle: number?, - textInputId: string?, - textInputLabel: string?, - textInputMinLength: number?, - textInputMaxLength: number?, - textInputRequired: boolean?, - textInputDefaultValue: any?, - textInputPlaceholderValue: any?, -} - -return TextInputBuilder.Interface diff --git a/Package/Classes/Builders/MemberBuilder.luau b/Package/Classes/Builders/MemberBuilder.luau deleted file mode 100644 index 4139efb..0000000 --- a/Package/Classes/Builders/MemberBuilder.luau +++ /dev/null @@ -1,173 +0,0 @@ -local Datetime = require("@Std/Datetime") - -local Construct = require("@Utils/Construct") - -local Resolvable = require("@Network/Resolvable") - -local ResolvableType = require("@Enums/ResolvableType") - ---[=[ - @class Builders.MemberBuilder - - MemberBuilder is used to update a member object for a guild, - - Usage: - ```lua - local member = MemberBuilder.new() - :setNickname("Discordian #0!") - ``` -]=] -local MemberBuilder = {} - -MemberBuilder.Interface = {} -MemberBuilder.Prototype = {} - -MemberBuilder.Prototype.type = "MemberBuilder" - ---[=[ - Sets the nickname of a discord member - - @method setNickname - @param nickname string - @within Builders.MemberBuilder - @return Builders.MemberBuilder -]=] -function MemberBuilder.Prototype.setNickname(self: MemberBuilder, nickname: string) - self.nickname = nickname - - return self -end - ---[=[ - Adds a role to a member - - @method addRole - @param roleId string - @within Builders.MemberBuilder - @return Builders.MemberBuilder -]=] -function MemberBuilder.Prototype.addRole(self: MemberBuilder, roleId: string) - table.insert(self.roles, roleId) - - return self -end - ---[=[ - Sets the muted state for this member - - @method setMuted - @param muted boolean - @within Builders.MemberBuilder - @return Builders.MemberBuilder -]=] -function MemberBuilder.Prototype.setMuted(self: MemberBuilder, muted: boolean) - self.mute = muted - - return self -end - ---[=[ - Sets the deafened state for this member - - @method setDeafened - @param deafened boolean - @within Builders.MemberBuilder - @return Builders.MemberBuilder -]=] -function MemberBuilder.Prototype.setDeafened(self: MemberBuilder, deafened: boolean) - self.deaf = deafened - - return self -end - ---[=[ - Allows you to specify the voice channel to move the member too. - - @method setActiveVoiceChannel - @param voiceChannelId string - @within Builders.MemberBuilder - @return Builders.MemberBuilder -]=] -function MemberBuilder.Prototype.setActiveVoiceChannel(self: MemberBuilder, voiceChannelId: string) - self.channelId = voiceChannelId - - return self -end - ---[=[ - Allows you to timeout a members communication. - - @method setTimeoutFor - @param epoch number - @within Builders.MemberBuilder - @return Builders.MemberBuilder -]=] -function MemberBuilder.Prototype.setTimeoutFor(self: MemberBuilder, epoch: number) - self.communicationDisabledUntil = epoch - - return self -end - ---[=[ - Sets the bitflags for this Member - - @method setFlags - @param flags number - @within Builders.MemberBuilder - @return Builders.MemberBuilder -]=] -function MemberBuilder.Prototype.setFlags(self: MemberBuilder, flags: number) - self.flags = flags - - return self -end - ---[=[ - Converts the member to a JSON object that can be sent to the Discord API. - - @method toPayloadObject - @within Builders.MemberBuilder - @return Network.Resolvable -]=] -function MemberBuilder.Prototype.toPayloadObject(self: MemberBuilder) - local communicationDisabledUntil - - if self.communicationDisabledUntil then - communicationDisabledUntil = Datetime.fromUnixTimestamp(os.time() + self.communicationDisabledUntil):toIsoDate() - end - - return Resolvable.new(ResolvableType.JSON, { - nick = self.nickname, - roles = self.roles, - mute = self.mute, - deaf = self.deaf, - channel_id = self.channelId, - communication_disabled_until = communicationDisabledUntil, - flags = self.flags, - }) -end - ---[=[ - Creates a new instance of MemberBuilder. - - @function new - @within Builders.MemberBuilder - @return Builders.MemberBuilder -- A new instance of MemberBuilder. -]=] -function MemberBuilder.Interface.new() - return (Construct({ - roles = {}, - }, MemberBuilder.Prototype) :: unknown) :: MemberBuilder -end - -export type MemberBuilder = typeof(MemberBuilder.Prototype) & { - nickname: string, - roles: { string }, - mute: boolean, - deaf: boolean, - channelId: string, - communicationDisabledUntil: number, - flags: number, -} - -return MemberBuilder.Interface diff --git a/Package/Classes/Builders/MessageBuilder.luau b/Package/Classes/Builders/MessageBuilder.luau deleted file mode 100644 index a5cba18..0000000 --- a/Package/Classes/Builders/MessageBuilder.luau +++ /dev/null @@ -1,279 +0,0 @@ -local Construct = require("@Utils/Construct") - -local Resolvable = require("@Network/Resolvable") - -local EmbedBuilder = require("@Builders/EmbedBuilder") -local AttachmentBuilder = require("@Builders/AttachmentBuilder") - -local ActionRowBuilder = require("@Builders/Interface/ActionRowBuilder") -local ButtonBuilder = require("@Builders/Interface/ButtonBuilder") -local SelectionBuilder = require("@Builders/Interface/SelectionBuilder") -local TextInputBuilder = require("@Builders/Interface/TextInputBuilder") - -local ResolvableType = require("@Enums/ResolvableType") - ---[=[ - @class Builders.MessageBuilder - - MessageBuilder is used to construct a message that can be sent to a Discord channel, including content, embeds, components, and stickers. - - Usage: - ```lua - local message = MessageBuilder.new() - :setContent("Hello, world!") - :addEmbed(EmbedBuilder.new():setTitle("Embed Title")) - :addComponent(ButtonBuilder.new("button_id")) - :addStickerId("sticker_id") - :setTTSEnabled(true) - ``` -]=] -local MessageBuilder = {} - -MessageBuilder.Interface = {} -MessageBuilder.Prototype = {} - -MessageBuilder.Prototype.type = "MessageBuilder" - ---[=[ - Sets the content of the message. - - @method setContent - @param content string -- The content of the message. - @within Builders.MessageBuilder - @return Builders.MessageBuilder -- Returns the MessageBuilder instance for method chaining. -]=] -function MessageBuilder.Prototype.setContent(self: MessageBuilder, content: string) - self.content = content - - return self -end - ---[=[ - Sets the nonce of the message. - - @method setNonce - @param nonce string -- The nonce of the message. - @within Builders.MessageBuilder - @return Builders.MessageBuilder -- Returns the MessageBuilder instance for method chaining. -]=] -function MessageBuilder.Prototype.setNonce(self: MessageBuilder, nonce: string) - self.nonce = nonce - self.nonceEnforced = true - - return self -end - ---[=[ - Sets whether text-to-speech (TTS) is enabled for the message. - - @method setTTSEnabled - @param ttsEnabled boolean -- Whether TTS is enabled. - @within Builders.MessageBuilder - @return Builders.MessageBuilder -- Returns the MessageBuilder instance for method chaining. -]=] -function MessageBuilder.Prototype.setTTSEnabled(self: MessageBuilder, ttsEnabled: boolean) - self.ttsEnabled = ttsEnabled - - return self -end - ---[=[ - Adds an embed to the message. - - @method addEmbed - @param embedBuilder EmbedBuilder -- The embed to add. - @within Builders.MessageBuilder - @return Builders.MessageBuilder -- Returns the MessageBuilder instance for method chaining. -]=] -function MessageBuilder.Prototype.addEmbed(self: MessageBuilder, embedBuilder: EmbedBuilder.EmbedBuilder) - table.insert(self.embeds, embedBuilder) - - return self -end - ---[=[ - Adds a component to the message. - - @method addComponent - @param discordComponent InterfaceBuilder -- The component to add. - @within Builders.MessageBuilder - @return Builders.MessageBuilder -- Returns the MessageBuilder instance for method chaining. -]=] -function MessageBuilder.Prototype.addComponent(self: MessageBuilder, discordComponent: InterfaceBuidler) - table.insert(self.components, discordComponent) - - return self -end - ---[=[ - Adds a sticker ID to the message. - - @method addStickerId - @param stickerId string -- The sticker ID to add. - @within Builders.MessageBuilder - @return Builders.MessageBuilder -- Returns the MessageBuilder instance for method chaining. -]=] -function MessageBuilder.Prototype.addStickerId(self: MessageBuilder, stickerId: string) - table.insert(self.stickers, stickerId) - - return self -end - ---[=[ - Sets the flags for the message. - - @method setFlags - @param flags number -- The flags for the message. - @within Builders.MessageBuilder - @return Builders.MessageBuilder -- Returns the MessageBuilder instance for method chaining. -]=] -function MessageBuilder.Prototype.setFlags(self: MessageBuilder, flags: number) - self.flags = flags - - return self -end - ---[=[ - Add a file to the Message object - - @method addFile - @param fileContent string - @param fileName string - @within Builders.MessageBuilder - @return Builders.MessageBuilder -- Returns the MessageBuilder instance for method chaining. -]=] -function MessageBuilder.Prototype.addFile(self: MessageBuilder, attachment: AttachmentBuilder.AttachmentBuilder) - table.insert(self.attachments, attachment) - - --todo: support descriptions? - - return self -end - ---[=[ - Converts the message to a JSON object that can be sent to the Discord API. - - @method toJsonObject - @within Builders.MessageBuilder - @return table -- The JSON representation of the message. -]=] -function MessageBuilder.Prototype.toJsonObject(self: MessageBuilder) - local components = {} - local embeds = {} - local attachments = {} - - for _, component in self.components do - table.insert(components, (component :: any):toPayloadObject():resolve()) - end - - for _, embed in self.embeds do - table.insert(embeds, embed:toPayloadObject():resolve()) - end - - for index, file in self.attachments do - table.insert(attachments, { - id = index - 1, - description = file.fileDescription, - filename = file.fileName, - }) - end - - return { - content = self.content or "", - nonce = self.nonce or nil, - tts = self.ttsEnabled or false, - flags = self.flags or nil, --fixme: should use a bitfield. - enforce_nonce = self.nonceEnforced or false, - - attachments = #attachments > 0 and attachments or nil, - embeds = #embeds > 0 and embeds or nil, - components = #components > 0 and components or nil, - - sticker_ids = #self.stickers > 0 and self.stickers or nil, - } -end - ---[=[ - Converts the message to a JSON object that can be sent to the Discord API. - - @method toPayloadObject - @within Builders.MessageBuilder - @return Network.Resolvable -]=] -function MessageBuilder.Prototype.toPayloadObject(self: MessageBuilder): Resolvable.Resolvable - local components = {} - local embeds = {} - - for _, component in self.components do - table.insert(components, (component :: any):toPayloadObject():resolve()) - end - - for _, embed in self.embeds do - table.insert(embeds, embed:toPayloadObject():resolve()) - end - - local data = { - content = self.content or "", - nonce = self.nonce or nil, - tts = self.ttsEnabled or false, - flags = self.flags or nil, --fixme: should use a bitfield. - enforce_nonce = self.nonceEnforced or false, - - embeds = #embeds > 0 and embeds or nil, - components = #components > 0 and components or nil, - - sticker_ids = #self.stickers > 0 and self.stickers or nil, - } - - if #self.attachments == 0 then - return Resolvable.new(ResolvableType.JSON, data) - else - return Resolvable.new(ResolvableType.FORMDATA, data, { - attachments = self.attachments, - headers = {}, - }) - end -end - ---[=[ - Creates a new instance of MessageBuilder. - - @function new - @within Builders.MessageBuilder - @return Builders.MessageBuilder -- A new instance of MessageBuilder. -]=] -function MessageBuilder.Interface.new() - return ( - Construct({ - embeds = {}, - components = {}, - attachments = {}, - stickers = {}, - }, MessageBuilder.Prototype) :: unknown - ) :: MessageBuilder -end - -type InterfaceBuidler = - ActionRowBuilder.ActionRowBuilder - | ButtonBuilder.ButtonBuilder - | SelectionBuilder.SelectionBuilder - | TextInputBuilder.TextInputBuilder - -export type MessageBuilder = typeof(MessageBuilder.Prototype) & { - content: string, - - nonce: string, - nonceEnforced: boolean, - - ttsEnabled: boolean, - - embeds: { EmbedBuilder.EmbedBuilder }, - components: { InterfaceBuidler }, - attachments: { AttachmentBuilder.AttachmentBuilder }, - - stickers: { string }, - - flags: number, -} - -return MessageBuilder.Interface diff --git a/Package/Classes/Builders/ModalBuilder.luau b/Package/Classes/Builders/ModalBuilder.luau deleted file mode 100644 index c72a896..0000000 --- a/Package/Classes/Builders/ModalBuilder.luau +++ /dev/null @@ -1,109 +0,0 @@ -local Construct = require("@Utils/Construct") - -local Resolvable = require("@Network/Resolvable") - -local ActionRowBuilder = require("@Builders/Interface/ActionRowBuilder") -local ButtonBuilder = require("@Builders/Interface/ButtonBuilder") -local SelectionBuilder = require("@Builders/Interface/SelectionBuilder") -local TextInputBuilder = require("@Builders/Interface/TextInputBuilder") - -local ResolvableType = require("@Enums/ResolvableType") - ---[=[ - @class Builders.ModalBuilder - - ModalBuilder is used to construct a modal dialog for a Discord interaction, including title and components. - - Usage: - ```lua - local modal = ModalBuilder.new("modal_id") - :setTitle("Example Modal") - :addComponent(ButtonBuilder.new("button_id")) - ``` -]=] -local ModalBuilder = {} - -ModalBuilder.Interface = {} -ModalBuilder.Prototype = {} - -ModalBuilder.Prototype.type = "ModalBuilder" - ---[=[ - Sets the title of the modal. - - @method setTitle - @param title string -- The title of the modal. - @within Builders.ModalBuilder - @return Builders.ModalBuilder -- Returns the ModalBuilder instance for method chaining. -]=] -function ModalBuilder.Prototype.setTitle(self: ModalBuilder, title: string): ModalBuilder - self.modalTitle = title - - return self -end - ---[=[ - Adds a component to the modal. The modal can contain up to five components. - - @method addComponent - @param componentObject DiscordComponentTypes.DiscordComponent -- The component to be added. - @within Builders.ModalBuilder - @return Builders.ModalBuilder -- Returns the ModalBuilder instance for method chaining. -]=] -function ModalBuilder.Prototype.addComponent(self: ModalBuilder, componentObject: InterfaceBuidler): ModalBuilder - assert(#self.components + 1 <= 5, "Action Row objects can only contain up to five components!") - - table.insert(self.components, componentObject) - - return self -end - ---[=[ - Converts the modal to a JSON object that can be sent to the Discord API. - - @method toPayloadObject - @within Builders.ModalBuilder - @return Network.Resolvable -]=] -function ModalBuilder.Prototype.toPayloadObject(self: ModalBuilder) - local components = {} - - for index, componentObject in self.components :: { any } do - components[index] = componentObject:toPayloadObject():resolve() - end - - return Resolvable.new(ResolvableType.JSON, { - title = self.modalTitle, - custom_id = self.modalId, - components = components, - }) -end - ---[=[ - Creates a new instance of ModalBuilder. - - @function new - @within Builders.ModalBuilder - @param modalId string -- The ID of the modal. - @return Builders.ModalBuilder -- A new instance of ModalBuilder. -]=] -function ModalBuilder.Interface.new(modalId: string) - return (Construct({ - components = {}, - modalId = modalId, - }, ModalBuilder.Prototype) :: unknown) :: ModalBuilder -end - -type InterfaceBuidler = - ActionRowBuilder.ActionRowBuilder - | ButtonBuilder.ButtonBuilder - | SelectionBuilder.SelectionBuilder - | TextInputBuilder.TextInputBuilder - -export type ModalBuilder = typeof(ModalBuilder.Prototype) & { - components: { InterfaceBuidler }, - modalId: string?, - modalTitle: string?, -} - -return ModalBuilder.Interface diff --git a/Package/Classes/Builders/OnboardingBuilder.luau b/Package/Classes/Builders/OnboardingBuilder.luau deleted file mode 100644 index 68524af..0000000 --- a/Package/Classes/Builders/OnboardingBuilder.luau +++ /dev/null @@ -1,154 +0,0 @@ -local Construct = require("@Utils/Construct") -local Enumerate = require("@Utils/Enumerate") - -local Resolvable = require("@Network/Resolvable") - -local OnboardingPromptBuilder = require("@Builders/OnboardingPromptBuilder") - -local ResolvableType = require("@Enums/ResolvableType") - ---[=[ - @class Builders.OnboardingBuilder - - OnboardingBuilder allows you to build onboarding for discord guilds. - - Usage: - ```lua - local DiscordLuau = require("../Submodules/DiscordLuau") - - local OnboardingBuilder = DiscordLuau.OnboardingBuilder.new() - :setMode(DiscordLuau.OnboardingBuilder.Mode.Default) - :setEnabled(true) - :addChannelId("1048686561685946489") - ``` -]=] -local OnboardingBuilder = {} - -OnboardingBuilder.Interface = {} -OnboardingBuilder.Prototype = {} - -OnboardingBuilder.Prototype.type = "OnboardingBuilder" - ---[=[ - @prop Mode table - @within Builders.OnboardingBuilder - - An enumeration of onboarding modes - - - Default: 0 - - Advanced: 1 -]=] -OnboardingBuilder.Interface.Mode = Enumerate.new({ - Default = 0, - Advanced = 1, -}) - ---[=[ - Sets the mode of the Guild onbaording object - - @method setMode - @param mode number - @within Builders.OnboardingBuilder - @return Builders.OnboardingBuilder -]=] -function OnboardingBuilder.Prototype.setMode(self: OnboardingBuilder, mode: number) - OnboardingBuilder.Interface.Mode:Assert(mode) - - self.mode = mode - - return self -end - ---[=[ - Sets if the guild onboarding is enabled or not - - @method setEnabled - @param isEnabled boolean - @within Builders.OnboardingBuilder - @return Builders.OnboardingBuilder -]=] -function OnboardingBuilder.Prototype.setEnabled(self: OnboardingBuilder, isEnabled: boolean) - self.enabled = isEnabled - - return self -end - ---[=[ - Channel IDs that members get opted into automatically - - @method addChannelId - @param channelId string - @within Builders.OnboardingBuilder - @return Builders.OnboardingBuilder -]=] -function OnboardingBuilder.Prototype.addChannelId(self: OnboardingBuilder, channelId: string) - table.insert(self.defaultChannelIds, channelId) - - return self -end - ---[=[ - Channel IDs that members get opted into automatically - - @method addOnboardingPrompt - @param prompt Objects.OnboardingPromptBuilder - @within Builders.OnboardingBuilder - @return Builders.OnboardingBuilder -]=] -function OnboardingBuilder.Prototype.addOnboardingPrompt( - self: OnboardingBuilder, - prompt: OnboardingPromptBuilder.OnboardingPromptBuilder -) - table.insert(self.prompts, prompt) - - return self -end - ---[=[ - Converts the message to a JSON object that can be sent to the Discord API. - - @method toPayloadObject - @within Builders.OnboardingBuilder - @return Network.Resolvable -]=] -function OnboardingBuilder.Prototype.toPayloadObject(self: OnboardingBuilder): Resolvable.Resolvable - local prompts = {} - - for index, prompt in self.prompts do - prompts[index] = prompt:toPayloadObject():resolve() - end - - return Resolvable.new(ResolvableType.JSON, { - enabled = self.enabled, - mode = self.mode, - - prompts = #prompts > 0 and prompts or nil, - default_channel_ids = #self.defaultChannelIds > 0 and self.defaultChannelIds or nil, - }) -end - ---[=[ - Creates a new instance of OnboardingBuilder. - - @function new - @within Builders.OnboardingBuilder - @return Builders.OnboardingBuilder -]=] -function OnboardingBuilder.Interface.new() - return ( - Construct({ - prompts = {}, - defaultChannelIds = {}, - }, OnboardingBuilder.Prototype) :: unknown - ) :: OnboardingBuilder -end - -export type OnboardingBuilder = typeof(OnboardingBuilder.Prototype) & { - enabled: boolean, - mode: number, - - defaultChannelIds: { string }, - prompts: { OnboardingPromptBuilder.OnboardingPromptBuilder }, -} - -return OnboardingBuilder.Interface diff --git a/Package/Classes/Builders/OnboardingPromptBuilder.luau b/Package/Classes/Builders/OnboardingPromptBuilder.luau deleted file mode 100644 index 82a0728..0000000 --- a/Package/Classes/Builders/OnboardingPromptBuilder.luau +++ /dev/null @@ -1,186 +0,0 @@ -local Construct = require("@Utils/Construct") -local Enumerate = require("@Utils/Enumerate") - -local Resolvable = require("@Network/Resolvable") - -local OnboardingPromptOptionBuilder = require("@Builders/OnboardingPromptOptionBuilder") - -local ResolvableType = require("@Enums/ResolvableType") - ---[=[ - @class Builders.OnboardingPromptBuilder - - OnboardingPromptBuilder allows you to build onboarding for discord guilds. - - Usage: - ```lua - local DiscordLuau = require("../Submodules/DiscordLuau") - - local OnboardingPromptBuilder = DiscordLuau.OnboardingPromptBuilder.new() - :setType(DiscordLuau.OnboardingPromptBuilder.Type.Dropdown) - :addOption( - DiscordLuau.OnboardingPromptOptionBuilder.new() - :setTitle("option-name") - :setDescription("Option Description!") - ) - ``` -]=] -local OnboardingPromptBuilder = {} - -OnboardingPromptBuilder.Interface = {} -OnboardingPromptBuilder.Prototype = {} - -OnboardingPromptBuilder.Prototype.type = "OnboardingPromptBuilder" - ---[=[ - @prop Type table - @within Builders.OnboardingPromptBuilder - - An enumeration of prompt types - - - MultipleChoice: 0 - - Dropdown: 1 -]=] -OnboardingPromptBuilder.Interface.Type = Enumerate.new({ - MultipleChoice = 0, - Dropdown = 1, -}) - ---[=[ - Set the type of the Onboarding Prompt builder - - @method setType - @param type number - @within Builders.OnboardingPromptBuilder - @return Builders.OnboardingPromptBuilder -]=] -function OnboardingPromptBuilder.Prototype.setType(self: OnboardingPromptBuilder, type: number) - OnboardingPromptBuilder.Interface.Type:Assert(type) - - self.promptType = type - - return self -end - ---[=[ - Adds an option to the Onboarding prompt builder. - - @method addOption - @param option Builders.OnboardingPromptOptionBuilder - @within Builders.OnboardingPromptBuilder - @return Builders.OnboardingPromptBuilder -]=] -function OnboardingPromptBuilder.Prototype.addOption( - self: OnboardingPromptBuilder, - option: OnboardingPromptOptionBuilder.OnboardingPromptOptionBuilder -) - table.insert(self.options, option) - - return self -end - ---[=[ - Sets the title of the onboarding prompt builder - - @method setTitle - @param title string - @within Builders.OnboardingPromptBuilder - @return Builders.OnboardingPromptBuilder -]=] -function OnboardingPromptBuilder.Prototype.setTitle(self: OnboardingPromptBuilder, title: string) - self.title = title - - return self -end - ---[=[ - Indicates whether users are limited to selecting one option for the prompt - - @method setSingleSelection - @param isSingleSelection boolean - @within Builders.OnboardingPromptBuilder - @return Builders.OnboardingPromptBuilder -]=] -function OnboardingPromptBuilder.Prototype.setSingleSelection(self: OnboardingPromptBuilder, isSingleSelection: boolean) - self.singleSelect = isSingleSelection - - return self -end - ---[=[ - Indicates whether the prompt is required before a user completes the onboarding flow - - @method setRequired - @param isRequired boolean - @within Builders.OnboardingPromptBuilder - @return Builders.OnboardingPromptBuilder -]=] -function OnboardingPromptBuilder.Prototype.setRequired(self: OnboardingPromptBuilder, isRequired: boolean) - self.required = isRequired - - return self -end - ---[=[ - Indicates whether the prompt is present in the onboarding flow. If false, the prompt will only appear in the Channels & Roles tab - - @method setIsOnboarding - @param isInOnboarding boolean - @within Builders.OnboardingPromptBuilder - @return Builders.OnboardingPromptBuilder -]=] -function OnboardingPromptBuilder.Prototype.setIsOnboarding(self: OnboardingPromptBuilder, isInOnboarding: boolean) - self.inOnboarding = isInOnboarding - - return self -end - ---[=[ - Converts the message to a JSON object that can be sent to the Discord API. - - @method toPayloadObject - @within Builders.OnboardingPromptBuilder - @return Network.Resolvable -]=] -function OnboardingPromptBuilder.Prototype.toPayloadObject(self: OnboardingPromptBuilder): Resolvable.Resolvable - local options = {} - - for index, option in self.options do - options[index] = option:toPayloadObject():resolve() - end - - return Resolvable.new(ResolvableType.JSON, { - type = self.promptType, - title = self.title, - single_select = self.singleSelect, - required = self.required, - in_onboarding = self.inOnboarding, - - options = #options > 0 and options or nil, - }) -end - ---[=[ - Creates a new instance of OnboardingPromptBuilder. - - @function new - @within Builders.OnboardingPromptBuilder - @return Builders.OnboardingPromptBuilder -]=] -function OnboardingPromptBuilder.Interface.new() - return (Construct({ - options = {}, - }, OnboardingPromptBuilder.Prototype) :: unknown) :: OnboardingPromptBuilder -end - -export type OnboardingPromptBuilder = typeof(OnboardingPromptBuilder.Prototype) & { - promptType: number, - title: string, - singleSelect: boolean, - required: boolean, - inOnboarding: boolean, - - options: { OnboardingPromptOptionBuilder.OnboardingPromptOptionBuilder }, -} - -return OnboardingPromptBuilder.Interface diff --git a/Package/Classes/Builders/OnboardingPromptOptionBuilder.luau b/Package/Classes/Builders/OnboardingPromptOptionBuilder.luau deleted file mode 100644 index c22bac7..0000000 --- a/Package/Classes/Builders/OnboardingPromptOptionBuilder.luau +++ /dev/null @@ -1,160 +0,0 @@ -local Construct = require("@Utils/Construct") - -local Resolvable = require("@Network/Resolvable") - -local ResolvableType = require("@Enums/ResolvableType") - ---[=[ - @class Builders.OnboardingPromptOptionBuilder - - OnboardingPromptOptionBuilder allows you to build onboarding for discord guilds. - - Usage: - ```lua - local DiscordLuau = require("../Submodules/DiscordLuau") - - local OnboardingPromptOptionBuilder = DiscordLuau.OnboardingPromptOptionBuilder.new() - :setTitle("option-name") - :setDescription("Option Description!") - ``` -]=] -local OnboardingPromptOptionBuilder = {} - -OnboardingPromptOptionBuilder.Interface = {} -OnboardingPromptOptionBuilder.Prototype = {} - -OnboardingPromptOptionBuilder.Prototype.type = "OnboardingPromptOptionBuilder" - ---[=[ - Sets the title of the Prompt Option builder - - @method setTitle - @param title string - @within Builders.OnboardingPromptOptionBuilder - @return Builders.OnboardingPromptOptionBuilder -]=] -function OnboardingPromptOptionBuilder.Prototype.setTitle(self: OnboardingPromptOptionBuilder, title: string) - self.title = title - - return self -end - ---[=[ - Sets the description of the Prompt Option builder - - @method setDescription - @param description string - @within Builders.OnboardingPromptOptionBuilder - @return Builders.OnboardingPromptOptionBuilder -]=] -function OnboardingPromptOptionBuilder.Prototype.setDescription( - self: OnboardingPromptOptionBuilder, - description: string -) - self.description = description - - return self -end - ---[=[ - Add a channel id to the prompt option builder - - @method addChannelId - @param channelId string - @within Builders.OnboardingPromptOptionBuilder - @return Builders.OnboardingPromptOptionBuilder -]=] -function OnboardingPromptOptionBuilder.Prototype.addChannelId(self: OnboardingPromptOptionBuilder, channelId: string) - table.insert(self.channelIds, channelId) - - return self -end - ---[=[ - Add a role id to the prompt option builder - - @method addRoleId - @param roleId string - @within Builders.OnboardingPromptOptionBuilder - @return Builders.OnboardingPromptOptionBuilder -]=] -function OnboardingPromptOptionBuilder.Prototype.addRoleId(self: OnboardingPromptOptionBuilder, roleId: string) - table.insert(self.roleIds, roleId) - - return self -end - ---[=[ - Sets the emoji of the Onboarding Prompt Option Builder - - @method setEmoji - @param emojiId string - @param emojiName string - @param emojiAnimated boolean? - @within Builders.OnboardingPromptOptionBuilder - @return Builders.OnboardingPromptOptionBuilder -]=] -function OnboardingPromptOptionBuilder.Prototype.setEmoji( - self: OnboardingPromptOptionBuilder, - emojiId: string, - emojiName: string, - emojiAnimated: boolean? -) - self.emojiId = emojiId - self.emojiName = emojiName - self.emojiAnimated = emojiAnimated or false - - return self -end - ---[=[ - Converts the message to a JSON object that can be sent to the Discord API. - - @method toPayloadObject - @within Builders.OnboardingBuilder - @return Network.Resolvable -]=] -function OnboardingPromptOptionBuilder.Prototype.toPayloadObject( - self: OnboardingPromptOptionBuilder -): Resolvable.Resolvable - return Resolvable.new(ResolvableType.JSON, { - title = self.title, - description = self.description, - emoji_id = self.emojiId, - emoji_name = self.emojiName, - emoji_animated = self.emojiAnimated, - - channels_ids = #self.channelIds > 0 and self.channelIds or nil, - role_ids = #self.roleIds > 0 and self.roleIds or nil, - }) -end - ---[=[ - - Creates a new instance of OnboardingPromptOptionBuilder with the specified intents. - - @function new - @param intentList { string } -- A list of intents to include. - @within Builders.OnboardingPromptOptionBuilder - @return Builders.OnboardingPromptOptionBuilder -- A new instance of OnboardingPromptOptionBuilder. -]=] -function OnboardingPromptOptionBuilder.Interface.new() - return ( - Construct({ - channelIds = {}, - roleIds = {}, - }, OnboardingPromptOptionBuilder.Prototype) :: unknown - ) :: OnboardingPromptOptionBuilder -end - -export type OnboardingPromptOptionBuilder = typeof(OnboardingPromptOptionBuilder.Prototype) & { - channelIds: { string }, - roleIds: { string }, - emojiId: string?, - emojiName: string?, - emojiAnimated: boolean?, - title: string, - description: string?, -} - -return OnboardingPromptOptionBuilder.Interface diff --git a/Package/Classes/Builders/PermissionsBuilder.luau b/Package/Classes/Builders/PermissionsBuilder.luau deleted file mode 100644 index 46adcf8..0000000 --- a/Package/Classes/Builders/PermissionsBuilder.luau +++ /dev/null @@ -1,260 +0,0 @@ -local Construct = require("@Utils/Construct") -local Enumerate = require("@Utils/Enumerate") - ---[=[ - @class Builders.PermissionsBuilder - - PermissionsBuilder is used to construct and manage permissions for a Discord bot. - - Usage: - ```lua - local permissions = PermissionsBuilder.new() - :addPermission(PermissionsBuilder.Permissions.Administrator) - :addPermission(PermissionsBuilder.Permissions.ManageChannels) - - print(permissions:getValue()) - ``` -]=] -local PermissionsBuilder = {} - -PermissionsBuilder.Interface = {} -PermissionsBuilder.Prototype = {} - -PermissionsBuilder.Prototype.type = "PermissionsBuilder" - ---[=[ - @prop Permissions table - @within Builders.PermissionsBuilder - - An enumeration of permissions. - - - CreateInstantInvite: 0 - - KickMembers: 1 - - BanMembers: 2 - - Administrator: 3 - - ManageChannels: 4 - - ManageGuild: 5 - - AddReactions: 6 - - ViewAuditLog: 7 - - PrioritySpeaker: 8 - - Stream: 9 - - ViewChannel: 10 - - SendMessages: 11 - - SendTTSMessages: 12 - - ManageMessages: 13 - - EmbedLinks: 14 - - AttachFiles: 15 - - ReadMessageHistory: 16 - - MentionEveryone: 17 - - UseExternalEmojis: 18 - - ViewGuildInsights: 19 - - Connect: 20 - - Speak: 21 - - MuteMembers: 22 - - DeafenMembers: 23 - - MoveMembers: 24 - - UseVAD: 25 - - ChangeNickname: 26 - - ChangeNicknames: 27 - - ManageRoles: 28 - - ManageWebhooks: 29 - - ManageGuildExpressions: 30 - - UseApplicationCommands: 31 - - RequestToSpeak: 32 - - ManageEvents: 33 - - ManageThreads: 34 - - CreatePublicThreads: 35 - - CreatePrivateThreads: 36 - - UseExternalStickers: 37 - - SendMessagesInThreads: 38 - - UseEmbeddedActivities: 39 - - ModerateMembers: 40 - - ViewCreatorMonetizationAnalytics: 41 - - UseSoundboard: 42 - - UseExternalSounds: 45 - - SendVoiceMessages: 46 -]=] -PermissionsBuilder.Interface.Permissions = Enumerate.new({ - CreateInstantInvite = 0, - KickMembers = 1, - BanMembers = 2, - Administrator = 3, - ManageChannels = 4, - ManageGuild = 5, - AddReactions = 6, - ViewAuditLog = 7, - PrioritySpeaker = 8, - Stream = 9, - ViewChannel = 10, - SendMessages = 11, - SendTTSMessages = 12, - ManageMessages = 13, - EmbedLinks = 14, - AttachFiles = 15, - ReadMessageHistory = 16, - MentionEveryone = 17, - UseExternalEmojis = 18, - ViewGuildInsights = 19, - Connect = 20, - Speak = 21, - MuteMembers = 22, - DeafenMembers = 23, - MoveMembers = 24, - UseVAD = 25, - ChangeNickname = 26, - ChangeNicknames = 27, - ManageRoles = 28, - ManageWebhooks = 29, - ManageGuildExpressions = 30, - UseApplicationCommands = 31, - RequestToSpeak = 32, - ManageEvents = 33, - ManageThreads = 34, - CreatePublicThreads = 35, - CreatePrivateThreads = 36, - UseExternalStickers = 37, - SendMessagesInThreads = 38, - UseEmbeddedActivities = 39, - ModerateMembers = 40, - ViewCreatorMonetizationAnalytics = 41, - UseSoundboard = 42, - UseExternalSounds = 45, - SendVoiceMessages = 46, -}) - ---[=[ - Adds a permission to the builder. - - @method addPermission - @param permission number -- The permission to add. - @within Builders.PermissionsBuilder - @return Builders.PermissionsBuilder -- Returns the PermissionsBuilder instance for method chaining. -]=] -function PermissionsBuilder.Prototype.addPermission(self: PermissionsBuilder, permission: number) - if table.find(self.permissions, permission) then - return - end - - table.insert(self.permissions, permission) - - return self -end - ---[=[ - Removes a permission from the builder. - - @method deletePermission - @param permission number -- The permission to remove. - @within Builders.PermissionsBuilder - @return Builders.PermissionsBuilder -- Returns the PermissionsBuilder instance for method chaining. -]=] -function PermissionsBuilder.Prototype.deletePermission(self: PermissionsBuilder, permission: number) - local index = table.find(self.permissions, permission) - - if not index then - return self - end - - table.remove(self.permissions, index) - - return self -end - ---[=[ - Checks if a permission is in the builder. - - @method hasPermission - @param permission number -- The permission to check. - @within Builders.PermissionsBuilder - @return boolean -- Returns true if the permission is in the builder, false otherwise. -]=] -function PermissionsBuilder.Prototype.hasPermission(self: PermissionsBuilder, permission: number) - return table.find(self.permissions, permission) ~= nil -end - ---[=[ - Gets the bitfield value of the permissions. - - @method getValue - @within Builders.PermissionsBuilder - @return string -- The bitfield value as a string. -]=] -function PermissionsBuilder.Prototype.getValue(self: PermissionsBuilder) - local permissionsInt = 0 - - for _, intentValue in self.permissions do - permissionsInt += bit32.lshift(1, intentValue) - end - - return tostring(permissionsInt) -end - ---[=[ - Creates a new instance of PermissionsBuilder. - - @function new - @within Builders.PermissionsBuilder - @return Builders.PermissionsBuilder -- A new instance of PermissionsBuilder. -]=] -function PermissionsBuilder.Interface.new() - return Construct({ - permissions = {}, - }, PermissionsBuilder.Prototype) -end - ---[=[ - Creates a new instance of PermissionsBuilder with no permissions. - - @function none - @within Builders.PermissionsBuilder - @return Builders.PermissionsBuilder -- A new instance of PermissionsBuilder. -]=] -function PermissionsBuilder.Interface.none() - return PermissionsBuilder.Interface.new() -end - ---[=[ - Creates a new instance of PermissionsBuilder with all permissions. - - @function all - @within Builders.PermissionsBuilder - @return Builders.PermissionsBuilder -- A new instance of PermissionsBuilder. -]=] -function PermissionsBuilder.Interface.all() - local permissions = PermissionsBuilder.Interface.new() - - for _, value in PermissionsBuilder.Interface.Permissions :: {} do - permissions:addPermission(value :: number) - end - - return permissions -end - ---[=[ - Creates a new instance of PermissionsBuilder from a bitfield. - - @function from - @param bitfield string -- The bitfield representing the permissions. - @within Builders.PermissionsBuilder - @return Builders.PermissionsBuilder -- A new instance of PermissionsBuilder. -]=] -function PermissionsBuilder.Interface.from(bitfield: string) - local permissions = PermissionsBuilder.Interface.new() - local numberBitfield = tonumber(bitfield) - - assert(numberBitfield, `Invalid BitField '{bitfield}'`) - - for _, value in PermissionsBuilder.Interface.Permissions :: {} do - if bit32.band(numberBitfield, value :: number) == value then - permissions:addPermission(value :: number) - end - end - - return permissions -end - -export type PermissionsBuilder = typeof(PermissionsBuilder.Prototype) & { - permissions: { number }, -} - -return PermissionsBuilder.Interface diff --git a/Package/Classes/Builders/PresenceBuilder.luau b/Package/Classes/Builders/PresenceBuilder.luau deleted file mode 100644 index d226beb..0000000 --- a/Package/Classes/Builders/PresenceBuilder.luau +++ /dev/null @@ -1,152 +0,0 @@ -local Construct = require("@Utils/Construct") -local Enumerate = require("@Utils/Enumerate") - -local Resolvable = require("@Network/Resolvable") - -local ActivityBuilder = require("@Builders/ActivityBuilder") - -local ResolvableType = require("@Enums/ResolvableType") - ---[=[ - @class Builders.PresenceBuilder - - PresenceBuilder is used to construct a presence object for a Discord bot, including activities, status, and AFK state. - - Usage: - ```lua - local presence = PresenceBuilder.new() - :addActivity(ActivityBuilder.new():setName("Playing a game")) - :setStatus(PresenceBuilder.Status.Online) - :setAfk(false) - :setSince(os.time()) - ``` -]=] -local PresenceBuilder = {} - -PresenceBuilder.Type = "PresenceBuilder" - -PresenceBuilder.Interface = {} -PresenceBuilder.Prototype = {} - -PresenceBuilder.Prototype.type = "PresenceBuilder" - ---[=[ - @prop Status table - @within Builders.PresenceBuilder - - An enumeration of presence statuses. - - - Online: "online" - - DoNotDisturb: "dnd" - - Idle: "idle" - - Invisible: "invisible" - - Offline: "offline" -]=] -PresenceBuilder.Interface.Status = Enumerate.new({ - Online = "online", - DoNotDisturb = "dnd", - Idle = "idle", - Invisible = "invisible", - Offline = "offline", -}) - ---[=[ - Adds an activity to the presence. - - @method addActivity - @param activity ActivityBuilder -- The activity to add. - @within Builders.PresenceBuilder - @return Builders.PresenceBuilder -- Returns the PresenceBuilder instance for method chaining. -]=] -function PresenceBuilder.Prototype.addActivity(self: PresenceBuilder, activity: ActivityBuilder.ActivityBuilder) - table.insert(self.activities, activity) - - return self -end - ---[=[ - Sets the status of the presence. - - @method setStatus - @param status string -- The status to set. - @within Builders.PresenceBuilder - @return Builders.PresenceBuilder -- Returns the PresenceBuilder instance for method chaining. -]=] -function PresenceBuilder.Prototype.setStatus(self: PresenceBuilder, status: string) - self.status = status - - return self -end - ---[=[ - Sets whether the presence is AFK. - - @method setAfk - @param isAfk boolean -- Whether the presence is AFK. - @within Builders.PresenceBuilder - @return Builders.PresenceBuilder -- Returns the PresenceBuilder instance for method chaining. -]=] -function PresenceBuilder.Prototype.setAfk(self: PresenceBuilder, isAfk: boolean) - self.idle = isAfk - - return self -end - ---[=[ - Sets the time since the presence was last updated. - - @method setSince - @param delta number -- The time since the presence was last updated, in milliseconds. - @within Builders.PresenceBuilder - @return Builders.PresenceBuilder -- Returns the PresenceBuilder instance for method chaining. -]=] -function PresenceBuilder.Prototype.setSince(self: PresenceBuilder, delta: number) - self.since = delta - - return self -end - ---[=[ - Converts the presence to a JSON object that can be sent to the Discord API. - - @method toPayloadObject - @within Builders.PresenceBuilder - @return Network.Resolvable -]=] -function PresenceBuilder.Prototype.toPayloadObject(self: PresenceBuilder) - local activities = {} - - for index, activity in self.activities :: { ActivityBuilder.ActivityBuilder } do - activities[index] = activity:toPayloadObject():resolve() - end - - return Resolvable.new(ResolvableType.JSON, { - since = self.since or 0, - - activities = activities, - afk = self.idle or false, - status = self.status or "online", - }) -end - ---[=[ - Creates a new instance of PresenceBuilder. - - @function new - @within Builders.PresenceBuilder - @return Builders.PresenceBuilder -- A new instance of PresenceBuilder. -]=] -function PresenceBuilder.Interface.new() - return (Construct({ - activities = {}, - }, PresenceBuilder.Prototype) :: unknown) :: PresenceBuilder -end - -export type PresenceBuilder = typeof(PresenceBuilder.Prototype) & { - activities: { ActivityBuilder.ActivityBuilder }, - since: number?, - idle: boolean?, - status: string?, -} - -return PresenceBuilder.Interface diff --git a/Package/Classes/Builders/SettingsBuilder.luau b/Package/Classes/Builders/SettingsBuilder.luau deleted file mode 100644 index 8a4fece..0000000 --- a/Package/Classes/Builders/SettingsBuilder.luau +++ /dev/null @@ -1,79 +0,0 @@ -local Construct = require("@Utils/Construct") - -local IntentsBuilder = require("@Builders/IntentsBuilder") - ---[=[ - @class Builders.SettingsBuilder - - A simple class to retain some generic data before a Discord Client can be started, for instance: - - - The Discord bot token/authentication - - The Discord bot intents - - Usage: - ```lua - local DiscordLuau = require("../Submodules/DiscordLuau") - - local IntentsBuilder = DiscordLuau.IntentsBuilder.fromAll() - local SettingsBuilder = DiscordLuau.SettingsBuilder.new("BOT TOKEN", IntentsBuilder) - ``` -]=] -local SettingsBuilder = {} - -SettingsBuilder.Interface = {} -SettingsBuilder.Prototype = {} - -SettingsBuilder.Prototype.type = "SettingsBuilder" - ---[=[ - Sets the Discord bot token. - - @method setDiscordToken - @param discordToken string -- The token for the Discord bot. - @within Builders.SettingsBuilder - @return Builders.SettingsBuilder -- Returns the SettingsBuilder instance for method chaining. -]=] -function SettingsBuilder.Prototype.setDiscordToken(self: SettingsBuilder, discordToken: string) - self.discordToken = discordToken - - return self -end - ---[=[ - Sets the Discord bot intents. - - @method setIntents - @param discordIntents IntentsBuilder -- The intents for the Discord bot. - @within Builders.SettingsBuilder - @return Builders.SettingsBuilder -- Returns the SettingsBuilder instance for method chaining. -]=] -function SettingsBuilder.Prototype.setIntents(self: SettingsBuilder, discordIntents: IntentsBuilder.IntentsBuilder) - self.discordIntents = discordIntents - - return self -end - ---[=[ - Constructs a new instance of SettingsBuilder. - - @function new - @param discordToken string -- The token for the Discord bot. - @param discordIntents IntentsBuilder? -- (Optional) The intents for the Discord bot. Defaults to `fromDefault` if not provided. - @within Builders.SettingsBuilder - @return Builders.SettingsBuilder -- A new instance of SettingsBuilder. -]=] -function SettingsBuilder.Interface.new(discordToken: string, discordIntents: IntentsBuilder.IntentsBuilder?) - return ( - Construct({ - discordToken = discordToken, - discordIntents = discordIntents or IntentsBuilder.fromDefault(), - }, SettingsBuilder.Prototype) :: unknown - ) :: SettingsBuilder -end - -export type SettingsBuilder = typeof(SettingsBuilder.Prototype) & { - discordToken: string, - discordIntents: IntentsBuilder.IntentsBuilder, -} - -return SettingsBuilder.Interface diff --git a/Package/Classes/Builders/WelcomeScreenBuilder.luau b/Package/Classes/Builders/WelcomeScreenBuilder.luau deleted file mode 100644 index aac79dd..0000000 --- a/Package/Classes/Builders/WelcomeScreenBuilder.luau +++ /dev/null @@ -1,125 +0,0 @@ -local Construct = require("@Utils/Construct") - -local Resolvable = require("@Network/Resolvable") - -local ResolvableType = require("@Enums/ResolvableType") - ---[=[ - @class Builders.WelcomeScreenBuilder - - WelcomeScreenBuilder allows you to build welcome screens for discord guilds. - - Usage: - ```lua - local DiscordLuau = require("../Submodules/DiscordLuau") - - local WelcomeScreenBuilder = DiscordLuau.WelcomeScreenBuilder.new() - :addChannel("1048686561685946489", "Super cool discord channel") - :setDescription("Yeah, a super awesome dsicord channel!") - ``` -]=] -local WelcomeScreenBuilder = {} - -WelcomeScreenBuilder.Interface = {} -WelcomeScreenBuilder.Prototype = {} - -WelcomeScreenBuilder.Prototype.type = "WelcomeScreenBuilder" - ---[=[ - Adds a channel object to the guilds Welcome Screen - - @method addChannel - @param channelId string - @param description string - @param emojiId string - @param emojiName string - @within Builders.WelcomeScreenBuilder - @return Builders.WelcomeScreenBuilder -]=] -function WelcomeScreenBuilder.Prototype.addChannel( - self: WelcomeScreenBuilder, - channelId: string, - description: string, - emojiId: string?, - emojiName: string? -) - table.insert(self.welcomeChannels, { - channelId = channelId, - description = description, - - emojiId = emojiId, - emojiName = emojiName, - }) - - return self -end - ---[=[ - Adds a channel object to the guilds Welcome Screen - - @method setDescription - @param description string - @within Builders.WelcomeScreenBuilder - @return Builders.WelcomeScreenBuilder -]=] -function WelcomeScreenBuilder.Prototype.setDescription(self: WelcomeScreenBuilder, description: string) - self.description = description - - return self -end - ---[=[ - Converts the message to a JSON object that can be sent to the Discord API. - - @method toPayloadObject - @within Builders.WelcomeScreenBuilder - @return Network.Resolvable -]=] -function WelcomeScreenBuilder.Prototype.toPayloadObject(self: WelcomeScreenBuilder): Resolvable.Resolvable - local welcomeChannels = {} - - for index, welcomeChannel in self.welcomeChannels do - welcomeChannels[index] = { - channel_id = welcomeChannel.channelId, - description = welcomeChannel.description, - - emoji_id = welcomeChannel.emojiId, - emoji_name = welcomeChannel.emojiName, - } - end - - return Resolvable.new(ResolvableType.JSON, { - description = self.description, - - welcome_channels = #welcomeChannels > 0 and welcomeChannels or nil, - }) -end - ---[=[ - Creates a new instance of WelcomeScreenBuilder. - - @function new - @within Builders.WelcomeScreenBuilder - @return Builders. -]=] -function WelcomeScreenBuilder.Interface.new() - return (Construct({ - welcomeChannels = {}, - }, WelcomeScreenBuilder.Prototype) :: unknown) :: WelcomeScreenBuilder -end - -export type WelcomeScreenBuilder = typeof(WelcomeScreenBuilder.Prototype) & { - description: string?, - - welcomeChannels: { - { - channelId: string, - description: string, - - emojiId: string?, - emojiName: string?, - } - }, -} - -return WelcomeScreenBuilder.Interface diff --git a/Package/Classes/DiscordClient.luau b/Package/Classes/DiscordClient.luau deleted file mode 100644 index b8b98b5..0000000 --- a/Package/Classes/DiscordClient.luau +++ /dev/null @@ -1,403 +0,0 @@ -local Task = require("@Std/Task") - -local Console = require("@Vendor/Console") -local Future = require("@Vendor/Future") -local Signal = require("@Vendor/Signal") - -local Construct = require("@Utils/Construct") - -local DiscordGateway = require("@Network/DiscordGateway") -local DiscordShard = require("@Network/DiscordShard") - -local SettingsBuilder = require("@Builders/SettingsBuilder") -local PresenceBuilder = require("@Builders/PresenceBuilder") -local GuildBuilder = require("@Builders/GuildBuilder") - -local EndpointCache = require("@Objects/EndpointCache") -local DiscordCache = require("@Objects/DiscordCache") -local DiscordUser = require("@Objects/DiscordUser") -local DiscordGuild = require("@Objects/DiscordGuild") -local DiscordChannel = require("@Objects/DiscordChannel") -local DiscordMessage = require("@Objects/DiscordMessage") -local DiscordApplication = require("@Objects/DiscordApplication") -local DiscordInteraction = require("@Objects/DiscordInteraction") -local EventManager = require("@Objects/EventManager") - -local DiscordEndpoints = require("@Data/DiscordEndpoints") - -local WebsocketEvents = require("@Enums/WebsocketEvents") -local DiscordEvents = require("@Enums/DiscordEvents") -local WebsocketOperationCodes = require("@Enums/WebsocketOperationCodes") -local WebsocketOperationKeys = require("@Enums/WebsocketOperationKeys") - -local CONCURRENT_IDENTIFY_YIELD = 5 - ---[=[ - @class DiscordClient - - The base class that implements the various properties, methods, and events that developers can use to interact with the Discord API. - - ```lua - local DiscordLuau = require("../Submodules/DiscordLuau") - - local SettingsBuilder = DiscordLuau.SettingsBuilder.new("BOT TOKEN") - local DiscordClient = DiscordLuau.DiscordClient.new(SettingsBuilder) - ``` -]=] - ---[=[ - @prop eventManager Objects.EventManager - @within DiscordClient -]=] - ---[=[ - @prop discordGateway Objects.DiscordGateway - @within DiscordClient -]=] - ---[=[ - @prop onEvent Vendor.Signal - @within DiscordClient -]=] - ---[=[ - @prop discordSettings SettingsBuilder - @within DiscordClient -]=] - ---[=[ - @prop discordCache DiscordCache - @within DiscordClient -]=] - ---[=[ - @prop reporter Vendor.Reporter - @within DiscordClient -]=] - ---[=[ - @prop websocketUrl string - @within DiscordClient -]=] - ---[=[ - @prop shardCount number - @within DiscordClient -]=] - ---[=[ - @prop maxConcurrency number - @within DiscordClient -]=] - ---[=[ - @prop discordShards { Network.DiscordShard } - @within DiscordClient -]=] - ---[=[ - @prop discordUser Objects.DiscordUser? - @within DiscordClient -]=] - ---[=[ - @prop discordApplication Objects.discordApplication - @within DiscordClient -]=] - -local DiscordClient = {} - -DiscordClient.Prototype = {} -DiscordClient.Interface = {} - -DiscordClient.Prototype.type = "DiscordClient" - ---[=[ - @method connectAsync - @within DiscordClient - - @return Vendor.Future - - Connects the current DiscordClient to the Discord API. - - ```lua - local DiscordClient = DiscordLuau.DiscordClient.new(SettingsBuilder) - - DiscordClient:connectAsync():after(function() - print("Connected to the Discord API!") - end) - ``` -]=] -function DiscordClient.Prototype.connectAsync(self: DiscordClient) - return Future.try(function() - self.discordGateway:setEndpointCache(DiscordEndpoints.BotGateway, EndpointCache.new(10)) - - local gatewayData = self.discordGateway:getAsync(DiscordEndpoints.BotGateway):await() - local websocketVersion = self.discordGateway:getApiVersion() - - self.websocketUrl = `{gatewayData.url}/?v={websocketVersion}` :: string - self.maxConcurrency = gatewayData.sessionStartLimit.maxConcurrency :: number - self.shardCount = gatewayData.shards :: number - - for shardId = 1, self.shardCount do - self.discordShards[shardId] = DiscordShard.new(self, shardId - 1) - end - - for bucketIndex = 1, self.shardCount, self.maxConcurrency do - for shardId = bucketIndex, (bucketIndex - 1) + self.maxConcurrency do - self.discordShards[shardId]:connectAsync(self.websocketUrl):await() - end - - if self.shardCount > self.maxConcurrency then - Task.wait(CONCURRENT_IDENTIFY_YIELD) - end - end - - for shardId = 1, self.shardCount do - local shard = self.discordShards[shardId] - - shard.onEvent:connect(function(eventName, data) - if eventName == WebsocketEvents.Ready then - --[=[ - @prop discordUser DiscordUser - @within DiscordClient - ]=] - self.discordUser = DiscordUser.new(self, data.user) - --[=[ - @prop discordApplication DiscordApplication - @within DiscordClient - ]=] - self.discordApplication = DiscordApplication.new(self, data.application) - - self.onEvent:fire(DiscordEvents.Ready) - elseif eventName == WebsocketEvents.MessageCreate then - local discordMessage = DiscordMessage.new(self, data) - - self.onEvent:fire(DiscordEvents.Message, discordMessage) - elseif eventName == WebsocketEvents.MessageUpdate then - local discordMessage = DiscordMessage.new(self, data) - - self.onEvent:fire(DiscordEvents.MessageChanged, discordMessage) - elseif eventName == WebsocketEvents.MessageDeleted then - local discordMessage = DiscordMessage.new(self, data) - - self.onEvent:fire(DiscordEvents.MessageDeleted, discordMessage) - elseif eventName == WebsocketEvents.InteractionCreate then - local discordInteraction = DiscordInteraction.new(self, data) - - self.onEvent:fire(DiscordEvents.Interaction, discordInteraction) - elseif eventName == WebsocketEvents.MessageBulkDeleted then - self.onEvent:fire(DiscordEvents.MessageBulkDeleted, data) - elseif eventName == WebsocketEvents.ChannelCreate then - local discordChannel = DiscordChannel.from(self, data) - - self.onEvent:fire(DiscordEvents.ChannelCreate, discordChannel) - elseif eventName == WebsocketEvents.ChannelUpdate then - local discordChannel = DiscordChannel.from(self, data) - - self.onEvent:fire(DiscordEvents.ChannelUpdate, discordChannel) - elseif eventName == WebsocketEvents.ChannelDeleted then - local discordChannel = DiscordChannel.from(self, data) - - self.onEvent:fire(DiscordEvents.ChannelDelete, discordChannel) - elseif eventName == WebsocketEvents.UserUpdated then - self.discordUser = DiscordUser.new(self, data.user) - - self.onEvent:fire(DiscordEvents.UserUpdated, self.discordUser) - elseif eventName == WebsocketEvents.ChannelPinsUpdate then - self.onEvent:fire(DiscordEvents.ChannelPinsUpdate, data) - elseif eventName == WebsocketEvents.GuildCreate then - local discordGuild = DiscordGuild.new(self, data) - - self.onEvent:fire(DiscordEvents.GuildCreate, discordGuild) - elseif eventName == WebsocketEvents.GuildUpdate then - local discordGuild = DiscordGuild.new(self, data) - - self.onEvent:fire(DiscordEvents.GuildCreate, discordGuild) - elseif eventName == WebsocketEvents.GuildDeleted then - local discordGuild = DiscordGuild.new(self, data) - - self.onEvent:fire(DiscordEvents.GuildCreate, discordGuild) - elseif eventName == WebsocketEvents.GuildMemberBanned then - data.user = DiscordUser.new(self, data.user) - - self.onEvent:fire(DiscordEvents.GuildMemberBanned, data) - elseif eventName == WebsocketEvents.GuildMemberUnbanned then - data.user = DiscordUser.new(self, data.user) - - self.onEvent:fire(DiscordEvents.GuildMemberUnbanned, data) - elseif eventName == WebsocketEvents.GuildMemberJoined then - data.user = DiscordUser.new(self, data.user) - - self.onEvent:fire(DiscordEvents.GuildMemberJoined, data) - elseif eventName == WebsocketEvents.GuildMemberLeft then - data.user = DiscordUser.new(self, data.user) - - self.onEvent:fire(DiscordEvents.GuildMemberLeft, data) - elseif eventName == WebsocketEvents.GuildMemberUpdated then - data.user = DiscordUser.new(self, data.user) - - self.onEvent:fire(DiscordEvents.GuildMemberUpdated, data) - else - self.reporter:debug(`Unknown Event: '{eventName}'`) - end - end) - end - end) -end - ---[=[ - Fetches a guild by its ID. - - @method fetchGuildAsync - @param guildId string - @within DiscordClient - @return Vendor.Future -]=] -function DiscordClient.Prototype.fetchGuildAsync( - self: DiscordClient, - guildId: string -): Future.Future - return Future.try(function() - local rawGuildData = self.discordGateway:getAsync(string.format(DiscordEndpoints.BotGetGuild, guildId)):await() - - return DiscordGuild.new(self, rawGuildData) - end) -end - ---[=[ - - Fetches a channel by its ID. - - @method fetchChannelAsync - @param channelId string - @within DiscordClient - @return Vendor.Future -]=] -function DiscordClient.Prototype.fetchChannelAsync( - self: DiscordClient, - channelId: string -): Future.Future - return Future.try(function() - local rawChannelData = - self.discordGateway:getAsync(string.format(DiscordEndpoints.BotGetChannel, channelId)):await() - - return DiscordChannel.from(self, rawChannelData) :: DiscordChannel.DiscordChannel - end) -end - ---[=[ - Updates the client's presence. - - @method updatePresenceAsync - @param discordPresence Builders.PresenceBuilder - @within DiscordClient - @return Vendor.Future -]=] -function DiscordClient.Prototype.updatePresenceAsync( - self: DiscordClient, - discordPresence: PresenceBuilder.PresenceBuilder -): Future.Future - return Future.try(function() - for shardId = 1, self.shardCount do - (self.discordShards :: { DiscordShard.DiscordShard })[shardId].discordWebsocket - :sendAsync({ - [WebsocketOperationKeys.OperationCode] = WebsocketOperationCodes.PresenceUpdate, - [WebsocketOperationKeys.Data] = discordPresence:toPayloadObject():resolve(), - }) - :await() - end - - return true - end) -end - ---[=[ - Creates a Guild, owned by the application. - - @method createGuildAsync - @param guildBuilder Builders.GuildBuilder - @within DiscordClient - @return Vendor.Future -]=] -function DiscordClient.Prototype.createGuildAsync( - self: DiscordClient, - guildBuilder: GuildBuilder.GuildBuilder -): Future.Future - return Future.try(function() - local rawGuildData: any = - self.discordGateway:postAsync(DiscordEndpoints.BotCreateGuild, guildBuilder:toPayloadObject()):await() - - return DiscordGuild.new(self, rawGuildData) - end) -end - ---[=[ - Sets the verbosity level for logging. - - @method setVerbose - @within DiscordClient - @param verbose boolean -]=] -function DiscordClient.Prototype.setVerbose(_: DiscordClient, verbose: boolean) - Console.setGlobalLogLevel(verbose and Console.LogLevel.debug or Console.LogLevel.warn) -end - ---[=[ - Constructor function for the Discord Client, used to instantiate a Discord Client object. - - @function new - @param discordSettings Builders.SettingsBuilder - @within DiscordClient - @return DiscordClient - - ```lua - local DiscordClient = DiscordLuau.DiscordClient.new(SettingsBuilder) - - DiscordClient:connectAsync():after(function() - print("Connected to the Discord API!") - end) - ``` -]=] -function DiscordClient.Interface.new(discordSettings: SettingsBuilder.SettingsBuilder): DiscordClient - local self = Construct({ - discordSettings = discordSettings, - discordCache = DiscordCache.new(), - reporter = Console.new("🎮 DiscordClient"), - websocketUrl = "", - shardCount = 0, - maxConcurrency = 0, - discordShards = {}, - onEvent = Signal.new(), - }, DiscordClient.Prototype) :: any - - self.discordGateway = DiscordGateway.new(self :: any) - self.eventManager = EventManager.new(self :: any) - - return self -end - -Console.setGlobalLogLevel(Console.LogLevel.warn) - -export type DiscordClient = typeof(DiscordClient.Prototype) & { - discordGateway: DiscordGateway.DiscordGateway, - discordSettings: SettingsBuilder.SettingsBuilder, - discordCache: DiscordCache.DiscordCache, - - eventManager: EventManager.EventManager, - - websocketUrl: string, - shardCount: number, - maxConcurrency: number, - - onEvent: Signal.Signal, - - reporter: Console.Console, - - discordShards: { DiscordShard.DiscordShard }, - - discordUser: DiscordUser.DiscordUser, - discordApplication: DiscordApplication.DiscordApplication, -} - -return DiscordClient.Interface diff --git a/Package/Classes/Network/DiscordGateway.luau b/Package/Classes/Network/DiscordGateway.luau deleted file mode 100644 index d4c4399..0000000 --- a/Package/Classes/Network/DiscordGateway.luau +++ /dev/null @@ -1,454 +0,0 @@ -local Serde = require("@Std/Serde") -local Net = require("@Std/Net") -local Task = require("@Std/Task") - -local Future = require("@Vendor/Future") -local Console = require("@Vendor/Console") - -local Construct = require("@Utils/Construct") - -local HTTPRatelimit = require("@Network/HTTPRatelimit") -local HTTPScheduler = require("@Network/HTTPScheduler") -local Resolvable = require("@Network/Resolvable") - -local EventIn = require("@Network/Middleware/EventIn") - -local ResolvableType = require("@Enums/ResolvableType") - -local DISCORD_API_VERSION = 9 - -local BASE_DISCORD_APP_URL = "https://discord.com" -local BASE_DISCORD_APP_API_PREFIX = "api" - -local FILE_FORMATS = { - jpg = "image/jpg", - jpeg = "image/jpeg", - png = "image/png", - webp = "image/webp", - gif = "image/gif", - - csv = "text/csv", - txt = "text/plain", - css = "text/css", - js = "text/javascript", - html = "text/html", - - mp4 = "video/mp4", -} - ---[=[ - @class Network.DiscordGateway - - DiscordGateway is an internal class responsible for managing communication with the Discord API. This class handles rate limiting, scheduling HTTP requests, and processing events. - - :::caution - This class is internal and should not be used directly by developers. - ::: -]=] -local DiscordGateway = {} - -DiscordGateway.Interface = {} -DiscordGateway.Prototype = {} - -DiscordGateway.Prototype.type = "DiscordGateway" - ---[=[ - Parses error messages from the Discord API response. - - @private - @method parseErrors - @param errorTable table -- The table containing error messages. - @param source string? -- The source of the error (optional). - @param depth number? -- The depth of the error message (optional). - @within Network.DiscordGateway - @return string -- The parsed error message. -]=] -function DiscordGateway.Prototype.parseErrors( - self: DiscordGateway, - errorTable: { [any]: any }, - source: string?, - depth: number? -) - local _source = source or "" - local _depth = depth or 0 - - for index, value in errorTable do - if index == "_errors" then - for _, errorObject in value do - _source ..= `{string.rep(" ", _depth)}{errorObject.code}: {errorObject.message}\n` - end - else - _source ..= `{string.rep(" ", _depth)}{index}:\n{self:parseErrors(value, _source, _depth + 1)}\n` - end - end - - return _source -end - ---[=[ - Parses errors from a Discord API response. - - @private - @method parseDiscordAPIErrors - @param api string -- The API endpoint that was called. - @param networkResponse Net.FetchResponse -- The response from the network request. - @within Network.DiscordGateway - @return string -- The parsed error message. -]=] -function DiscordGateway.Prototype.parseDiscordAPIErrors( - self: DiscordGateway, - api: string, - networkResponse: Net.FetchResponse -) - local success, messageDecoded = pcall(function() - return Serde.decode("json", networkResponse.body) - end) - - if success then - local discordErrorString = "" - - if messageDecoded.errors then - discordErrorString = self:parseErrors(messageDecoded.errors) - end - - return `Discord API Error - {messageDecoded.code} ({messageDecoded.message}): \n\nAPI: {api}\n\nTRACE:\n{discordErrorString}` - end - - return `Discord HTTP Error - {networkResponse.statusCode} ({networkResponse.statusMessage}): \n{networkResponse.body}` -end - ---[=[ - Sends an asynchronous request to the Discord API. - - @private - @method requestAsync - @param api string -- The API endpoint. - @param method string -- The HTTP method (e.g., "GET", "POST"). - @param data table? -- The data to send with the request (optional). - @within Network.DiscordGateway - @return Vendor.Future -- A Future that resolves with the response. -]=] -function DiscordGateway.Prototype.requestAsync( - self: DiscordGateway, - api: string, - method: "DELETE" | "GET" | "PATCH" | "POST" | "PUT", - data: Resolvable.Resolvable?, - headers: { [string]: string }? -) - return Future.try(function() - return self.discordScheduler:addTask(function() - local ratelimitApi = string.gsub(api, "%d+", "-") - - if self.endpointRateLimits[ratelimitApi] and self.endpointRateLimits[ratelimitApi]:isConsumed() then - while self.endpointRateLimits[ratelimitApi]:isConsumed() do - Task.wait() - end - end - - local bodyObject - local headerObject = { - ["authorization"] = `Bot {self.discordClient.discordSettings.discordToken}`, - ["content-type"] = "application/json", - } - - if headers then - for key, value in headers do - headerObject[key] = value - end - end - - if data then - if data.type == ResolvableType.JSON then - bodyObject = Serde.encode("json", data:resolve()) - elseif data.type == ResolvableType.FORMDATA then - local jsonContent = Serde.encode("json", data:resolve()) - - local multiformUuid = `DiscordLuau.{math.random()}` - local multiformContentBoundary = `{multiformUuid}` - local multiformContent = "" - - multiformContent ..= `--{multiformContentBoundary}\r\n` - multiformContent ..= `Content-Disposition: form-data; name="payload_json"\r\n` - multiformContent ..= `Content-Type: application/json\r\n` - - multiformContent ..= `\r\n` - multiformContent ..= `{jsonContent}\r\n` - - for index, fileObject in data.metadata.attachments do - local fileName = fileObject.fileName - local fileType = string.match(fileObject.fileName, ".+%.(%S+)") - local fileFormat = FILE_FORMATS[fileType] or "text/plain" - - multiformContent ..= `--{multiformContentBoundary}\r\n` - multiformContent ..= `Content-Disposition: form-data; name="files[{index - 1}]"; filename="{fileName}"\r\n` - multiformContent ..= `Content-Type: {fileFormat}\r\n` - - multiformContent ..= `\r\n` - multiformContent ..= `{fileObject.fileData}\r\n` - end - - multiformContent ..= `--{multiformContentBoundary}--\r\n` - - bodyObject = multiformContent - - headerObject["content-type"] = `multipart/form-data; boundary={multiformContentBoundary}` - end - - for key, value in data.metadata.headers do - headerObject[key] = value - end - end - - local networkResponse = Net.request({ - url = `{BASE_DISCORD_APP_URL}/{BASE_DISCORD_APP_API_PREFIX}/v{DISCORD_API_VERSION}/{api}`, - method = method, - headers = headerObject, - body = bodyObject, - }) - - self.reporter:debug( - `{method} Async to '{api}': {networkResponse.statusCode} - {networkResponse.statusMessage}` - ) - - local ratelimitLimit = tonumber(networkResponse.headers["x-ratelimit-limit"]) - local ratelimitRemaining = tonumber(networkResponse.headers["x-ratelimit-remaining"]) - local ratelimitResetAfter = tonumber(networkResponse.headers["x-ratelimit-reset-after"]) - - if ratelimitLimit and ratelimitRemaining and ratelimitResetAfter then - if not self.endpointRateLimits[ratelimitApi] then - self.endpointRateLimits[ratelimitApi] = ( - HTTPRatelimit.new(ratelimitLimit, ratelimitRemaining) :: unknown - ) :: HTTPRatelimit.HTTPRatelimit - end - - self.endpointRateLimits[ratelimitApi]:setRemaining(ratelimitRemaining) - self.endpointRateLimits[ratelimitApi]:resetAfter(ratelimitResetAfter + 1) - - self.reporter:debug( - `{method} Rate Limit '{ratelimitLimit - ratelimitRemaining}/{ratelimitLimit}, resetting in {ratelimitResetAfter}'` - ) - end - - if not networkResponse.ok then - error(self:parseDiscordAPIErrors(api, networkResponse)) - end - - if networkResponse.body == "" then - return - else - return self.gatewayEventIn:processJSON(Serde.decode("json", networkResponse.body)) - end - end) - end) -end - ---[=[ - Sends an asynchronous GET request to the Discord API. - - @private - @method getAsync - @param api string -- The API endpoint. - @param headers { [string]: string } - @within Network.DiscordGateway - @return Vendor.Future -- A Future that resolves with the response. -]=] -function DiscordGateway.Prototype.getAsync(self: DiscordGateway, api: string, headers: { [string]: string }?) - return Future.try(function() - local cache = self:getEndpointCache(api) - local cacheValue = cache and cache:get() - - if cacheValue then - return cacheValue - end - - local response = self:requestAsync(api, "GET", nil, headers):await() - - if cache then - cache:set(response) - end - - return response - end) -end - ---[=[ - Sends an asynchronous POST request to the Discord API. - - @private - @method postAsync - @param api string -- The API endpoint. - @param data table -- The data to send with the request. - @param headers { [string]: string } - @within Network.DiscordGateway - @return Vendor.Future -- A Future that resolves with the response. -]=] -function DiscordGateway.Prototype.postAsync( - self: DiscordGateway, - api: string, - data: any?, - headers: { [string]: string }? -) - return self:requestAsync(api, "POST", data, headers) -end - ---[=[ - - Sends an asynchronous PUT request to the Discord API. - - @private - @method putAsync - @param api string -- The API endpoint. - @param data table -- The data to send with the request. - @param headers { [string]: string } - @within Network.DiscordGateway - @return Vendor.Future -- A Future that resolves with the response. -]=] -function DiscordGateway.Prototype.putAsync( - self: DiscordGateway, - api: string, - data: any?, - headers: { [string]: string }? -) - return self:requestAsync(api, "PUT", data, headers) -end - ---[=[ - Sends an asynchronous DELETE request to the Discord API. - - @private - @method deleteAsync - @param api string -- The API endpoint. - @param data table? -- The data to send with the request (optional). - @param headers { [string]: string } - @within Network.DiscordGateway - @return Vendor.Future -- A Future that resolves with the response. -]=] -function DiscordGateway.Prototype.deleteAsync( - self: DiscordGateway, - api: string, - data: any?, - headers: { [string]: string }? -) - return self:requestAsync(api, "DELETE", data, headers) -end - ---[=[ - Sends an asynchronous PATCH request to the Discord API. - - @private - @method patchAsync - @param api string -- The API endpoint. - @param data table -- The data to send with the request. - @param headers { [string]: string } - @within Network.DiscordGateway - @return Vendor.Future -- A Future that resolves with the response. -]=] -function DiscordGateway.Prototype.patchAsync( - self: DiscordGateway, - api: string, - data: any?, - headers: { [string]: string }? -) - return self:requestAsync(api, "PATCH", data, headers) -end - ---[=[ - - Sets the cache for a specific API endpoint. - - @private - @method setEndpointCache - @param endpoint string -- The API endpoint. - @param cache table -- The cache object. - @within Network.DiscordGateway -]=] -function DiscordGateway.Prototype.setEndpointCache(self: DiscordGateway, endpoint: string, cache: { [any]: any }) - self.endpointCaches[endpoint] = cache -end - ---[=[ - Gets the cache for a specific API endpoint. - - @private - @method getEndpointCache - @param endpoint string -- The API endpoint. - @within Network.DiscordGateway - @return table -- The cache object. -]=] -function DiscordGateway.Prototype.getEndpointCache(self: DiscordGateway, endpoint: string) - return self.endpointCaches[endpoint] -end - ---[=[ - Gets the current Discord API version. - - @private - @method getApiVersion - @within Network.DiscordGateway - @return number -- The API version. -]=] -function DiscordGateway.Prototype.getApiVersion(_: DiscordGateway) - return DISCORD_API_VERSION -end - ---[=[ - Creates a new instance of DiscordGateway. - - @private - @function new - @param discordClient any -- The Discord client object. - @within Network.DiscordGateway - @return DiscordGateway -- A new instance of DiscordGateway. -]=] -function DiscordGateway.Interface.new(discordClient: any) - --[=[ - @prop discordScheduler Network.HTTPScheduler - @within Network.DiscordGateway - ]=] - - --[=[ - @prop gatewayEventIn Network.EventIn - @within Network.DiscordGateway - ]=] - - --[=[ - @prop reporter Vendor.Console - @within Network.DiscordGateway - ]=] - - --[=[ - @prop endpointRateLimits { [string]: HTTPRatelimit.HTTPRatelimit } - @within Network.DiscordGateway - ]=] - - --[=[ - @prop endpointCaches { [string]: { [any]: any } } - @within Network.DiscordGateway - ]=] - - return Construct({ - discordClient = discordClient, - discordScheduler = HTTPScheduler.new(1), - - gatewayEventIn = EventIn.new(), - - reporter = Console.new("🌉 DiscordGateway"), - - endpointRateLimits = {} :: { [string]: HTTPRatelimit.HTTPRatelimit }, - endpointCaches = {} :: { [string]: { [any]: any } }, - }, DiscordGateway.Prototype) -end - -export type DiscordGateway = typeof(DiscordGateway.Prototype) & { - discordClient: any, - discordScheduler: HTTPScheduler.HTTPScheduler, - - gatewayEventIn: EventIn.EventIn, - - reporter: Console.Console, - - endpointRateLimits: { [string]: HTTPRatelimit.HTTPRatelimit }, - endpointCaches: { [string]: { [any]: any } }, -} - -return DiscordGateway.Interface diff --git a/Package/Classes/Network/DiscordShard.luau b/Package/Classes/Network/DiscordShard.luau deleted file mode 100644 index 1f915c9..0000000 --- a/Package/Classes/Network/DiscordShard.luau +++ /dev/null @@ -1,501 +0,0 @@ -local Process = require("@Std/Process") -local Task = require("@Std/Task") - -local Future = require("@Vendor/Future") -local Console = require("@Vendor/Console") -local Signal = require("@Vendor/Signal") - -local Construct = require("@Utils/Construct") - -local DiscordWebsocket = require("@Network/DiscordWebsocket") - -local WebsocketOperationCodes = require("@Enums/WebsocketOperationCodes") -local WebsocketOperationKeys = require("@Enums/WebsocketOperationKeys") -local WebsocketCloseCodes = require("@Enums/WebsocketCloseCodes") -local WebsocketEvents = require("@Enums/WebsocketEvents") - -local LIBRARY_IDENTIFIER = "DiscordLuau" - ---[=[ - @class Network.DiscordShard - - DiscordShard is an internal class responsible for managing the connection and communication with a specific shard of the Discord WebSocket. This class handles events, errors, and operations specific to a shard. - - :::caution - This class is internal and should not be used directly by developers. - ::: -]=] -local DiscordShard = {} - -DiscordShard.Interface = {} -DiscordShard.Prototype = {} - -DiscordShard.Prototype.type = "DiscordShard" - ---[=[ - Observes and handles WebSocket errors. - - @private - @method observeWebsocketErrors - @within Network.DiscordShard -]=] -function DiscordShard.Prototype.observeWebsocketErrors(self: DiscordShard) - table.insert( - self.activeConnections, - self.discordWebsocket.onSocketDead:connect(function(closeCode) - if closeCode == WebsocketCloseCodes.UnknownError then - self:reconnectAsync() - elseif closeCode == WebsocketCloseCodes.UnknownOperation then - self:reconnectAsync() - elseif closeCode == WebsocketCloseCodes.DecodeError then - self:reconnectAsync() - elseif closeCode == WebsocketCloseCodes.NotAuthenticated then - self.reporter:error( - `Something has gone terribly wrong with Discord-Luau.. please create an issue! [{closeCode}]` - ) - elseif closeCode == WebsocketCloseCodes.AuthenticationFailed then - self.reporter:warn(`Failed to authenticate with the Discord API, is your token correct?`) - elseif closeCode == WebsocketCloseCodes.AlreadyAuthenticated then - self:reconnectAsync() - elseif closeCode == WebsocketCloseCodes.InvalidSequence then - self:reconnectAsync() - elseif closeCode == WebsocketCloseCodes.RateLimited then - self:reconnectAsync() - elseif closeCode == WebsocketCloseCodes.SessionTimedOut then - self:reconnectAsync() - elseif closeCode == WebsocketCloseCodes.InvalidShard then - self.reporter:error( - `Something has gone terribly wrong with Discord-Luau.. please create an issue! [{closeCode}]` - ) - elseif closeCode == WebsocketCloseCodes.InvalidAPIVersion then - self.reporter:error( - `Something has gone terribly wrong with Discord-Luau.. please create an issue! [{closeCode}]` - ) - elseif closeCode == WebsocketCloseCodes.InvalidIntents then - self.reporter:error( - `Something has gone terribly wrong with Discord-Luau.. please create an issue! [{closeCode}]` - ) - elseif closeCode == WebsocketCloseCodes.DisallowedIntents then - self.reporter:warn( - `Discord Bot intents are invalid, are you sure the bot has the chosen intents enabled?` - ) - end - end) - ) -end - ---[=[ - Observes and handles WebSocket events. - - @private - @method observeWebsocketEvents - @within Network.DiscordShard -]=] -function DiscordShard.Prototype.observeWebsocketEvents(self: DiscordShard) - table.insert( - self.activeConnections, - self.onEvent:connect(function(eventName, data) - if eventName == WebsocketEvents.Ready then - local websocketVersion = self.discordClient.discordGateway:getApiVersion() - - self.reporter:log(`DiscordShard is active - id: "{data.sessionId}"`) - - self.resumeSessionId = data.sessionId - self.resumeGatewayUrl = `{data.resumeGatewayUrl}/?v={websocketVersion}` - end - end) - ) -end - ---[=[ - Observes and handles WebSocket operations. - - @private - @method observeWebsocketOperations - @within Network.DiscordShard -]=] -function DiscordShard.Prototype.observeWebsocketOperations(self: DiscordShard) - table.insert( - self.activeConnections, - self.discordWebsocket.onOperationRecv:connect(function(operationCode, operationData, eventName, sequence) - if operationCode == WebsocketOperationCodes.Dispatch then - self.dispatchSequence = sequence :: number - - self.onEvent:fire(eventName :: string, operationData) - elseif operationCode == WebsocketOperationCodes.Hello then - Task.wait(math.random()) - - self.heartbeatInterval = operationData.heartbeatInterval - self:heartbeatAsync(true):after(function() - self:heartbeatIn(math.random(self.heartbeatInterval / 2, self.heartbeatInterval)) - end) - elseif operationCode == WebsocketOperationCodes.Heartbeat then - self.reporter:log(`Discord Websocket requested heartbeat, sending heartbeat!`) - - self:heartbeatAsync(true) - elseif operationCode == WebsocketOperationCodes.HeartbeatACK then - self.heartbeatAck = true - self.heartbeatPing = os.clock() - self.heartbeatClockTime :: number - - self.reporter:debug(`HeartbeatACK - Ping: {self.heartbeatPing}`) - - if not self.identified then - self.identified = true - - xpcall(function() - self:identifyAsync():await() - end, function(exception) - self.reporter:warn(`:identifyAsync call failed: '{exception}'`) - - self.identified = nil - end) - end - elseif operationCode == WebsocketOperationCodes.Reconnect then - self.reporter:log(`Discord Websocket requested reconnect, attempting to reconnect!`) - - self:reconnectAsync() - elseif operationCode == WebsocketOperationCodes.InvalidSession then - self.reporter:warn(`Discord Websocket session invalid!`) - - if operationData == true then - self.reporter:log(`Attempting to reconnect from Invalid Session!`) - - self:reconnectAsync() - else - self.reporter:warn(`Unrecoverable InvalidSession, attempting to destroy and recreate websocket.`) - - self.discordWebsocket:disconnectAsync(tostring(1005)):await() - - Task.wait(5) - - self:initiateWebsocket() - self:connectAsync(self.websocketUrl) - end - end - end) - ) -end - ---[=[ - Initiates the WebSocket connection. - - @private - @method initiateWebsocket - @within Network.DiscordShard -]=] -function DiscordShard.Prototype.initiateWebsocket(self: DiscordShard) - self.discordWebsocket = DiscordWebsocket.new() - - for _, connection in self.activeConnections do - connection:disconnect() - end - - self:observeWebsocketEvents() - self:observeWebsocketErrors() - self:observeWebsocketOperations() -end - ---[=[ - Sends a heartbeat to the Discord WebSocket. - - @private - @method heartbeatAsync - @within Network.DiscordShard - @param ignoreHeartbeatAck boolean? -- Whether to ignore the heartbeat acknowledgment (optional). - @return Vendor.Future -- A Future that resolves when the heartbeat is sent. -]=] -function DiscordShard.Prototype.heartbeatAsync(self: DiscordShard, ignoreHeartbeatAck: boolean?) - return Future.try(function() - if not ignoreHeartbeatAck then - if not self.heartbeatAck then - self.reporter:warn(`Discord Websocket state has become zombified, attempting to reconnect!`) - - self:reconnectAsync():await() - end - - self.heartbeatAck = nil - end - - self.heartbeatClockTime = os.clock() - - self.discordWebsocket - :sendAsync({ - [WebsocketOperationKeys.OperationCode] = WebsocketOperationCodes.Heartbeat, - [WebsocketOperationKeys.Data] = self.dispatchSequence or false, - }) - :await() - end) -end - ---[=[ - Schedules the next heartbeat. - - @private - @method heartbeatIn - @within Network.DiscordShard - @param milliseconds number -- The interval in milliseconds before the next heartbeat. -]=] -function DiscordShard.Prototype.heartbeatIn(self: DiscordShard, milliseconds: number) - if self.heartbeatTask then - Task.cancel(self.heartbeatTask) - end - - self.heartbeatTask = Task.delay(milliseconds / 1000, function() - self.heartbeatTask = nil - - self:heartbeatAsync():await() - self:heartbeatIn(milliseconds) - end) -end - ---[=[ - Identifies the Discord client with the Discord WebSocket. - - @private - @method identifyAsync - @within Network.DiscordShard - @return Vendor.Future -- A Future that resolves when the client is identified. -]=] -function DiscordShard.Prototype.identifyAsync(self: DiscordShard) - return Future.try(function() - self.discordWebsocket - :sendAsync({ - [WebsocketOperationKeys.OperationCode] = WebsocketOperationCodes.Identify, - [WebsocketOperationKeys.Data] = { - ["token"] = self.discordClient.discordSettings.discordToken, - ["intents"] = self.discordClient.discordSettings.discordIntents.intents, - ["properties"] = { - ["os"] = Process.os, - ["browser"] = LIBRARY_IDENTIFIER, - ["device"] = LIBRARY_IDENTIFIER, - }, - ["compress"] = true, - ["large_threshold"] = 250, - ["shard"] = { - self.shardId, - self.discordClient.shardCount, - }, - }, - }) - :await() - - self.onIdentified:fire() - end) -end - ---[=[ - Connects the DiscordShard to the Discord WebSocket. - - @private - @method connectAsync - @param websocketUrl string -- The WebSocket URL. - @within Network.DiscordShard - @return Vendor.Future -- A Future that resolves when the shard is connected. -]=] -function DiscordShard.Prototype.connectAsync(self: DiscordShard, websocketUrl: string) - self.websocketUrl = websocketUrl - - return Future.try(function() - self.discordWebsocket:connectAsync(websocketUrl):await() - - self.onIdentified:wait() - end) -end - ---[=[ - Resumes a previous session with the Discord WebSocket. - - @private - @method resumeAsync - @within Network.DiscordShard - @return Vendor.Future -- A Future that resolves when the session is resumed. -]=] -function DiscordShard.Prototype.resumeAsync(self: DiscordShard) - return Future.try(function() - local gatewayUrl = self.resumeGatewayUrl - - self.discordWebsocket:connectAsync(gatewayUrl or self.websocketUrl):await() - - if gatewayUrl then - self.discordWebsocket - :sendAsync({ - [WebsocketOperationKeys.OperationCode] = WebsocketOperationCodes.Resume, - [WebsocketOperationKeys.Data] = { - token = self.discordClient.discordSettings.discordToken, - session_id = self.resumeSessionId, - seq = self.dispatchSequence, - }, - }) - :await() - end - end) -end - ---[=[ - Attempts to reconnect the DiscordShard to the Discord WebSocket. - - @private - @method reconnectAsync - @within Network.DiscordShard - @return Vendor.Future -- A Future that resolves when the shard is reconnected. -]=] -function DiscordShard.Prototype.reconnectAsync(self: DiscordShard) - if self.heartbeatTask then - Task.cancel(self.heartbeatTask) - end - - return Future.try(function() - self.discordWebsocket:disconnectAsync(tostring(1005)):await() - - Task.wait(math.random()) - - self:resumeAsync():await() - end) -end - ---[=[ - Creates a new instance of DiscordShard. - - @private - @function new - @param discordClient any -- The Discord client object. - @param shardId number -- The ID of the shard. - @within Network.DiscordShard - @return DiscordShard -- A new instance of DiscordShard. -]=] -function DiscordShard.Interface.new(discordClient: any, shardId: number): DiscordShard - --[=[ - @prop discordWebsocket Network.DiscordWebsocket - @within Network.DiscordShard - ]=] - - --[=[ - @prop websocketUrl string - @within Network.DiscordShard - ]=] - - --[=[ - @prop reporter Vendor.Console - @within Network.DiscordShard - ]=] - - --[=[ - @prop heartbeatTask thread? - @within Network.DiscordShard - ]=] - - --[=[ - @prop resumeSessionId string - @within Network.DiscordShard - ]=] - - --[=[ - @prop dispatchSequence number - @within Network.DiscordShard - ]=] - - --[=[ - @prop heartbeatInterval number - @within Network.DiscordShard - ]=] - - --[=[ - @prop heartbeatClockTime number - @within Network.DiscordShard - ]=] - - --[=[ - @prop heartbeatPing number - @within Network.DiscordShard - ]=] - - --[=[ - @prop identified boolean? - @within Network.DiscordShard - ]=] - - --[=[ - @prop resumeGatewayUrl string - @within Network.DiscordShard - ]=] - - --[=[ - @prop heartbeatAck boolean? - @within Network.DiscordShard - ]=] - - --[=[ - @prop activeConnections { unknown } - @within Network.DiscordShard - ]=] - - --[=[ - @prop onEvent Vendor.Signal - @within Network.DiscordShard - ]=] - - --[=[ - @prop onIdentified Vendor.Signal - @within Network.DiscordShard - ]=] - - --[=[ - @prop shardId number - @within Network.DiscordShard - ]=] - - local self = Construct({ - discordClient = discordClient, - discordWebsocket = nil, - activeConnections = {}, - - websocketUrl = "", - - reporter = Console.new(`🖲️ DiscordShard {shardId}`), - - heartbeatTask = nil, - resumeSessionId = nil, - dispatchSequence = nil, - heartbeatInterval = nil, - - onEvent = Signal.new(), - onIdentified = Signal.new(), - - shardId = shardId, - }, DiscordShard.Prototype) :: any - - self:initiateWebsocket() - - return self -end - -export type DiscordShard = typeof(DiscordShard.Prototype) & { - discordWebsocket: DiscordWebsocket.DiscordWebsocket, - discordClient: any, - - websocketUrl: string, - - reporter: Console.Console, - - heartbeatTask: thread?, - resumeSessionId: string, - dispatchSequence: number, - heartbeatInterval: number, - heartbeatClockTime: number, - heartbeatPing: number, - - identified: boolean?, - - resumeGatewayUrl: string?, - - heartbeatAck: boolean?, - - activeConnections: { Signal.Connection }, - - onEvent: Signal.Signal, - onIdentified: Signal.Signal, - - shardId: number, -} - -return DiscordShard.Interface diff --git a/Package/Classes/Network/DiscordUDP.luau b/Package/Classes/Network/DiscordUDP.luau deleted file mode 100644 index 367098c..0000000 --- a/Package/Classes/Network/DiscordUDP.luau +++ /dev/null @@ -1,204 +0,0 @@ -local Net = require("@Std/Net") -local Task = require("@Std/Task") - -local Future = require("@Vendor/Future") -local Signal = require("@Vendor/Signal") -local Console = require("@Vendor/Console") - -local Construct = require("@Utils/Construct") - ---[=[ - @class Network.DiscordUDP - - DiscordUDP is an internal class responsible for managing UDP communication with the Discord API. This class handles sending and receiving UDP packets. - - :::caution - This class is internal and should not be used directly by developers. - ::: -]=] -local DiscordUDP = {} - -DiscordUDP.Interface = {} -DiscordUDP.Prototype = {} - -DiscordUDP.Prototype.type = "DiscordUDP" - ---[=[ - Sends a UDP packet asynchronously. - - @private - @method sendAsync - @param dataPacket string -- The data packet to send. - @within Network.DiscordUDP - @return Vendor.Future -- A Future that resolves when the packet is sent. -]=] -function DiscordUDP.Prototype.sendAsync(self: DiscordUDP, dataPacket: string) - return Future.try(function() - if not self.socketInstance then - error(`Socket Instance isn't available!`) - end - - self.socketInstance.send(dataPacket) - end) -end - ---[=[ - Connects to a UDP socket asynchronously. - - @private - @method connectAsync - @param discordUdp string -- The UDP address to connect to. - @within Network.DiscordUDP - @return Vendor.Future -- A Future that resolves when the connection is established. -]=] -function DiscordUDP.Prototype.connectAsync(self: DiscordUDP, discordUdp: string) - return Future.try(function() - local socket = self.socketInstance :: any - - self.socketUrl = discordUdp - - socket:connect(discordUdp) - - self.socketActive = true - self.socketThread = Task.spawn(function() - while self.socketActive do - if not socket then - return - end - - if socket.closeCode then - -- This isn't a thing, but in the real impl - it would be. - self.socketActive = false - - self.onSocketDead:fire(socket.closeCode) - else - local socketMessage = socket.next() - - self.onMessageRecv:fire(buffer.tostring(socketMessage)) - end - end - end) - end) -end - ---[=[ - Disconnects from the UDP socket asynchronously. - - @private - @method disconnectAsync - @param closingCode string -- The closing code for the disconnection. - @within Network.DiscordUDP - @return Vendor.Future -- A Future that resolves when the socket is disconnected. -]=] -function DiscordUDP.Prototype.disconnectAsync(self: DiscordUDP, closingCode: string) - return Future.try(function() - if not self.socketInstance then - return - end - - (Net :: any).udpSocketClose(self.socketInstance, closingCode) - end) -end - ---[=[ - Creates a new instance of DiscordUDP. - - @function new - @param publicUdp string -- The public UDP address. - @within Network.DiscordUDP - @return DiscordUDP -- A new instance of DiscordUDP. -]=] -function DiscordUDP.Interface.new(publicUdp: string) - --[=[ - @prop udpActive boolean - @within Network.DiscordUDP - ]=] - - --[=[ - @prop publicUdp Nstring - @within Network.DiscordUDP - ]=] - - --[=[ - @prop onMessageRecv Vendor.Signal - @within Network.DiscordUDP - ]=] - - --[=[ - @prop onSocketDead Vendor.Signal - @within Network.DiscordUDP - ]=] - - --[=[ - @prop socketInstance WebSocket - @within Network.DiscordUDP - ]=] - - --[=[ - @prop reporter Vendor.Console - @within Network.DiscordUDP - ]=] - - --[=[ - @prop socketUrl string - @within Network.DiscordUDP - ]=] - - --[=[ - @prop socketActive string - @within Network.DiscordUDP - ]=] - - --[=[ - @prop socketActiveChanged Vendor.Signal - @within Network.DiscordUDP - ]=] - - --[=[ - @prop socketThread thread? - @within Network.DiscordUDP - ]=] - - local self = Construct({ - publicUdp = publicUdp, - socketInstance = (Net :: any).udpSocket(publicUdp), - - socketActive = false, - socketActiveChanged = Signal.new(), - - onMessageRecv = Signal.new(), - onSocketDead = Signal.new(), - - socketUrl = nil, - - reporter = Console.new("🪁 DiscordUDP"), - }, DiscordUDP.Prototype) - - self.socketActiveChanged:connect(function(state) - self.reporter:debug( - `Discord UDP Socket {(state and "Connected") or "Disconnected"} [{self.socketUrl or "unknown"}]` - ) - end) - - return self -end - -export type DiscordUDP = typeof(DiscordUDP.Prototype) & { - udpActive: boolean, - publicUdp: string, - - onMessageRecv: Signal.Signal, - onSocketDead: Signal.Signal, - - socketInstance: Net.WebSocket?, - - reporter: Console.Console, - - socketUrl: string?, - socketActive: boolean, - socketActiveChanged: Signal.Signal, - - socketThread: thread?, -} - -return DiscordUDP.Interface diff --git a/Package/Classes/Network/DiscordVoiceConnection.luau b/Package/Classes/Network/DiscordVoiceConnection.luau deleted file mode 100644 index d0aaa4e..0000000 --- a/Package/Classes/Network/DiscordVoiceConnection.luau +++ /dev/null @@ -1,517 +0,0 @@ -local Task = require("@Std/Task") - -local Future = require("@Vendor/Future") -local Console = require("@Vendor/Console") -local Signal = require("@Vendor/Signal") - -local Construct = require("@Utils/Construct") - -local DiscordWebsocket = require("@Network/DiscordWebsocket") -local DiscordUDP = require("@Network/DiscordUDP") - -local VoiceWebsocketOperationCodes = require("@Enums/VoiceWebsocketOperationCodes") -local VoiceWebsocketCloseCodes = require("@Enums/VoiceWebsocketCloseCodes") -local WebsocketOperationCodes = require("@Enums/WebsocketOperationCodes") -local WebsocketOperationKeys = require("@Enums/WebsocketOperationKeys") -local WebsocketEvents = require("@Enums/WebsocketEvents") - -local BASE_VOICE_CONNECTION_PORT = 1050 - ---[=[ - @class Network.DiscordVoiceConnection - - The `DiscordVoiceConnection` class handles the voice connection functionalities for Discord-Luau. This class is responsible for managing the lifecycle of a voice connection, handling websocket operations, and ensuring proper communication with Discord's voice servers. - - :::caution - This class is internal and should not be used directly by developers. Instead, use the provided public interfaces and methods to interact with voice connections. - ::: -]=] -local DiscordVoiceConnection = {} - -DiscordVoiceConnection.Internal = {} -DiscordVoiceConnection.Interface = {} -DiscordVoiceConnection.Prototype = {} - -DiscordVoiceConnection.Prototype.type = "DiscordVoiceConnection" - -DiscordVoiceConnection.Prototype.activeConnections = 0 - ---[=[ - Observes and handles errors from the voice websocket. - - @private - @method observeWebsocketErrors - @param self DiscordVoiceConnection - @within Network.DiscordVoiceConnection -]=] -function DiscordVoiceConnection.Prototype.observeWebsocketErrors(self: DiscordVoiceConnection) - self.voiceWebsocket.onSocketDead:connect(function(closeCode) - if closeCode == VoiceWebsocketCloseCodes.UnknownError then - self:reconnectAsync() - elseif closeCode == VoiceWebsocketCloseCodes.UnknownOperation then - self:reconnectAsync() - elseif closeCode == VoiceWebsocketCloseCodes.DecodeError then - self:reconnectAsync() - elseif closeCode == VoiceWebsocketCloseCodes.NotAuthenticated then - self.reporter:error( - `Something has gone terribly wrong with Discord-Luau.. please create an issue! [{closeCode}]` - ) - elseif closeCode == VoiceWebsocketCloseCodes.AuthenticationFailed then - self.reporter:warn(`Failed to authenticate with the Discord Voice Gateway, is your token correct?`) - elseif closeCode == VoiceWebsocketCloseCodes.AlreadyAuthenticated then - self:reconnectAsync() - elseif closeCode == VoiceWebsocketCloseCodes.SessionTimedOut then - self:reconnectAsync() - elseif closeCode == VoiceWebsocketCloseCodes.ServerNotFound then - self.reporter:error(`Voice Websocket unable to find server; '{self.guildId}'`) - elseif closeCode == VoiceWebsocketCloseCodes.UnknownProtocol then - self.reporter:error( - `Something has gone terribly wrong with Discord-Luau.. please create an issue! [{closeCode}]` - ) - elseif closeCode == VoiceWebsocketCloseCodes.Disconnected then - self.reporter:debug(`Disconnected from Voice websocket`) - elseif closeCode == VoiceWebsocketCloseCodes.VoiceServerCrashed then - self:reconnectAsync() - elseif closeCode == VoiceWebsocketCloseCodes.UnknownEncryptionMode then - self.reporter:error( - `Something has gone terribly wrong with Discord-Luau.. please create an issue! [{closeCode}]` - ) - end - end) -end - ---[=[ - Observes and handles operations received from the voice websocket. - - @private - @method observeWebsocketOperations - @param self DiscordVoiceConnection - @within Network.DiscordVoiceConnection -]=] -function DiscordVoiceConnection.Prototype.observeWebsocketOperations(self: DiscordVoiceConnection) - self.voiceWebsocket.onOperationRecv:connect(function(operationCode, operationData) - if operationCode == VoiceWebsocketOperationCodes.Hello then - Task.wait(math.random()) - - self.heartbeatInterval = operationData.heartbeatInterval - self:heartbeatAsync(true):after(function() - self:heartbeatIn(self.heartbeatInterval) - end) - elseif operationCode == VoiceWebsocketOperationCodes.HeartbeatACK then - self.heartbeatAck = true - self.heartbeatPing = os.clock() - self.heartbeatClockTime :: number - - if operationData ~= self.heartbeatNonce then - self.reporter:warn(`HeartbeatACK - nonce received was incorrect`) - end - - self.reporter:debug(`HeartbeatACK - Ping: {self.heartbeatPing}`) - elseif operationCode == VoiceWebsocketOperationCodes.Ready then - DiscordVoiceConnection.Prototype.activeConnections += 1 - - local thread = coroutine.running() - - self.voiceUDP = ( - DiscordUDP.new( - `0.0.0.0:{BASE_VOICE_CONNECTION_PORT + DiscordVoiceConnection.Prototype.activeConnections}` - ) :: unknown - ) :: DiscordUDP.DiscordUDP - - self.voiceUDP.onMessageRecv:once(function(packetData: string) - local responseData = { string.unpack(">I2I2I4c64I2", packetData) } - - coroutine.resume(thread, responseData[4]) - end) - - self.voiceUDP:connectAsync(`{operationData.ip}:{operationData.port}`):await() - self.voiceUDP:sendAsync( - string.pack(`>I2I2I4c64I2`, 1, 70, operationData.ssrc, operationData.ip, operationData.port) - ) - - local publicIp = coroutine.yield() - - self.voiceWebsocket - :sendAsync({ - [WebsocketOperationKeys.OperationCode] = VoiceWebsocketOperationCodes.SelectProtocol, - [WebsocketOperationKeys.Data] = { - protocol = "udp", - data = { - address = publicIp, - port = BASE_VOICE_CONNECTION_PORT + DiscordVoiceConnection.Prototype.activeConnections, - mode = "xsalsa20_poly1305", - }, - }, - }) - :await() - elseif operationCode == VoiceWebsocketOperationCodes.SessionDescription then - self.encryptionKey = buffer.create(64) - self.mediaSessionId = operationData.mediaSessionId - - for index = 1, 32 do - buffer.writei16(self.encryptionKey, index, operationData.secretKey[tostring(index)]) - end - end - end) -end - ---[=[ - Sends a heartbeat to the voice websocket and manages the heartbeat acknowledgements. - - @private - @method heartbeatAsync - @param self DiscordVoiceConnection - @param ignoreHeartbeatAck boolean? -- Whether to ignore heartbeat acknowledgements. - @within Network.DiscordVoiceConnection - @return Vendor.Future -]=] -function DiscordVoiceConnection.Prototype.heartbeatAsync(self: DiscordVoiceConnection, ignoreHeartbeatAck: boolean?) - return Future.try(function() - if not ignoreHeartbeatAck then - if not self.heartbeatAck then - self.reporter:warn(`Discord Websocket state has become zombified, attempting to reconnect!`) - - self:reconnectAsync():await() - end - - self.heartbeatAck = nil - end - - self.heartbeatClockTime = os.clock() - self.heartbeatNonce = math.random(1_000_000_000) - - self.voiceWebsocket - :sendAsync({ - [WebsocketOperationKeys.OperationCode] = VoiceWebsocketOperationCodes.Heartbeat, - [WebsocketOperationKeys.Data] = self.heartbeatNonce, - }) - :await() - end) -end - ---[=[ - Schedules the next heartbeat to be sent after a specified interval. - - @private - @method heartbeatIn - @param self DiscordVoiceConnection - @param milliseconds number -- The interval in milliseconds before the next heartbeat is sent. - @within Network.DiscordVoiceConnection -]=] -function DiscordVoiceConnection.Prototype.heartbeatIn(self: DiscordVoiceConnection, milliseconds: number) - if self.heartbeatTask then - Task.cancel(self.heartbeatTask) - end - - self.heartbeatTask = Task.delay(milliseconds / 1000, function() - self.heartbeatTask = nil - - self:heartbeatAsync():await() - self:heartbeatIn(milliseconds) - end) -end - ---[=[ - Connects to a voice channel asynchronously. - - @private - @method connectAsync - @param self DiscordVoiceConnection - @param guildId string - @param channelId string - @within Network.DiscordVoiceConnection - @return Vendor.Future -]=] -function DiscordVoiceConnection.Prototype.connectAsync(self: DiscordVoiceConnection, guildId: string, channelId: string) - return Future.try(function() - local guildObject = self.discordClient:fetchGuildAsync(guildId):await() - local websocket: DiscordWebsocket.DiscordWebsocket = - self.discordClient.discordShards[guildObject.shardId].discordWebsocket - local connection - - connection = self.discordClient.discordShards[guildObject.shardId].onEvent:connect( - function(eventName, data: { [any]: any }) - if eventName == WebsocketEvents.VoiceServerUpdate then - if data.guildId == guildId then - self.voiceServerUpdate = data - end - elseif eventName == WebsocketEvents.VoiceStateUpdate then - if data.userId == self.discordClient.discordUser.id then - self.voiceStateUpdate = data - end - end - end - ) - - websocket - :sendAsync({ - [WebsocketOperationKeys.OperationCode] = WebsocketOperationCodes.VoiceStateUpdate, - [WebsocketOperationKeys.Data] = { - guild_id = guildId, - channel_id = channelId, - self_mute = false, - self_deaf = false, - }, - }) - :await() - - repeat - Task.wait(0.5) - until self.voiceServerUpdate and self.voiceStateUpdate - - connection:disconnect() - - self.guildId = guildId - self.channelId = channelId - - self.voiceWebsocket:connectAsync(`wss://{self.voiceServerUpdate.endpoint}/?v=4`):await() - self.voiceWebsocket - :sendAsync({ - [WebsocketOperationKeys.OperationCode] = VoiceWebsocketOperationCodes.Identify, - [WebsocketOperationKeys.Data] = { - server_id = guildId, - user_id = self.discordClient.discordUser.id, - session_id = self.voiceStateUpdate.sessionId, - token = self.voiceServerUpdate.token, - }, - }) - :await() - - return true - end) -end - ---[=[ - Resumes a voice connection asynchronously. - - @private - @method resumeAsync - @param self DiscordVoiceConnection - @within Network.DiscordVoiceConnection - @return Vendor.Future -]=] -function DiscordVoiceConnection.Prototype.resumeAsync(self: DiscordVoiceConnection) - return Future.try(function() - self.voiceWebsocket:connectAsync(`wss://{self.voiceServerUpdate.endpoint}/?v=4`):await() - self.voiceWebsocket - :sendAsync({ - [WebsocketOperationKeys.OperationCode] = VoiceWebsocketOperationCodes.Resume, - [WebsocketOperationKeys.Data] = { - server_id = self.guildId, - session_id = self.voiceStateUpdate.sessionId, - token = self.voiceServerUpdate.token, - }, - }) - :await() - - return true - end) -end - ---[=[ - Reconnects to the voice websocket asynchronously. - - @private - @method reconnectAsync - @param self DiscordVoiceConnection - @within Network.DiscordVoiceConnection - @return Vendor.Future -]=] -function DiscordVoiceConnection.Prototype.reconnectAsync(self: DiscordVoiceConnection) - if self.heartbeatTask then - Task.cancel(self.heartbeatTask) - end - - return Future.try(function() - self.voiceWebsocket:disconnectAsync(tostring(1005)):await() - - Task.wait(math.random()) - - self:resumeAsync():await() - end) -end - ---[=[ - Disconnects from the voice websocket asynchronously. - - @private - @method disconnectAsync - @param self DiscordVoiceConnection - @within Network.DiscordVoiceConnection - @return Vendor.Future -]=] -function DiscordVoiceConnection.Prototype.disconnectAsync(self: DiscordVoiceConnection) - return Future.try(function() - if self.heartbeatTask then - Task.cancel(self.heartbeatTask) - end - - local guildObject = self.discordClient:fetchGuildAsync(self.guildId):await() - local websocket: DiscordWebsocket.DiscordWebsocket = - self.discordClient.discordShards[guildObject.shardId].discordWebsocket - - websocket - :sendAsync({ - [WebsocketOperationKeys.OperationCode] = WebsocketOperationCodes.VoiceStateUpdate, - [WebsocketOperationKeys.Data] = { - guild_id = self.guildId, - }, - }) - :await() - - self.voiceWebsocket:disconnectAsync(tostring(1000)):await() - - DiscordVoiceConnection.Prototype.activeConnections -= 1 - end) -end - ---[=[ - Creates a new instance of `DiscordVoiceConnection`. - - @private - @function new - @param discordClient any -- The Discord client. - @param id string -- The ID for this connection. - @within Network.DiscordVoiceConnection - @return DiscordVoiceConnection -]=] -function DiscordVoiceConnection.Interface.new(discordClient: any, id: string): DiscordVoiceConnection - --[=[ - @prop voiceWebsocket Network.DiscordWebsocket - @within Network.DiscordVoiceConnection - ]=] - - --[=[ - @prop voiceUDP Network.VoiceUDP - @within Network.DiscordVoiceConnection - ]=] - - --[=[ - @prop reporter Vendor.Console - @within Network.DiscordVoiceConnection - ]=] - - --[=[ - @prop heartbeatTask thread? - @within Network.DiscordVoiceConnection - ]=] - - --[=[ - @prop resumeSessionId string - @within Network.DiscordVoiceConnection - ]=] - - --[=[ - @prop heartbeatInterval number - @within Network.DiscordVoiceConnection - ]=] - - --[=[ - @prop heartbeatAck boolean? - @within Network.DiscordVoiceConnection - ]=] - - --[=[ - @prop heartbeatPing number? - @within Network.DiscordVoiceConnection - ]=] - - --[=[ - @prop heartbeatNonce number? - @within Network.DiscordVoiceConnection - ]=] - - --[=[ - @prop heartbeatClockTime number? - @within Network.DiscordVoiceConnection - ]=] - - --[=[ - @prop encryptionKey buffer - @within Network.DiscordVoiceConnection - ]=] - - --[=[ - @prop mediaSessionId string - @within Network.DiscordVoiceConnection - ]=] - - --[=[ - @prop channelId string - @within Network.DiscordVoiceConnection - ]=] - - --[=[ - @prop guildId string - @within Network.DiscordVoiceConnection - ]=] - - --[=[ - @prop voiceServerUpdate { endpoint: string, token: string, guildId: string } - @within Network.DiscordVoiceConnection - ]=] - - --[=[ - @prop voiceStateUpdate { sessionId: string } - @within Network.DiscordVoiceConnection - ]=] - - --[=[ - @prop onEvent Vendor.Signal - @within Network.DiscordVoiceConnection - ]=] - local self = ( - Construct({ - voiceWebsocket = DiscordWebsocket.new(), - discordClient = discordClient, - - reporter = Console.new(`🖲️ DiscordVoiceConnection {id}`), - - heartbeatTask = nil, - resumeSessionId = nil, - heartbeatInterval = nil, - - onEvent = Signal.new(), - }, DiscordVoiceConnection.Prototype) :: unknown - ) :: DiscordVoiceConnection - - self:observeWebsocketOperations() - - return self -end - -export type DiscordVoiceConnection = typeof(DiscordVoiceConnection.Prototype) & { - voiceWebsocket: DiscordWebsocket.DiscordWebsocket, - voiceUDP: DiscordUDP.DiscordUDP, - discordClient: any, - - reporter: Console.Console, - - heartbeatTask: thread?, - resumeSessionId: string, - heartbeatInterval: number, - - heartbeatAck: boolean?, - heartbeatPing: number?, - heartbeatNonce: number?, - heartbeatClockTime: number?, - - encryptionKey: buffer, - mediaSessionId: string, - - channelId: string, - guildId: string, - - voiceServerUpdate: { - endpoint: string, - token: string, - guildId: string, - }, - - voiceStateUpdate: { - sessionId: string, - }, - - onEvent: unknown, -} - -return DiscordVoiceConnection.Interface diff --git a/Package/Classes/Network/DiscordWebsocket.luau b/Package/Classes/Network/DiscordWebsocket.luau deleted file mode 100644 index 22d6430..0000000 --- a/Package/Classes/Network/DiscordWebsocket.luau +++ /dev/null @@ -1,332 +0,0 @@ -local Net = require("@Std/Net") -local Serde = require("@Std/Serde") -local Task = require("@Std/Task") - -local Future = require("@Vendor/Future") -local Signal = require("@Vendor/Signal") -local Console = require("@Vendor/Console") - -local Construct = require("@Utils/Construct") - -local EventIn = require("@Network/Middleware/EventIn") -local EventOut = require("@Network/Middleware/EventOut") - -local WebsocketBuffer = require("@Network/WebsocketBuffer") - -local WebsocketOperationKeys = require("@Enums/WebsocketOperationKeys") -local WebsocketOperationCodes = require("@Enums/WebsocketOperationCodes") - -local ZLIB_SUFFIX_START = "\120" -local ZLIB_SUFFIX_END = "\84" - ---[=[ - @class Network.DiscordWebsocket - - The `DiscordWebsocket` class manages the websocket connection to Discord. It handles sending and receiving messages, managing the connection state, and processing incoming and outgoing events. - - :::caution - This class is internal and should not be used directly by developers. Instead, use the provided public interfaces and methods to interact with the Discord websocket. - ::: -]=] -local DiscordWebsocket = {} - -DiscordWebsocket.Interface = {} -DiscordWebsocket.Prototype = {} - -DiscordWebsocket.Prototype.type = "DiscordWebsocket" - ---[=[ - Sends a data packet asynchronously through the websocket. - - @private - @method sendAsync - @param self DiscordWebsocket - @param dataPacket table -- The data packet to send. - @within Network.DiscordWebsocket - @return Vendor.Future -]=] -function DiscordWebsocket.Prototype.sendAsync(self: DiscordWebsocket, dataPacket: { [any]: any }) - local traceback = debug.traceback() - - return Future.try(function() - if not self.socketInstance then - error(`No WebSocket available!`) - end - - local messageSendOperationSuccessful, result = pcall(function() - return ( - self.socketInstance.send(Serde.encode("json", self.gatewayEventOut:processJSON(dataPacket))) :: unknown - ) :: buffer - end) - - if messageSendOperationSuccessful then - self.reporter:debug(`Discord Websocket Message Sent: {dataPacket[WebsocketOperationKeys.OperationCode]}`) - else - local resultType: string = typeof(result) - local messageResult - - if resultType == "userdata" then - local success = pcall(function() - messageResult = buffer.tostring(result) - end) - - if not success then - messageResult = tostring(result) - end - end - - self.reporter:warn(`Socket error: {string.split(messageResult, "\n")[1]}, traceback:\n{traceback}`) - - self.socketActive = false - self.onSocketDead:fire(self.socketInstance.closeCode) - end - end) -end - ---[=[ - Connects to a websocket URL asynchronously. - - @private - @method connectAsync - @param self DiscordWebsocket - @param websocketUrl string -- The URL to connect to. - @within Network.DiscordWebsocket - @return Vendor.Future -]=] -function DiscordWebsocket.Prototype.connectAsync(self: DiscordWebsocket, websocketUrl: string) - return Future.try(function() - self.socketUrl = websocketUrl - self.socketRequestSuccess, self.socketInstance = pcall(Net.socket, websocketUrl) - - if not self.socketRequestSuccess then - error(self.socketInstance) - end - - self.socketActive = true - self.socketThread = Task.spawn(function() - while self.socketActive do - if not self.socketInstance then - return - end - - if self.socketInstance.closeCode then - self.socketActive = false - self.onSocketDead:fire(self.socketInstance.closeCode) - else - local success, socketMessage = pcall(self.socketInstance.next) - - if not success or not socketMessage then - self.socketActive = false - self.onSocketDead:fire(self.socketInstance.closeCode) - - continue - end - - self.onMessageRecv:fire(socketMessage) - end - end - end) - end) -end - ---[=[ - Disconnects from the websocket asynchronously. - - @private - @method disconnectAsync - @param self DiscordWebsocket - @param closingCode string -- The closing code for the disconnection. - @within Network.DiscordWebsocket - @return Vendor.Future -]=] -function DiscordWebsocket.Prototype.disconnectAsync(self: DiscordWebsocket, closingCode: string) - return Future.try(function() - if not self.socketInstance then - return - end - - if self.socketActive then - xpcall(function() - self.socketInstance.close(tonumber(closingCode)) - end, function(source: string) - self.reporter:warn(`Failed to close websocket: '{source}'`) - end) - end - - self.socketActive = false - self.onSocketDead:fire(tonumber(closingCode)) - end) -end - ---[=[ - Creates a new instance of `DiscordWebsocket`. - - @private - @function new - @return DiscordWebsocket - @within Network.DiscordWebsocket -]=] -function DiscordWebsocket.Interface.new() - --[=[ - @prop websocketWebsocketBuffer Network.WebsocketBuffer - @within Network.DiscordWebsocket - ]=] - - --[=[ - @prop socketActiveChanged Vendor.Signal - @within Network.DiscordWebsocket - ]=] - - --[=[ - @prop socketActive boolean - @within Network.DiscordWebsocket - ]=] - - --[=[ - @prop gatewayEventIn Network.EventIn - @within Network.DiscordWebsocket - ]=] - - --[=[ - @prop gatewayEventOut Network.EventOut - @within Network.DiscordWebsocket - ]=] - - --[=[ - @prop onMessageRecv Vendor.Signal - @within Network.DiscordWebsocket - ]=] - - --[=[ - @prop onSocketDead Vendor.Signal - @within Network.DiscordWebsocket - ]=] - - --[=[ - @prop onOperationRecv Vendor.Signal - @within Network.DiscordWebsocket - ]=] - - --[=[ - @prop reporter Vendor.Console - @within Network.DiscordWebsocket - ]=] - - --[=[ - @prop socketUrl STRING? - @within Network.DiscordWebsocket - ]=] - - --[=[ - @prop socketInstance WebSocket - @within Network.DiscordWebsocket - ]=] - - --[=[ - @prop socketRequestSuccess boolean? - @within Network.DiscordWebsocket - ]=] - - --[=[ - @prop socketThread thread? - @within Network.DiscordWebsocket - ]=] - - local self = Construct({ - websocketWebsocketBuffer = WebsocketBuffer.new(), - - gatewayEventIn = EventIn.new(), - gatewayEventOut = EventOut.new(), - - onMessageRecv = Signal.new(), - onSocketDead = Signal.new(), - onOperationRecv = Signal.new(), - - reporter = Console.new("🪁 DiscordWebsocket"), - - socketActiveChanged = Signal.new(), - socketActive = false, - - socketUrl = nil, - socketInstance = nil, - - socketRequestSuccess = nil, - socketThread = nil, - }, DiscordWebsocket.Prototype) - - self.socketActiveChanged:connect(function(state) - local connectedLabel = state and "Connected" or "Disconnected" - - local socketUrl = self.socketUrl and `[{self.socketUrl}]` or "" - local closeCode = self.socketInstance and self.socketInstance.closeCode and `[{self.socketInstance.closeCode}]` - or "" - - self.reporter:debug(`Discord Websocket {connectedLabel} {socketUrl} {closeCode}`) - end) - - self.onMessageRecv:connect(function(discordPacket) - local isJSON, dataPacket = pcall(function() - return Serde.decode("json", discordPacket) - end) - - if not isJSON then - self.websocketWebsocketBuffer:add(discordPacket) - - if string.sub(discordPacket, 1, 1) ~= ZLIB_SUFFIX_START then - return - end - - if string.sub(discordPacket, 4, 4) ~= ZLIB_SUFFIX_END then - return - end - - local encodedDiscordPacket = self.websocketWebsocketBuffer:flush() - local decodedDiscordPacket = Serde.decompress("zlib", encodedDiscordPacket) - - dataPacket = Serde.decode("json", decodedDiscordPacket) - end - - if dataPacket[WebsocketOperationKeys.OperationCode] == WebsocketOperationCodes.Dispatch then - self.reporter:debug( - `Discord Websocket Message Received: {dataPacket[WebsocketOperationKeys.OperationCode]} - {dataPacket[WebsocketOperationKeys.EventName]}` - ) - else - self.reporter:debug( - `Discord Websocket Message Received: {dataPacket[WebsocketOperationKeys.OperationCode]}` - ) - end - - self.onOperationRecv:fire( - dataPacket[WebsocketOperationKeys.OperationCode], - self.gatewayEventIn:processJSON(dataPacket[WebsocketOperationKeys.Data]), - dataPacket[WebsocketOperationKeys.EventName], - dataPacket[WebsocketOperationKeys.Sequence] - ) - end) - - return self -end - -export type DiscordWebsocket = typeof(DiscordWebsocket.Prototype) & { - websocketWebsocketBuffer: WebsocketBuffer.WebsocketBuffer, - - socketActiveChanged: Signal.Signal, - socketActive: boolean, - - gatewayEventIn: EventIn.EventIn, - gatewayEventOut: EventOut.EventOut, - - onMessageRecv: Signal.Signal, - onSocketDead: Signal.Signal, - onOperationRecv: Signal.Signal, - - reporter: Console.Console, - - socketUrl: string?, - socketInstance: Net.WebSocket?, - - socketRequestSuccess: boolean?, - socketThread: thread?, -} - -return DiscordWebsocket.Interface diff --git a/Package/Classes/Network/HTTPRatelimit.luau b/Package/Classes/Network/HTTPRatelimit.luau deleted file mode 100644 index 5a38341..0000000 --- a/Package/Classes/Network/HTTPRatelimit.luau +++ /dev/null @@ -1,114 +0,0 @@ -local Task = require("@Std/Task") - -local Construct = require("@Utils/Construct") - ---[=[ - @class Network.HTTPRatelimit - - The `HTTPRatelimit` class manages the rate limiting for HTTP requests. It tracks the remaining requests that can be made and handles the reset of these limits after a specified duration. - - :::caution - This class is internal and should not be used directly by developers. Instead, use the provided public interfaces and methods to manage rate limits. - ::: -]=] -local HTTPRatelimit = {} - -HTTPRatelimit.Prototype = {} -HTTPRatelimit.Interface = {} - -HTTPRatelimit.Prototype.type = "HTTPRatelimit" - ---[=[ - Sets the remaining number of requests that can be made. - - @private - @method setRemaining - @param self HTTPRatelimit - @param remaining number -- The number of remaining requests. - @within Network.HTTPRatelimit -]=] -function HTTPRatelimit.Prototype.setRemaining(self: HTTPRatelimit, remaining: number) - self.remaining = remaining -end - ---[=[ - Resets the remaining requests after a specified number of seconds. - - @private - @method resetAfter - @param self HTTPRatelimit - @param seconds number -- The number of seconds after which to reset the remaining requests. - @within Network.HTTPRatelimit -]=] -function HTTPRatelimit.Prototype.resetAfter(self: HTTPRatelimit, seconds: number) - Task.delay(seconds, function() - self.remaining = math.min(self.remaining + 1, self.limit) - end) -end - ---[=[ - Sets the maximum number of requests that can be made. - - @private - @method setLimit - @param self HTTPRatelimit - @param limit number -- The maximum number of requests. - @within Network.HTTPRatelimit -]=] -function HTTPRatelimit.Prototype.setLimit(self: HTTPRatelimit, limit: number) - self.limit = limit -end - ---[=[ - Checks if the rate limit has been consumed. - - @private - @method isConsumed - @param self HTTPRatelimit - @within Network.HTTPRatelimit - @return boolean -- Returns `true` if the rate limit is consumed, `false` otherwise. -]=] -function HTTPRatelimit.Prototype.isConsumed(self: HTTPRatelimit) - return self.remaining <= 0 -end - ---[=[ - Creates a new instance of `HTTPRatelimit`. - - @private - @function new - @param remaining number? -- The initial number of remaining requests. - @param limit number? -- The initial limit of requests. - @within Network.HTTPRatelimit - @return HTTPRatelimit -]=] -function HTTPRatelimit.Interface.new(remaining: number?, limit: number?) - --[=[ - @prop remaining number - @within Network.HTTPRatelimit - ]=] - - --[=[ - @prop limit number - @within Network.HTTPRatelimit - ]=] - - --[=[ - @prop resetAfterThread thread? - @within Network.HTTPRatelimit - ]=] - - return Construct({ - remaining = remaining or 0, - limit = limit or 0, - }, HTTPRatelimit.Prototype) -end - -export type HTTPRatelimit = typeof(HTTPRatelimit.Prototype) & { - remaining: number, - limit: number, - - resetAfterThread: thread?, -} - -return HTTPRatelimit.Interface diff --git a/Package/Classes/Network/HTTPScheduler.luau b/Package/Classes/Network/HTTPScheduler.luau deleted file mode 100644 index f919645..0000000 --- a/Package/Classes/Network/HTTPScheduler.luau +++ /dev/null @@ -1,235 +0,0 @@ -local Task = require("@Std/Task") - -local Console = require("@Vendor/Console") - -local Construct = require("@Utils/Construct") - -local DEFAULT_QUEUE_FREQUENCY = 0 - ---[=[ - @class Network.HTTPScheduler - - The `HTTPScheduler` class manages the scheduling of HTTP tasks. It allows for the addition, removal, and execution of tasks at a specified frequency. - - :::caution - This class is internal and should not be used directly by developers. Instead, use the provided public interfaces and methods to manage HTTP scheduling. - ::: -]=] -local HTTPScheduler = {} - -HTTPScheduler.Prototype = {} -HTTPScheduler.Interface = {} - -HTTPScheduler.Prototype.type = "HTTPScheduler" - ---[=[ - @within Network.HTTPScheduler - @method spawnWorker - @private - Spawns a new worker task that cycles through scheduled tasks at the specified frequency. - - @param self HTTPScheduler -]=] -function HTTPScheduler.Prototype.spawnWorker(self: HTTPScheduler) - table.insert( - self.workers, - Task.spawn(function() - while true do - self:cycle() - - Task.wait(self.frequency) - end - end) - ) -end - ---[=[ - @within Network.HTTPScheduler - @method stopWorker - @private - Stops and removes a worker task. - - @param self HTTPScheduler -]=] -function HTTPScheduler.Prototype.stopWorker(self: HTTPScheduler) - local thread = table.remove(self.workers, 1) - - if not thread then - return - end - - Task.cancel(thread) -end - ---[=[ - @within Network.HTTPScheduler - @method setFrequency - @private - Sets the frequency at which tasks are cycled. - - @param self HTTPScheduler - @param frequency number -- The frequency in seconds. -]=] -function HTTPScheduler.Prototype.setFrequency(self: HTTPScheduler, frequency: number) - self.frequency = frequency -end - ---[=[ - @within Network.HTTPScheduler - @method cycle - @private - Processes the next task in the stack. - - @param self HTTPScheduler -]=] -function HTTPScheduler.Prototype.cycle(self: HTTPScheduler) - local item = table.remove(self.stack, 1) - - if not item then - return - end - - table.insert(self.processing, true) - - local values = { pcall(item) } - - if not values[1] then - self.reporter:warn(`HTTP Scheduled call failed: '{values[2]}'`) - end - - table.remove(self.processing, 1) -end - ---[=[ - @within Network.HTTPScheduler - @method isActive - @private - Checks if there are any tasks currently being processed. - - @param self HTTPScheduler - @return boolean -- Returns `true` if there are tasks being processed, `false` otherwise. -]=] -function HTTPScheduler.Prototype.isActive(self: HTTPScheduler) - return #self.processing > 0 -end - ---[=[ - @within Network.HTTPScheduler - @method setLimit - @private - Sets the limit for the number of tasks that can be scheduled. - - @param self HTTPScheduler - @param limit number -- The maximum number of tasks. -]=] -function HTTPScheduler.Prototype.setLimit(self: HTTPScheduler, limit: number) - self.limit = limit -end - ---[=[ - @within Network.HTTPScheduler - @method removeTask - @private - Removes a task from the scheduler. - - @param self HTTPScheduler - @param object function -- The task function to remove. -]=] -function HTTPScheduler.Prototype.removeTask(self: HTTPScheduler, object: (...T) -> ...T) - local taskIndex = table.find(self.stack, object) - - if taskIndex then - table.remove(self.stack, taskIndex) - end -end - ---[=[ - Adds a task to the scheduler. - - @private - @method addTask - @param self HTTPScheduler - @param object function -- The task function to add. - @within Network.HTTPScheduler - @return any -- Returns the result of the task function. -]=] -function HTTPScheduler.Prototype.addTask(self: HTTPScheduler, object: (...T) -> ...T) - local thread = coroutine.running() - - table.insert(self.stack, function() - coroutine.resume(thread, object()) - end) - - return coroutine.yield() -end - ---[=[ - Creates a new instance of `HTTPScheduler`. - - @private - @function new - @param threadCount number? -- The number of worker threads to spawn. - @within Network.HTTPScheduler - @return HTTPScheduler -]=] -function HTTPScheduler.Interface.new(threadCount: number?): HTTPScheduler - --[=[ - @prop stack { () -> () } - @within Network.HTTPScheduler - ]=] - - --[=[ - @prop processing { boolean } - @within Network.HTTPScheduler - ]=] - - --[=[ - @prop workers { thread } - @within Network.HTTPScheduler - ]=] - - --[=[ - @prop reporter Vendor.Console - @within Network.HTTPScheduler - ]=] - - --[=[ - @prop limit number? - @within Network.HTTPScheduler - ]=] - - --[=[ - @prop frequency number - @within Network.HTTPScheduler - ]=] - - local self = Construct({ - stack = {}, - processing = {}, - workers = {}, - - reporter = Console.new("⚠️ HTTP Scheduler"), - - limit = nil, - frequency = DEFAULT_QUEUE_FREQUENCY, - }, HTTPScheduler.Prototype) - - for _ = 1, threadCount or 0 do - self:spawnWorker() - end - - return self -end - -export type HTTPScheduler = typeof(HTTPScheduler.Prototype) & { - stack: { () -> () }, - processing: { boolean }, - workers: { thread }, - - reporter: Console.Console, - - limit: number?, - frequency: number, -} - -return HTTPScheduler.Interface diff --git a/Package/Classes/Network/Middleware/EventIn.luau b/Package/Classes/Network/Middleware/EventIn.luau deleted file mode 100644 index a871d15..0000000 --- a/Package/Classes/Network/Middleware/EventIn.luau +++ /dev/null @@ -1,79 +0,0 @@ -local Construct = require("@Utils/Construct") - ---[=[ - @class Network.Middleware.EventIn - - The `EventIn` class processes incoming events, converting JSON keys to camelCase format for consistency within the application. - - :::caution - This class is internal and should not be used directly by developers. Instead, use the provided public interfaces and methods to handle incoming events. - ::: -]=] -local EventIn = {} - -EventIn.Prototype = {} -EventIn.Interface = {} - -EventIn.Prototype.type = "EventIn" - ---[=[ - Converts JSON keys from snake_case to camelCase. - - @private - @method toCamelCase - @param self EventIn - @param json table -- The JSON object to convert. - @within Network.Middleware.EventIn - @return table -- The converted JSON object. -]=] -function EventIn.Prototype.toCamelCase(self: EventIn, json: { [string]: any }) - local source = {} - - for illegalIndex, value in json do - local legalIndex = string.gsub(illegalIndex, "(_%S)", function(illegal: string) - return string.upper(string.sub(illegal, 2, 2)) - end) - - if typeof(value) == "table" then - value = self:toCamelCase(value) - end - - source[legalIndex] = value - end - - return source -end - ---[=[ - Processes incoming JSON data, converting keys to camelCase if the data is a table. - - @private - @method processJSON - @param self EventIn - @param data any -- The data to process. - @within Network.Middleware.EventIn - @return any -- The processed data. -]=] -function EventIn.Prototype.processJSON(self: EventIn, data: any) - if typeof(data) == "table" then - return self:toCamelCase(data) - end - - return data -end - ---[=[ - Creates a new instance of `EventIn`. - - @private - @function new - @within Network.Middleware.EventIn - @return EventIn -]=] -function EventIn.Interface.new() - return Construct({}, EventIn.Prototype) -end - -export type EventIn = typeof(EventIn.Prototype) - -return EventIn.Interface diff --git a/Package/Classes/Network/Middleware/EventOut.luau b/Package/Classes/Network/Middleware/EventOut.luau deleted file mode 100644 index 9033134..0000000 --- a/Package/Classes/Network/Middleware/EventOut.luau +++ /dev/null @@ -1,47 +0,0 @@ -local Construct = require("@Utils/Construct") - ---[=[ - @class Network.Middleware.EventOut - - The `EventOut` class processes outgoing events. Currently, it acts as a placeholder for any future processing that may be required for outgoing JSON data. - - :::caution - This class is internal and should not be used directly by developers. Instead, use the provided public interfaces and methods to handle outgoing events. - ::: -]=] -local EventOut = {} - -EventOut.Prototype = {} -EventOut.Interface = {} - -EventOut.Prototype.type = "EventOut" - ---[=[ - Processes outgoing JSON data. Currently, this method returns the data unchanged. - - @private - @method processJSON - @param self EventOut - @param data any -- The data to process. - @within Network.Middleware.EventOut - @return any -- The processed data. -]=] -function EventOut.Prototype.processJSON(_: EventOut, data: any) - return data -end - ---[=[ - Creates a new instance of `EventOut`. - - @private - @function new - @within Network.Middleware.EventOut - @return EventOut -]=] -function EventOut.Interface.new() - return Construct({}, EventOut.Prototype) -end - -export type EventOut = typeof(EventOut.Prototype) - -return EventOut.Interface diff --git a/Package/Classes/Network/Resolvable.luau b/Package/Classes/Network/Resolvable.luau deleted file mode 100644 index 1bc24aa..0000000 --- a/Package/Classes/Network/Resolvable.luau +++ /dev/null @@ -1,140 +0,0 @@ -local Construct = require("@Utils/Construct") - -local ResolvableType = require("@Enums/ResolvableType") - -local EventOut = require("@Network/Middleware/EventOut") -local EventIn = require("@Network/Middleware/EventIn") - ---[=[ - @class Network.Resolvable - - The `Resolvable` class manages the scheduling of HTTP tasks. It allows for the addition, removal, and execution of tasks at a specified frequency. - - :::caution - This class is internal and should not be used directly by developers. Instead, use the provided public interfaces and methods to manage HTTP scheduling. - ::: -]=] -local Resolvable = {} - -Resolvable.Prototype = {} -Resolvable.Interface = {} - -Resolvable.Prototype.type = "Resolvable" - ---[=[ - Returns a dictionary of headers this resolvable requires in order to successfuly be processed by the Discord API - - @private - @method headers - @within Network.Resolvable - @return { [string]: string } -]=] -function Resolvable.Prototype.headers(self: Resolvable): { [string]: string } - return self.metadata and self.metadata.headers or {} -end - ---[=[ - Resolves the data that this Resolvable has, for instance - if this data is JSON, it will be thrown through the EventIn pre-processor. - - @private - @method resolve - @within Network.Resolvable - @return any -]=] -function Resolvable.Prototype.resolve(self: Resolvable, toCamelCase: boolean?) - if self.type == ResolvableType.JSON then - return toCamelCase and self.eventInMiddleware:processJSON(self.data) - or self.eventOutMiddleware:processJSON(self.data) - end - - return self.data -end - ---[=[ - Allows mutation of the data held in the resolvable - - @private - @method update - @within Network.Resolvable - @return any -]=] -function Resolvable.Prototype.update(self: Resolvable, callback: (data: any) -> any) - self.data = callback(self.data) - - return self -end - ---[=[ - Creates a new instance of `Resolvable`. - - @private - @function new - @param threadCount number? -- The number of worker threads to spawn. - @within Network.Resolvable - @return Resolvable -]=] -function Resolvable.Interface.new( - type: string, - data: any, - metadata: { - headers: { [string]: string }, - attachments: { any }, - }? -): Resolvable - --[=[ - @prop data any - @within Network.WebsocketBuffer - ]=] - - --[=[ - @prop type string - @within Network.WebsocketBuffer - ]=] - - --[=[ - @prop eventOutMiddleware Network.EventOut - @within Network.WebsocketBuffer - ]=] - - --[=[ - @prop eventInMiddleware Network.EventIn - @within Network.WebsocketBuffer - ]=] - - --[=[ - @prop metadata { headers: { [string]: string }, attachments: { any } }, - @within Network.WebsocketBuffer - ]=] - - local self = ( - Construct({ - data = data, - type = type, - - eventOutMiddleware = EventOut.new(), - eventInMiddleware = EventIn.new(), - - metadata = metadata or { - headers = {}, - attachments = {}, - }, - }, Resolvable.Prototype) :: unknown - ) :: Resolvable - - return self -end - -export type Resolvable = typeof(Resolvable.Prototype) & { - data: any, - type: ResolvableType.ResolvableType, - - eventOutMiddleware: EventOut.EventOut, - eventInMiddleware: EventIn.EventIn, - - metadata: { - headers: { [string]: string }, - attachments: { any }, - }, -} - -return Resolvable.Interface diff --git a/Package/Classes/Network/WebsocketBuffer.luau b/Package/Classes/Network/WebsocketBuffer.luau deleted file mode 100644 index e59e9f8..0000000 --- a/Package/Classes/Network/WebsocketBuffer.luau +++ /dev/null @@ -1,72 +0,0 @@ -local Construct = require("@Utils/Construct") - ---[=[ - @class Network.WebsocketBuffer - - The `WebsocketBuffer` class manages a buffer for websocket data, allowing for data to be added incrementally and flushed when needed. - - :::caution - This class is internal and should not be used directly by developers. Instead, use the provided public interfaces and methods to manage websocket buffering. - ::: -]=] -local WebsocketBuffer = {} - -WebsocketBuffer.Prototype = {} -WebsocketBuffer.Interface = {} - -WebsocketBuffer.Prototype.type = "WebsocketBuffer" - ---[=[ - Flushes the buffer, returning all accumulated data and resetting the buffer. - - @private - @method flush - @param self WebsocketBuffer - @within Network.WebsocketBuffer - @return string -]=] -function WebsocketBuffer.Prototype.flush(self: WebsocketBuffer) - local data = self.data - - self.data = "" - - return data -end - ---[=[ - Adds data to the buffer. - - @private - @method add - @param self WebsocketBuffer - @within Network.WebsocketBuffer - @param data string -]=] -function WebsocketBuffer.Prototype.add(self: WebsocketBuffer, data: string) - self.data ..= data -end - ---[=[ - Creates a new instance of `WebsocketBuffer`. - - @private - @function new - @within Network.WebsocketBuffer - @return WebsocketBuffer -]=] -function WebsocketBuffer.Interface.new() - --[=[ - @prop data string - @within Network.WebsocketBuffer - ]=] - - return Construct({ - data = "", - }, WebsocketBuffer.Prototype) -end - -export type WebsocketBuffer = typeof(WebsocketBuffer.Prototype) & { - data: string, -} - -return WebsocketBuffer.Interface diff --git a/Package/Classes/Objects/BaseDiscordChannel.luau b/Package/Classes/Objects/BaseDiscordChannel.luau deleted file mode 100644 index 02a215c..0000000 --- a/Package/Classes/Objects/BaseDiscordChannel.luau +++ /dev/null @@ -1,74 +0,0 @@ -local Future = require("@Vendor/Future") - -local Construct = require("@Utils/Construct") - -local DiscordEndpoints = require("@Data/DiscordEndpoints") - ---[=[ - @class Objects.BaseDiscordChannel - - Represents a base discord channel, every discord channel object will extend from this as a base. -]=] - ---[=[ - @prop id string - @within Objects.BaseDiscordChannel -]=] - ---[=[ - @prop type number - @within Objects.BaseDiscordChannel -]=] - -local BaseDiscordChannel = {} - -BaseDiscordChannel.Prototype = {} -BaseDiscordChannel.Interface = {} - -BaseDiscordChannel.Prototype.type = "BaseDiscordChannel" - ---[=[ - Deletes the channel asynchronously. - - @method deleteAsync - @within Objects.BaseDiscordChannel - @return Vendor.Future -]=] -function BaseDiscordChannel.Prototype.deleteAsync(self: BaseDiscordChannel) - return Future.try(function() - return self.discordClient.discordGateway - :deleteAsync(string.format(DiscordEndpoints.BotDeleteChannel, self.id)) - :await() - end) -end - -function BaseDiscordChannel.Interface.new( - discordClient, - channelData: { - id: string, - - [any]: any, - } -): BaseDiscordChannel - local self = Construct({ - discordClient = discordClient, - }, BaseDiscordChannel.Prototype) :: any - - for key, value in channelData do - self[key] = value - end - - return self -end - -export type BaseDiscordChannel = typeof(BaseDiscordChannel.Prototype) & { - discordClient: any, - - id: string, - type: number, - - name: string, - description: string?, -} - -return BaseDiscordChannel.Interface diff --git a/Package/Classes/Objects/BaseDiscordGuildChannel.luau b/Package/Classes/Objects/BaseDiscordGuildChannel.luau deleted file mode 100644 index 2ee5781..0000000 --- a/Package/Classes/Objects/BaseDiscordGuildChannel.luau +++ /dev/null @@ -1,384 +0,0 @@ -local Datetime = require("@Std/Datetime") - -local Future = require("@Vendor/Future") - -local Construct = require("@Utils/Construct") -local Extend = require("@Utils/Extend") - -local Resolvable = require("@Network/Resolvable") - -local BaseDiscordChannel = require("@Objects/BaseDiscordChannel") -local DiscordInvite = require("@Objects/DiscordInvite") - -local PermissionsBuilder = require("@Builders/PermissionsBuilder") -local ChannelBuilder = require("@Builders/ChannelBuilder") - -local DiscordEndpoints = require("@Data/DiscordEndpoints") - -local ResolvableType = require("@Enums/ResolvableType") - ---[=[ - @class Objects.BaseDiscordGuildChannel - - Represents a base object for any/all guild channels. - - @tag inherit Objects.BaseDiscordChannel -]=] - ---[=[ - @prop guildId string - @within Objects.BaseDiscordGuildChannel -]=] - ---[=[ - @prop topic string - @within Objects.BaseDiscordGuildChannel -]=] - ---[=[ - @prop nsfw boolean - @within Objects.BaseDiscordGuildChannel -]=] - ---[=[ - @prop rateLimitPerUser number - @within Objects.BaseDiscordGuildChannel -]=] - ---[=[ - @prop parentId string - @within Objects.BaseDiscordGuildChannel -]=] - ---[=[ - @prop flags number - @within Objects.BaseDiscordGuildChannel -]=] - -local BaseDiscordGuildChannel = {} - -BaseDiscordGuildChannel.Prototype = {} -BaseDiscordGuildChannel.Interface = {} - -BaseDiscordGuildChannel.Prototype.type = "BaseDiscordGuildChannel" - ---[=[ - set overwrites for a specific role permissions for this channel in a discord guild. - - @method overwriteRolePermissionsAsync - @param roleId string - @param allowedPermissions PermissionsBuilder.PermissionsBuilder - @param disallowedPermissions PermissionsBuilder.PermissionsBuilder - @within Objects.BaseDiscordGuildChannel - @return Vendor.Future -]=] -function BaseDiscordGuildChannel.Prototype.overwriteRolePermissionsAsync( - self: BaseDiscordGuildChannel, - roleId: string, - allowedPermissions: PermissionsBuilder.PermissionsBuilder, - disallowedPermissions: PermissionsBuilder.PermissionsBuilder -) - return Future.try(function() - return self.discordClient.discordGateway - :putAsync( - string.format(DiscordEndpoints.BotEditChannelPermissions, self.id, roleId), - Resolvable.new(ResolvableType.JSON, { - allow = allowedPermissions:getValue(), - deny = disallowedPermissions:getValue(), - type = 0, - }) - ) - :await() - end) -end - ---[=[ - set overwrites for a specific members permissions for this channel in a discord guild. - - @method overwriteMemberPermissionssync - @param memberId string - @param allowedPermissions PermissionsBuilder.PermissionsBuilder - @param disallowedPermissions PermissionsBuilder.PermissionsBuilder - @within Objects.BaseDiscordGuildChannel - @return Vendor.Future -]=] -function BaseDiscordGuildChannel.Prototype.overwriteMemberPermissionssync( - self: BaseDiscordGuildChannel, - memberId: string, - allowedPermissions: PermissionsBuilder.PermissionsBuilder, - disallowedPermissions: PermissionsBuilder.PermissionsBuilder -) - return Future.try(function() - return self.discordClient.discordGateway - :putAsync( - string.format(DiscordEndpoints.BotEditChannelPermissions, self.id, memberId), - Resolvable.new(ResolvableType.JSON, { - allow = allowedPermissions:getValue(), - deny = disallowedPermissions:getValue(), - type = 1, - }) - ) - :await() - end) -end - ---[=[ - deletes overwritten permissions for the given id, the given id must be either a role id or a user id - - @method overwriteMemberPermissionssync - @param roleIdOrUserId string - @within Objects.BaseDiscordGuildChannel - @return Vendor.Future -]=] -function BaseDiscordGuildChannel.Prototype.deletePermissionOverwriteAsync( - self: BaseDiscordGuildChannel, - roleIdOrUserId: string -) - return Future.try(function() - return self.discordClient.discordGateway - :deleteAsync(string.format(DiscordEndpoints.BotDeleteChannelPermission, self.id, roleIdOrUserId)) - :await() - end) -end - ---[=[ - Gets the invites for the channel asynchronously. - - @method getInvitesAsync - @within Objects.BaseDiscordGuildChannel - @return Vendor.Future -]=] -function BaseDiscordGuildChannel.Prototype.getInvitesAsync(self: BaseDiscordGuildChannel) - return Future.try(function() - local invites = - self.discordClient.discordGateway:getAsync(string.format(DiscordEndpoints.BotGetInvites, self.id)):await() - - for index, inviteData in invites do - invites[index] = DiscordInvite.new(self.discordClient, inviteData) - end - - return invites - end) -end - ---[=[ - Creates an invite for the channel asynchronously. - - @method createInviteAsync - @param maxAge number? -- The maximum age of the invite in seconds. - @param maxUses number? -- The maximum number of uses for the invite. - @param temporary boolean? -- Whether the invite grants temporary membership. - @within Objects.BaseDiscordGuildChannel - @return Vendor.Future -]=] -function BaseDiscordGuildChannel.Prototype.createInviteAsync( - self: BaseDiscordGuildChannel, - maxAge: number?, - maxUses: number?, - temporary: boolean? -) - return Future.try(function() - local inviteData = self.discordClient.discordGateway - :postAsync( - string.format(DiscordEndpoints.BotGetInvites, self.id), - Resolvable.new(ResolvableType.JSON, { - max_age = maxAge, - max_uses = maxUses, - temporary = temporary, - }) - ) - :await() - - return DiscordInvite.new(self.discordClient, inviteData) - end) -end - ---[=[ - Sets the position of the channel asynchronously. - - @method setPositionAsync - @param position number -- The new position of the channel. - @param parentId string? -- The ID of the new parent category. - @param syncPermissions boolean? -- Whether to sync permissions with the new parent. - @within Objects.BaseDiscordGuildChannel - @return Vendor.Future -]=] -function BaseDiscordGuildChannel.Prototype.setPositionAsync( - self: BaseDiscordGuildChannel, - position: number, - parentId: string?, - syncPermissions: boolean? -) - return Future.try(function() - return self.discordClient.discordGateway - :patchAsync( - string.format(DiscordEndpoints.BotModifyGuildChannelPosition, self.id), - Resolvable.new(ResolvableType.JSON, { - id = self.id, - position = position, - parent_id = parentId, - lock_permissions = syncPermissions, - }) - ) - :await() - end) -end - ---[=[ - Modifies the thread channel settings asynchronously. - - @method modifyAsync - @param channelBuilder ChannelBuilder.ChannelBuilder - @within Objects.BaseDiscordGuildChannel - @return Vendor.Future -]=] -function BaseDiscordGuildChannel.Prototype.modifyAsync( - self: BaseDiscordGuildChannel, - channelBuilder: ChannelBuilder.ChannelBuilder -) - return Future.try(function() - return self.discordClient.discordGateway - :patchAsync(string.format(DiscordEndpoints.BotModifyChannel, self.id), channelBuilder:toPayloadObject()) - :await() - end) -end - ---[=[ - Gets all archived public threads in the guild channel asynchronously. - - @method getPublicArchivedThreadsAsync - @param epoch number? - @param limit number? - @return Vendor.Future<{ number }> - @within Objects.BaseDiscordGuildChannel -]=] -function BaseDiscordGuildChannel.Prototype.getPublicArchivedThreadsAsync( - self: BaseDiscordGuildChannel, - epoch: number?, - limit: number? -) - return Future.try(function() - local rawThreadData = self.discordClient.discordGateway - :getAsync( - string.format( - DiscordEndpoints.BotListPublicArchivedThreads, - self.id, - (epoch and Datetime.fromUnixTimestamp(epoch) or Datetime.now()):toIsoDate(), - limit or 100 - ) - ) - :await() - - local threads: { string } = {} - - for _, threadObject in rawThreadData.threads do - table.insert(threads, threadObject.id) - end - - return threads - end) -end - ---[=[ - Gets all archived private threads in the guild channel asynchronously. - - @method getPrivateArchivedThreadsAsync - @param epoch number? - @param limit number? - @return Vendor.Future<{ number }> - @within Objects.BaseDiscordGuildChannel -]=] -function BaseDiscordGuildChannel.Prototype.getPrivateArchivedThreadsAsync( - self: BaseDiscordGuildChannel, - epoch: number?, - limit: number? -) - return Future.try(function() - local rawThreadData = self.discordClient.discordGateway - :getAsync( - string.format( - DiscordEndpoints.BotListPrivateArchivedThreads, - self.id, - (epoch and Datetime.fromUnixTimestamp(epoch) or Datetime.now()):toIsoDate(), - limit or 100 - ) - ) - :await() - - local threads: { string } = {} - - for _, threadObject in rawThreadData.threads do - table.insert(threads, threadObject.id) - end - - return threads - end) -end - ---[=[ - Gets all archived private threads that the bot is a member of, in the guild channel asynchronously. - - @method getJoinedPrivateArchivedThreads - @param epoch number? - @param limit number? - @return Vendor.Future<{ number }> - @within Objects.BaseDiscordGuildChannel -]=] -function BaseDiscordGuildChannel.Prototype.getJoinedPrivateArchivedThreads( - self: BaseDiscordGuildChannel, - epoch: number?, - limit: number? -) - return Future.try(function() - local rawThreadData = self.discordClient.discordGateway - :getAsync( - string.format( - DiscordEndpoints.BotListJoinedPrivateArchivedThreads, - self.id, - (epoch and Datetime.fromUnixTimestamp(epoch) or Datetime.now()):toIsoDate(), - limit or 100 - ) - ) - :await() - - local threads: { string } = {} - - for _, threadObject in rawThreadData.threads do - table.insert(threads, threadObject.id) - end - - return threads - end) -end - -function BaseDiscordGuildChannel.Interface.new( - discordClient, - channelData: { - id: string, - guildId: string?, - } -) - local channelObject = Construct({ - discordClient = discordClient, - }, BaseDiscordGuildChannel.Prototype) - - for index, value in channelData do - (channelObject :: {})[index] = value - end - - return Extend(channelObject, BaseDiscordChannel.new(discordClient, channelData)) -end - -export type BaseDiscordGuildChannel = BaseDiscordChannel.BaseDiscordChannel & typeof(BaseDiscordGuildChannel.Prototype) & { - guildId: string, - - topic: string, - nsfw: boolean, - - rateLimitPerUser: number, - parentId: string, - - flags: number, -} - -return BaseDiscordGuildChannel.Interface diff --git a/Package/Classes/Objects/BaseGuildTextChannel.luau b/Package/Classes/Objects/BaseGuildTextChannel.luau deleted file mode 100644 index 04bee31..0000000 --- a/Package/Classes/Objects/BaseGuildTextChannel.luau +++ /dev/null @@ -1,188 +0,0 @@ -local Future = require("@Vendor/Future") - -local Construct = require("@Utils/Construct") -local Extend = require("@Utils/Extend") - -local Resolvable = require("@Network/Resolvable") - -local BaseDiscordGuildChannel = require("@Objects/BaseDiscordGuildChannel") -local DiscordMessage = require("@Objects/DiscordMessage") - -local MessageBuilder = require("@Builders/MessageBuilder") - -local DiscordEndpoints = require("@Data/DiscordEndpoints") - -local ResolvableType = require("@Enums/ResolvableType") - ---[=[ - @class Objects.BaseGuildTextChannel - - A base guild text channel, where any/all text channels extend from. - - @tag inherit Objects.BaseDiscordGuildChannel -]=] - ---[=[ - @prop lastPinTimestamp string - @within Objects.BaseGuildTextChannel -]=] - ---[=[ - @prop lastMessageId string? - @within Objects.BaseGuildTextChannel -]=] - -local BaseGuildTextChannel = {} - -BaseGuildTextChannel.Prototype = {} -BaseGuildTextChannel.Interface = {} - -BaseGuildTextChannel.Prototype.type = "BaseGuildTextChannel" - ---[=[ - Triggers the typing indicator in the channel. - - @method triggerTypingIndicator - @within Objects.BaseGuildTextChannel - @return Vendor.Future -]=] -function BaseGuildTextChannel.Prototype.triggerTypingIndicator(self: BaseGuildTextChannel) - return Future.try(function() - return self.discordClient.discordGateway - :postAsync(string.format(DiscordEndpoints.BotTriggerTypingChannel, self.id)) - :await() - end) -end - ---[=[ - Gets messages from the channel asynchronously. - - @method getMessagesAsync - @param limit number? -- The maximum number of messages to retrieve. - @within Objects.BaseGuildTextChannel - @return Vendor.Future -]=] -function BaseGuildTextChannel.Prototype.getMessagesAsync(self: BaseGuildTextChannel, limit: number?) - return Future.try(function() - local url = string.format(DiscordEndpoints.BotGetChannelMessages, self.id) - - if limit then - url ..= `?limit={limit}` - end - - local messageObjects = self.discordClient.discordGateway:getAsync(url):await() - - for index, messageObject in messageObjects do - messageObjects[index] = DiscordMessage.new(self.discordClient, messageObject) - end - - return messageObjects - end) -end - ---[=[ - Gets a specific message from the channel asynchronously. - - @method getMessageAsync - @param messageId string -- The ID of the message to retrieve. - @within Objects.BaseGuildTextChannel - @return Vendor.Future -]=] -function BaseGuildTextChannel.Prototype.getMessageAsync(self: BaseGuildTextChannel, messageId: string) - return Future.try(function() - local messageObject = self.discordClient.discordGateway - :getAsync(string.format(DiscordEndpoints.BotGetChannelMessage, self.id, messageId)) - :await() - - return DiscordMessage.new(self.discordClient, messageObject) - end) -end - ---[=[ - Deletes multiple messages from the channel asynchronously. - - @method bulkDeleteMessagesAsync - @param messageIds {string} -- A list of message IDs to delete. - @within Objects.BaseGuildTextChannel - @return Vendor.Future -]=] -function BaseGuildTextChannel.Prototype.bulkDeleteMessagesAsync(self: BaseGuildTextChannel, messageIds: { string }) - return Future.try(function() - return self.discordClient.discordGateway - :postAsync( - string.format(DiscordEndpoints.BotBulkDeleteMessages, self.id), - Resolvable.new(ResolvableType.JSON, { - messages = messageIds, - }) - ) - :await() - end) -end - ---[=[ - Sends a message to the channel asynchronously. - - @method sendMessageAsync - @param messageBuilder MessageBuilder -- The message builder object. - @within Objects.BaseGuildTextChannel - @return Vendor.Future -]=] -function BaseGuildTextChannel.Prototype.sendMessageAsync( - self: BaseGuildTextChannel, - messageBuilder: MessageBuilder.MessageBuilder -) - return Future.try(function() - return self.discordClient.discordGateway - :postAsync(string.format(DiscordEndpoints.BotCreateMessage, self.id), messageBuilder:toPayloadObject()) - :await() - end) -end - ---[=[ - Retrieves the pinned messages in the text channel asynchronously. - - @method getPinnedMessagesAsync - @within Objects.BaseGuildTextChannel - @return Vendor.Future<{[number]: DiscordMessage}> -- A future that resolves to an array of pinned messages. -]=] -function BaseGuildTextChannel.Prototype.getPinnedMessagesAsync(self: BaseGuildTextChannel) - return Future.try(function() - local messages = self.discordClient.discordGateway - :getAsync(string.format(DiscordEndpoints.BotGetPinnedMessages, self.id)) - :await() - - for index, messageData in messages do - messages[index] = DiscordMessage.new(self.discordClient, messageData) - end - - return messages - end) -end - -function BaseGuildTextChannel.Interface.new( - discordClient, - channelData: { - id: string, - guildId: string, - } -) - local channelObject = Construct({ - discordClient = discordClient, - }, BaseGuildTextChannel.Prototype) - - for index, value in channelData do - (channelObject :: {})[index] = value - end - - return Extend(channelObject, BaseDiscordGuildChannel.new(discordClient, channelData)) -end - -export type BaseGuildTextChannel = - BaseDiscordGuildChannel.BaseDiscordGuildChannel - & typeof(BaseGuildTextChannel.Prototype) - & { - lastPinTimestamp: string, - lastMessageId: string?, - } - -return BaseGuildTextChannel.Interface diff --git a/Package/Classes/Objects/BaseGuildThread.luau b/Package/Classes/Objects/BaseGuildThread.luau deleted file mode 100644 index e66c1a6..0000000 --- a/Package/Classes/Objects/BaseGuildThread.luau +++ /dev/null @@ -1,228 +0,0 @@ -local Future = require("@Vendor/Future") - -local Construct = require("@Utils/Construct") -local Extend = require("@Utils/Extend") - -local DiscordGuildMember = require("@Objects/DiscordGuildMember") -local BaseGuildTextChannel = require("@Objects/BaseGuildTextChannel") -local DiscordEmoji = require("@Objects/DiscordEmoji") - -local DiscordEndpoints = require("@Data/DiscordEndpoints") - ---[=[ - @class Objects.BaseGuildThread - - A base discord Thread channel, all discord threads extend from. - - @tag inherit Objects.BaseGuildTextChannel -]=] - ---[=[ - @prop ownerId string - @within Objects.BaseGuildThread -]=] - ---[=[ - @prop messageCount number - @within Objects.BaseGuildThread -]=] - ---[=[ - @prop memberCount number - @within Objects.BaseGuildThread -]=] - ---[=[ - @prop totalMessageSent number - @within Objects.BaseGuildThread -]=] - ---[=[ - @prop threadMetadata { archived: boolean, autoArchiveDuration: number, archiveTimestamp: string, locked: boolean, invitable: boolean?, createTimestamp: string? }, - @within Objects.BaseGuildThread -]=] - ---[=[ - @prop availableTags { { id: string, name: string, moderated: boolean, emojiId: string?, emojiName: string? } }, - @within Objects.BaseGuildThread -]=] - ---[=[ - @prop appliedTags { string } - @within Objects.BaseGuildThread -]=] - ---[=[ - @prop defaultReactionEmoji Objects.DiscordEmoji - @within Objects.BaseGuildThread -]=] - -local BaseGuildThread = {} - -BaseGuildThread.Prototype = {} -BaseGuildThread.Interface = {} - -BaseGuildThread.Prototype.type = "BaseGuildThread" - ---[=[ - Joins the current discord bot into the thread - - @method joinThreadAsync - @within Objects.BaseGuildThread - @return Vendor.Future -- A future the bot has joined the thread channel -]=] -function BaseGuildThread.Prototype.joinThreadAsync(self: BaseGuildThread) - return Future.try(function() - return self.discordClient.discordGateway - :putAsync(string.format(DiscordEndpoints.BotJoinThreadChannel, self.id)) - :await() - end) -end - ---[=[ - Adds the passed member to the thread channel - - @method addMemberToThreadAsync - @param userId string - @within Objects.BaseGuildThread - @return Vendor.Future -- A future the bot has joined the thread channel -]=] -function BaseGuildThread.Prototype.addMemberToThreadAsync(self: BaseGuildThread, userId: string) - return Future.try(function() - return self.discordClient.discordGateway - :putAsync(string.format(DiscordEndpoints.BotAddMemberToThreadChannel, self.id, userId)) - :await() - end) -end - ---[=[ - Leaves the thread channel if the discord bot is in the channel. - - @method leaveThreadAsync - @within Objects.BaseGuildThread - @return Vendor.Future -- A future the bot has joined the thread channel -]=] -function BaseGuildThread.Prototype.leaveThreadAsync(self: BaseGuildThread) - return Future.try(function() - return self.discordClient.discordGateway - :deleteAsync(string.format(DiscordEndpoints.BotLeaveThreadChannel, self.id)) - :await() - end) -end - ---[=[ - Removes the passed user id from the current thread channel - - @method removeMemberFromThreadAsync - @param userId string - @within Objects.BaseGuildThread - @return Vendor.Future -- A future the bot has joined the thread channel -]=] -function BaseGuildThread.Prototype.removeMemberFromThreadAsync(self: BaseGuildThread, userId: string) - return Future.try(function() - return self.discordClient.discordGateway - :putAsync(string.format(DiscordEndpoints.BotRemoveMemberFromThreadChannel, self.id, userId)) - :await() - end) -end - ---[=[ - Returns a list of members that are active in the current thread channel - - @method getThreadMemberAsync - @param userId string - @within Objects.BaseGuildThread - @return Vendor.Future<{ DiscordGuildMember }> -- A future the bot has joined the thread channel -]=] -function BaseGuildThread.Prototype.getThreadMemberAsync(self: BaseGuildThread, userId: string) - return Future.try(function() - local memberObject = self.discordClient.discordGateway - :getAsync(string.format(DiscordEndpoints.BotGetThreadMember, self.id, userId)) - :await() - - memberObject = DiscordGuildMember.new(self.discordClient, memberObject.userId, self.guildId, memberObject) - - return memberObject - end) -end - ---[=[ - Returns a list of members that are active in the current thread channel - - @method removeMemberFromThreadAsync - @param userId string - @within Objects.BaseGuildThread - @return Vendor.Future<{ DiscordGuildMember }> -- A future the bot has joined the thread channel -]=] -function BaseGuildThread.Prototype.getThreadMembersAsync(self: BaseGuildThread) - return Future.try(function() - local memberObjects = self.discordClient.discordGateway - :getAsync(string.format(DiscordEndpoints.BotGetThreadMembers, self.id)) - :await() - - for key, memberObject in memberObjects do - memberObjects[key] = - DiscordGuildMember.new(self.discordClient, memberObject.user.id, self.guildId, memberObject) - end - - return memberObjects - end) -end - -function BaseGuildThread.Interface.new( - discordClient, - channelData: { - id: string, - guildId: string, - - [string]: any, - } -) - local channelObject = Construct({ - discordClient = discordClient, - }, BaseGuildThread.Prototype) - - if channelData.defaultReactionEmoji then - channelData.defaultReactionEmoji = DiscordEmoji.new(discordClient, channelData.defaultReactionEmoji) - end - - for index, value in channelData do - (channelObject :: {})[index] = value - end - - return Extend(channelObject, BaseGuildTextChannel.new(discordClient, channelData)) -end - -export type BaseGuildThread = BaseGuildTextChannel.BaseGuildTextChannel & typeof(BaseGuildThread.Prototype) & { - discordClient: any, - - ownerId: string, - - messageCount: number, - memberCount: number, - totalMessageSent: number?, - - threadMetadata: { - archived: boolean, - autoArchiveDuration: number, - archiveTimestamp: string, - locked: boolean, - invitable: boolean?, - createTimestamp: string?, - }, - - availableTags: { - { - id: string, - name: string, - moderated: boolean, - emojiId: string?, - emojiName: string?, - } - }, - - appliedTags: { string }, - defaultReactionEmoji: DiscordEmoji.DiscordEmoji, -} - -return BaseGuildThread.Interface diff --git a/Package/Classes/Objects/BaseGuildVoiceChannel.luau b/Package/Classes/Objects/BaseGuildVoiceChannel.luau deleted file mode 100644 index 1060af7..0000000 --- a/Package/Classes/Objects/BaseGuildVoiceChannel.luau +++ /dev/null @@ -1,128 +0,0 @@ -local Future = require("@Vendor/Future") - -local Construct = require("@Utils/Construct") -local Extend = require("@Utils/Extend") - -local DiscordVoiceConnection = require("@Network/DiscordVoiceConnection") - -local BaseDiscordGuildChannel = require("@Objects/BaseDiscordGuildChannel") - ---[=[ - @class Objects.BaseGuildVoiceChannel - - A base discord voice channel, every Guild Voice channel extends from. - - @tag inherit Objects.BaseDiscordGuildChannel -]=] - ---[=[ - @prop id string - @within Objects.BaseGuildVoiceChannel -]=] - ---[=[ - @prop guildId string - @within Objects.BaseGuildVoiceChannel -]=] - ---[=[ - @prop voiceConnection Network.DiscordVoiceConnection? - @within Objects.BaseGuildVoiceChannel -]=] - ---[=[ - @prop bitrate number - @within Objects.BaseGuildVoiceChannel -]=] - ---[=[ - @prop userLimit number - @within Objects.BaseGuildVoiceChannel -]=] - ---[=[ - @prop rtcRegion string - @within Objects.BaseGuildVoiceChannel -]=] - ---[=[ - @prop videoQualityMode number - @within Objects.BaseGuildVoiceChannel -]=] - -local BaseGuildVoiceChannel = {} - -BaseGuildVoiceChannel.Prototype = {} -BaseGuildVoiceChannel.Interface = {} - -BaseGuildVoiceChannel.Prototype.type = "BaseGuildVoiceChannel" - ---[=[ - Connects to the voice channel asynchronously. - - @method connectAsync - @within Objects.BaseGuildVoiceChannel - @return Vendor.Future -- A future that resolves to a boolean indicating success. -]=] -function BaseGuildVoiceChannel.Prototype.connectAsync(self: BaseGuildVoiceChannel) - return Future.try(function() - local connection = DiscordVoiceConnection.new(self.discordClient, `voice-{self.id}`) - - self.voiceConnection = connection - - return connection:connectAsync(self.guildId, self.id):await() - end) -end - ---[=[ - Disconnects from the voice channel asynchronously. - - @method disconnectAsync - @within Objects.BaseGuildVoiceChannel - @return Vendor.Future -- A future that resolves when the disconnection is complete. -]=] -function BaseGuildVoiceChannel.Prototype.disconnectAsync(self: BaseGuildVoiceChannel): Future.Future - return Future.try(function() - if not self.voiceConnection then - return - end - - return self.voiceConnection:disconnectAsync():await() - end) -end - -function BaseGuildVoiceChannel.Interface.new( - discordClient, - channelData: { - id: string, - guildId: string, - } -) - local channelObject = Construct({ - discordClient = discordClient, - }, BaseGuildVoiceChannel.Prototype) - - for index, value in channelData do - (channelObject :: {})[index] = value - end - - return Extend(channelObject, BaseDiscordGuildChannel.new(discordClient, channelData)) -end - -export type BaseGuildVoiceChannel = - BaseDiscordGuildChannel.BaseDiscordGuildChannel - & typeof(BaseGuildVoiceChannel.Prototype) - & { - id: string, - guildId: string, - - voiceConnection: DiscordVoiceConnection.DiscordVoiceConnection?, - - bitrate: number, - userLimit: number, - - rtcRegion: string, - videoQualityMode: number, - } - -return BaseGuildVoiceChannel.Interface diff --git a/Package/Classes/Objects/BaseUserChannel.luau b/Package/Classes/Objects/BaseUserChannel.luau deleted file mode 100644 index bb73fbd..0000000 --- a/Package/Classes/Objects/BaseUserChannel.luau +++ /dev/null @@ -1,195 +0,0 @@ -local Future = require("@Vendor/Future") - -local Construct = require("@Utils/Construct") -local Extend = require("@Utils/Extend") - -local Resolvable = require("@Network/Resolvable") - -local BaseDiscordChannel = require("@Objects/BaseDiscordChannel") -local DiscordMessage = require("@Objects/DiscordMessage") -local DiscordUser = require("@Objects/DiscordUser") - -local MessageBuilder = require("@Builders/MessageBuilder") - -local DiscordEndpoints = require("@Data/DiscordEndpoints") - -local ResolvableType = require("@Enums/ResolvableType") - ---[=[ - @class Objects.BaseUserChannel - - A base User channel, allwoing bots to interact with channels outside of a discord Guild. - - @tag inherit Objects.BaseDiscordChannel -]=] - ---[=[ - @prop id string - @within Objects.BaseUserChannel -]=] - ---[=[ - @prop recipients { Objects.DiscordUser } - @within Objects.BaseUserChannel -]=] - -local BaseUserChannel = {} - -BaseUserChannel.Prototype = {} -BaseUserChannel.Interface = {} - -BaseUserChannel.Prototype.type = "BaseUserChannel" - ---[=[ - Triggers the typing indicator in the channel. - - @method triggerTypingIndicator - @within Objects.BaseUserChannel - @return Vendor.Future -]=] -function BaseUserChannel.Prototype.triggerTypingIndicator(self: BaseUserChannel) - return Future.try(function() - return self.discordClient.discordGateway - :postAsync(string.format(DiscordEndpoints.BotTriggerTypingChannel, self.id)) - :await() - end) -end - ---[=[ - Gets messages from the channel asynchronously. - - @method getMessagesAsync - @param limit number? -- The maximum number of messages to retrieve. - @within Objects.BaseUserChannel - @return Vendor.Future -]=] -function BaseUserChannel.Prototype.getMessagesAsync(self: BaseUserChannel, limit: number?) - return Future.try(function() - local url = string.format(DiscordEndpoints.BotGetChannelMessages, self.id) - - if limit then - url ..= `?limit={limit}` - end - - local messageObjects = self.discordClient.discordGateway:getAsync(url):await() - - for index, messageObject in messageObjects do - messageObjects[index] = DiscordMessage.new(self.discordClient, messageObject) - end - - return messageObjects - end) -end - ---[=[ - Gets a specific message from the channel asynchronously. - - @method getMessageAsync - @param messageId string -- The ID of the message to retrieve. - @within Objects.BaseUserChannel - @return Vendor.Future -]=] -function BaseUserChannel.Prototype.getMessageAsync(self: BaseUserChannel, messageId: string) - return Future.try(function() - local messageObject = self.discordClient.discordGateway - :getAsync(string.format(DiscordEndpoints.BotGetChannelMessage, self.id, messageId)) - :await() - - return DiscordMessage.new(self.discordClient, messageObject) - end) -end - ---[=[ - Deletes multiple messages from the channel asynchronously. - - @method bulkDeleteMessagesAsync - @param messageIds {string} -- A list of message IDs to delete. - @within Objects.BaseUserChannel - @return Vendor.Future -]=] -function BaseUserChannel.Prototype.bulkDeleteMessagesAsync(self: BaseUserChannel, messageIds: { string }) - return Future.try(function() - return self.discordClient.discordGateway - :postAsync( - string.format(DiscordEndpoints.BotBulkDeleteMessages, self.id), - Resolvable.new(ResolvableType.JSON, { - messages = messageIds, - }) - ) - :await() - end) -end - ---[=[ - Sends a message to the channel asynchronously. - - @method sendMessageAsync - @param messageBuilder MessageBuilder -- The message builder object. - @within Objects.BaseUserChannel - @return Vendor.Future -]=] -function BaseUserChannel.Prototype.sendMessageAsync( - self: BaseUserChannel, - messageBuilder: MessageBuilder.MessageBuilder -) - return Future.try(function() - return self.discordClient.discordGateway - :postAsync(string.format(DiscordEndpoints.BotCreateMessage, self.id), messageBuilder:toPayloadObject()) - :await() - end) -end - ---[=[ - Retrieves the pinned messages in the text channel asynchronously. - - @method getPinnedMessagesAsync - @within Objects.BaseUserChannel - @return Vendor.Future<{[number]: DiscordMessage}> -- A future that resolves to an array of pinned messages. -]=] -function BaseUserChannel.Prototype.getPinnedMessagesAsync(self: BaseUserChannel) - return Future.try(function() - local messages = self.discordClient.discordGateway - :getAsync(string.format(DiscordEndpoints.BotGetPinnedMessages, self.id)) - :await() - - for index, messageData in messages do - messages[index] = DiscordMessage.new(self.discordClient, messageData) - end - - return messages - end) -end - -function BaseUserChannel.Interface.new( - discordClient, - channelData: { - id: string, - - [string]: any, - } -) - local channelObject = Construct({ - discordClient = discordClient, - }, BaseUserChannel.Prototype) - - if channelData.recipients then - for key, value in channelData.recipients do - channelData.recipients[key] = DiscordUser.new(discordClient, value) - end - end - - for index, value in channelData do - (channelObject :: {})[index] = value - end - - return Extend(channelObject, BaseDiscordChannel.new(discordClient, channelData)) -end - -export type BaseUserChannel = BaseDiscordChannel.BaseDiscordChannel & typeof(BaseUserChannel.Prototype) & { - discordClient: any, - id: string, - - recipients: { DiscordUser.DiscordUser }, -} - -return BaseUserChannel.Interface diff --git a/Package/Classes/Objects/DiscordApplication.luau b/Package/Classes/Objects/DiscordApplication.luau deleted file mode 100644 index c84a2de..0000000 --- a/Package/Classes/Objects/DiscordApplication.luau +++ /dev/null @@ -1,185 +0,0 @@ -local Future = require("@Vendor/Future") - -local Construct = require("@Utils/Construct") - -local Resolvable = require("../Network/Resolvable") - -local CommandBuilder = require("../Builders/CommandBuilder") - -local DiscordEndpoints = require("@Data/DiscordEndpoints") - -local ResolvableType = require("@Enums/ResolvableType") -local CacheType = require("@Enums/CacheType") - ---[=[ - @class Objects.DiscordApplication - - The `DiscordApplication` class provides methods to interact with Discord application commands such as creating, deleting, editing, fetching, and setting global application commands. -]=] - ---[=[ - @prop id string - @within Objects.DiscordApplication -]=] - ---[=[ - @prop flags number - @within Objects.DiscordApplication -]=] - ---[=[ - @prop id string - @within Objects.DiscordApplication -]=] - ---[=[ - @prop flags number - @within Objects.DiscordApplication -]=] - -local DiscordApplication = {} - -DiscordApplication.Prototype = {} -DiscordApplication.Interface = {} - -DiscordApplication.Prototype.type = "DiscordApplication" - ---[=[ - @method createSlashCommandAsync - @param command CommandBuilder.CommandBuilder The command to create. - @within Objects.DiscordApplication - @return Vendor.Future - - Creates a new slash command for the Discord application. -]=] -function DiscordApplication.Prototype.createSlashCommandAsync( - self: DiscordApplication, - command: CommandBuilder.CommandBuilder -) - return Future.try(function() - return self.discordClient.discordGateway - :postAsync( - string.format(DiscordEndpoints.CreateGlobalApplicationCommand, self.id), - command:toPayloadObject() - ) - :await() - end) -end - ---[=[ - @method deleteSlashCommandAsync - @param commandId string The ID of the command to delete. - @within Objects.DiscordApplication - @return Vendor.Future - - Deletes an existing slash command from the Discord application. -]=] -function DiscordApplication.Prototype.deleteSlashCommandAsync(self: DiscordApplication, commandId: string) - return Future.try(function() - return self.discordClient.discordGateway - :deleteAsync(string.format(DiscordEndpoints.DeleteGlobalApplicationCommand, self.id, commandId)) - :await() - end) -end - ---[=[ - @method editSlashCommandAsync - @param commandId string The ID of the command to edit. - @param command CommandBuilder.CommandBuilder The updated command data. - @within Objects.DiscordApplication - @return Vendor.Future - - Edits an existing slash command for the Discord application. -]=] -function DiscordApplication.Prototype.editSlashCommandAsync( - self: DiscordApplication, - commandId: string, - command: CommandBuilder.CommandBuilder -) - return Future.try(function() - return self.discordClient.discordGateway - :patchAsync( - string.format(DiscordEndpoints.EditGlobalApplicationCommand, self.id, commandId), - command:toPayloadObject() - ) - :await() - end) -end - ---[=[ - @method fetchSlashCommandsAsync - @within Objects.DiscordApplication - @return Vendor.Future<{ApplicationCommand}> - - Fetches all existing slash commands for the Discord application. -]=] -function DiscordApplication.Prototype.fetchSlashCommandsAsync(self: DiscordApplication) - return Future.try(function() - return self.discordClient.discordGateway - :getAsync(string.format(DiscordEndpoints.GetGlobalApplicationCommands, self.id)) - :await() - end) -end - ---[=[ - @method setSlashCommandsAsync - @param commands {CommandBuilder.CommandBuilder} The list of commands to set. - @within Objects.DiscordApplication - @return Vendor.Future - - Sets the slash commands for the Discord application, updating existing ones and creating or deleting as necessary. -]=] -function DiscordApplication.Prototype.setSlashCommandsAsync( - self: DiscordApplication, - commands: { CommandBuilder.CommandBuilder } -) - return Future.try(function() - local commandObjects = {} - - for index, commandBuilder in commands do - commandObjects[index] = commandBuilder:toPayloadObject():resolve() - end - - return self.discordClient.discordGateway - :putAsync( - string.format(DiscordEndpoints.OverwriteGlobalApplicationCommand, self.id), - Resolvable.new(ResolvableType.JSON, commandObjects) - ) - :await() - end) -end - ---[=[ - @function new - @param discordClient any The Discord client instance. - @param applicationData {id: string, flags: number} The application data. - @within Objects.DiscordApplication - @return DiscordApplication - - Creates a new instance of the DiscordApplication. -]=] -function DiscordApplication.Interface.new( - discordClient: any, - applicationData: { - id: string, - flags: number, - } -) - return discordClient.discordCache:getDataOr(CacheType.Application, applicationData.id, function() - return Construct({ - id = applicationData.id, - flags = applicationData.flags, - - discordClient = discordClient, - }, DiscordApplication.Prototype) - end) -end - -export type DiscordApplication = typeof(DiscordApplication.Prototype) & { - id: string, - flags: number, - - discordClient: any, -} - -return DiscordApplication.Interface diff --git a/Package/Classes/Objects/DiscordAutomoderationRule.luau b/Package/Classes/Objects/DiscordAutomoderationRule.luau deleted file mode 100644 index 6686d77..0000000 --- a/Package/Classes/Objects/DiscordAutomoderationRule.luau +++ /dev/null @@ -1,162 +0,0 @@ -local Construct = require("@Utils/Construct") - -local CacheType = require("@Enums/CacheType") -local DiscordEndpoints = require("@Data/DiscordEndpoints") - -local AutomoderationRuleBuilder = require("@Builders/AutomoderationRuleBuilder") - ---[=[ - @class Objects.DiscordAutomoderationRule - - The `DiscordAutomoderationRule` class provides methods to interact with Discord auto-moderation rules, including modifying and deleting rules. -]=] - ---[=[ - @prop id string - @within Objects.DiscordAutomoderationRule -]=] - ---[=[ - @prop guildId string - @within Objects.DiscordAutomoderationRule -]=] - ---[=[ - @prop name string - @within Objects.DiscordAutomoderationRule -]=] - ---[=[ - @prop eventType number - @within Objects.DiscordAutomoderationRule -]=] - ---[=[ - @prop triggerType number - @within Objects.DiscordAutomoderationRule -]=] - ---[=[ - @prop enabled boolean - @within Objects.DiscordAutomoderationRule -]=] - ---[=[ - @prop exemptRoles { string } - @within Objects.DiscordAutomoderationRule -]=] - ---[=[ - @prop exemptChannels { string } - @within Objects.DiscordAutomoderationRule -]=] - ---[=[ - @prop triggerMetadata { keywordFilter: { string }, regexPatterns: { string }, presets: { number }, allowList: { string }, mentionTotalLimit: number, mentionRaidProtectionEnabled: boolean } - @within Objects.DiscordAutomoderationRule -]=] - ---[=[ - @prop actions { type: number, metadata: { channelId: string, durationSeconds: number, customMessage: string? }? }, - @within Objects.DiscordAutomoderationRule -]=] - -local DiscordAutomoderationRule = {} - -DiscordAutomoderationRule.Prototype = {} -DiscordAutomoderationRule.Interface = {} - -DiscordAutomoderationRule.Prototype.type = "DiscordAutomoderationRule" - ---[=[ - @method modifyAsync - @param moderationRoleBuilder DiscordAutomoderationRule.DiscordAutomoderationRule The builder for the modified rule. - @within Objects.DiscordAutomoderationRule - @return Vendor.Future - - Modifies an existing auto-moderation rule. -]=] -function DiscordAutomoderationRule.Prototype.modifyAsync( - self: DiscordAutomoderationRule, - moderationRoleBuilder: AutomoderationRuleBuilder.AutomoderationRuleBuilder -) - return self.discordClient.discordGateway:patchAsync( - string.format(DiscordEndpoints.BotModifyAutomoderationRule, self.guildId, self.id), - moderationRoleBuilder:toPayloadObject() - ) -end - ---[=[ - @method deleteAsync - @within Objects.DiscordAutomoderationRule - @return Vendor.Future - - Deletes an existing auto-moderation rule. -]=] -function DiscordAutomoderationRule.Prototype.deleteAsync(self: DiscordAutomoderationRule) - return self.discordClient.discordGateway:deletAsync( - string.format(DiscordEndpoints.BotDeleteAutomoderationRule, self.guildId, self.id) - ) -end - ---[=[ - @function new - @param discordClient any The Discord client instance. - @param automoderationData table The data for the auto-moderation rule. - @within Objects.DiscordAutomoderationRule - @return DiscordAutomoderationRule - - Creates a new instance of the DiscordAutomoderationRule. -]=] -function DiscordAutomoderationRule.Interface.new( - discordClient: any, - automoderationData: { - id: string, - } -): DiscordAutomoderationRule - local self = discordClient.discordCache:getDataOr(CacheType.AutoModeration, automoderationData.id, function() - return Construct({ - id = automoderationData.id, - - discordClient = discordClient, - }, DiscordAutomoderationRule.Prototype) - end) - - for index, value in automoderationData do - self[index] = value - end - - return self -end - -export type DiscordAutomoderationRule = typeof(DiscordAutomoderationRule.Prototype) & { - discordClient: any, - - id: string, - guildId: string, - name: string, - creatorId: string, - eventType: number, - triggerType: number, - enabled: boolean?, - exemptRoles: { string }, - exemptChannels: { string }, - triggerMetadata: { - keywordFilter: { string }, - regexPatterns: { string }, - presets: { number }, - allowList: { string }, - mentionTotalLimit: number, - mentionRaidProtectionEnabled: boolean, - }, - actions: { - type: number, - metadata: { - channelId: string, - durationSeconds: number, - customMessage: string?, - }?, - }, -} - -return DiscordAutomoderationRule.Interface diff --git a/Package/Classes/Objects/DiscordCache.luau b/Package/Classes/Objects/DiscordCache.luau deleted file mode 100644 index 414891a..0000000 --- a/Package/Classes/Objects/DiscordCache.luau +++ /dev/null @@ -1,93 +0,0 @@ -local Construct = require("@Utils/Construct") - ---[=[ - @class Objects.DiscordCache - - The `DiscordCache` class provides a caching mechanism for storing and retrieving data associated with Discord objects. -]=] - ---[=[ - @prop cache { [string]: any }, - @within Objects.DiscordCache -]=] - -local DiscordCache = {} - -DiscordCache.Prototype = {} -DiscordCache.Interface = {} - ---[=[ - @prop type string - @within Objects.DiscordCache - @readonly - - The type of the DiscordCache. Default is "DiscordCache". -]=] -DiscordCache.Prototype.type = "DiscordCache" - ---[=[ - Sets data in the cache. - - @param cacheType string -- The type of the cache. - @param uuid string -- The unique identifier for the cached data. - @param data any -- The data to be cached. - @method setData - @within Objects.DiscordCache -]=] -function DiscordCache.Prototype.setData(self: DiscordCache, cacheType: string, uuid: string, data: any) - self.cache[`{cacheType}-{uuid}`] = data -end - ---[=[ - Gets data from the cache. - - @param cacheType string -- The type of the cache. - @param uuid string -- The unique identifier for the cached data. - @return any -- The cached data, or `nil` if not found. - @method getData - @within Objects.DiscordCache -]=] -function DiscordCache.Prototype.getData(self: DiscordCache, cacheType: string, uuid: string) - return self.cache[`{cacheType}-{uuid}`] -end - ---[=[ - Gets data from the cache, or calls a callback to generate and cache the data if not found. - - @param cacheType string -- The type of the cache. - @param uuid string -- The unique identifier for the cached data. - @param callback function -- The callback function to generate the data if not found in the cache. - @return any -- The cached or newly generated data. - @method getDataOr - @within Objects.DiscordCache -]=] -function DiscordCache.Prototype.getDataOr(self: DiscordCache, cacheType: string, uuid: string, callback: () -> any) - local data = self:getData(cacheType, uuid) - - if not data then - data = callback() - - self:setData(cacheType, uuid, data) - end - - return data -end - ---[=[ - Creates a new DiscordCache instance. - - @function new - @return DiscordCache - @within Objects.DiscordCache -]=] -function DiscordCache.Interface.new() - return Construct({ - cache = {}, - }, DiscordCache.Prototype) -end - -export type DiscordCache = typeof(DiscordCache.Prototype) & { - cache: { [string]: any }, -} - -return DiscordCache.Interface diff --git a/Package/Classes/Objects/DiscordChannel.luau b/Package/Classes/Objects/DiscordChannel.luau deleted file mode 100644 index 3a74a54..0000000 --- a/Package/Classes/Objects/DiscordChannel.luau +++ /dev/null @@ -1,82 +0,0 @@ -local ChannelType = require("@Enums/ChannelType") - -local GuildVoiceChannel = require("@Objects/GuildVoiceChannel") -local GuildTextChannel = require("@Objects/GuildTextChannel") -local UserDMChannel = require("@Objects/UserDMChannel") -local UserGroupChannel = require("@Objects/UserGroupChannel") -local GuildCategoryChannel = require("@Objects/GuildCategoryChannel") -local GuildAnnouncementChannel = require("@Objects/GuildAnnouncementChannel") -local GuildAnnouncementThreadChannel = require("@Objects/GuildAnnouncementThreadChannel") -local GuildPublicThreadChannel = require("@Objects/GuildPublicThreadChannel") -local GuildPrivateThreadChannel = require("@Objects/GuildPrivateThreadChannel") -local GuildStageVoiceChannel = require("@Objects/GuildStageVoiceChannel") -local GuildDirectoryChannel = require("@Objects/GuildDirectoryChannel") -local GuildForumChannel = require("@Objects/GuildForumChannel") -local GuildMediaChannel = require("@Objects/GuildMediaChannel") - ---[=[ - @class Objects.DiscordChannel - - The `DiscordChannel` class represents a generic Discord channel and provides various methods to interact with and manage the channel. -]=] -local DiscordChannel = {} - -DiscordChannel.Prototype = {} -DiscordChannel.Interface = {} - -function DiscordChannel.Interface.from( - discordClient: any, - rawChannelData: { - id: string, - type: number, - - [any]: any, - } -): DiscordChannel - if rawChannelData.type == ChannelType.GuildTextChannel then - return GuildTextChannel.new(discordClient, rawChannelData) - elseif rawChannelData.type == ChannelType.UserDMChannel then - return UserDMChannel.new(discordClient, rawChannelData) - elseif rawChannelData.type == ChannelType.GuildVoiceChannel then - return GuildVoiceChannel.new(discordClient, rawChannelData) - elseif rawChannelData.type == ChannelType.UserGroupChannel then - return UserGroupChannel.new(discordClient, rawChannelData) - elseif rawChannelData.type == ChannelType.GuildCategory then - return GuildCategoryChannel.new(discordClient, rawChannelData) - elseif rawChannelData.type == ChannelType.GuildAnnouncement then - return GuildAnnouncementChannel.new(discordClient, rawChannelData) - elseif rawChannelData.type == ChannelType.GuildAnnouncementThread then - return GuildAnnouncementThreadChannel.new(discordClient, rawChannelData) - elseif rawChannelData.type == ChannelType.GuildPublicThread then - return GuildPublicThreadChannel.new(discordClient, rawChannelData) - elseif rawChannelData.type == ChannelType.GuildPrivateThread then - return GuildPrivateThreadChannel.new(discordClient, rawChannelData) - elseif rawChannelData.type == ChannelType.GuildStageVoiceChannel then - return GuildStageVoiceChannel.new(discordClient, rawChannelData) - elseif rawChannelData.type == ChannelType.GuildDirectory then - return GuildDirectoryChannel.new(discordClient, rawChannelData) - elseif rawChannelData.type == ChannelType.GuildForumChannel then - return GuildForumChannel.new(discordClient, rawChannelData) - elseif rawChannelData.type == ChannelType.GuildMediaChannel then - return GuildMediaChannel.new(discordClient, rawChannelData) - else - error(`Unknown discord channel type '{rawChannelData.type}'`) - end -end - -export type DiscordChannel = - GuildTextChannel.GuildTextChannel - | UserDMChannel.UserDMChannel - | GuildVoiceChannel.GuildVoiceChannel - | UserGroupChannel.UserGroupChannel - | GuildCategoryChannel.GuildCategoryChannel - | GuildAnnouncementChannel.GuildAnnouncementChannel - | GuildAnnouncementThreadChannel.GuildAnnouncementThreadChannel - | GuildPublicThreadChannel.GuildPublicThreadChannel - | GuildPrivateThreadChannel.GuildPrivateThreadChannel - | GuildStageVoiceChannel.GuildStageVoiceChannel - | GuildDirectoryChannel.GuildDirectoryChannel - | GuildForumChannel.GuildForumChannel - | GuildMediaChannel.GuildMediaChannel - -return DiscordChannel.Interface diff --git a/Package/Classes/Objects/DiscordEmoji.luau b/Package/Classes/Objects/DiscordEmoji.luau deleted file mode 100644 index 37f7d5e..0000000 --- a/Package/Classes/Objects/DiscordEmoji.luau +++ /dev/null @@ -1,104 +0,0 @@ -local Construct = require("@Utils/Construct") - -local CacheType = require("@Enums/CacheType") - ---[=[ - @class Objects.DiscordEmoji - - The `DiscordEmoji` class represents a Discord emoji and provides a structure for emoji data. -]=] - ---[=[ - @prop id string - @within Objects.DiscordEmoji -]=] - ---[=[ - @prop name string - @within Objects.DiscordEmoji -]=] - ---[=[ - @prop roles { string } - @within Objects.DiscordEmoji -]=] - ---[=[ - @prop requireColons boolean - @within Objects.DiscordEmoji -]=] - ---[=[ - @prop managed boolean - @within Objects.DiscordEmoji -]=] - ---[=[ - @prop animated boolean - @within Objects.DiscordEmoji -]=] - ---[=[ - @prop available boolean - @within Objects.DiscordEmoji -]=] - -local DiscordEmoji = {} - -DiscordEmoji.Prototype = {} -DiscordEmoji.Interface = {} - ---[=[ - @prop type string - @within Objects.DiscordEmoji - @readonly - - The type of the DiscordEmoji. Default is "DiscordEmoji". -]=] -DiscordEmoji.Prototype.type = "DiscordEmoji" - ---[=[ - Creates a new DiscordEmoji instance. - - @function new - @param discordClient any -- The Discord client instance. - @param emojiData table -- The raw data for the emoji. - @return DiscordEmoji -- The newly created DiscordEmoji instance. - @within Objects.DiscordEmoji -]=] -function DiscordEmoji.Interface.new( - discordClient: any, - emojiData: { - id: string, - name: string, - roles: { string }, - requireColons: boolean, - managed: boolean, - animated: boolean, - available: boolean, - } -) - return discordClient.discordCache:getDataOr(CacheType.DiscordEmoji, emojiData.id, function() - local emojiStruct = { - discordClient = discordClient, - } - - for index, value in emojiData do - emojiStruct[index] = value - end - - return Construct(emojiStruct, DiscordEmoji.Prototype) - end) -end - -export type DiscordEmoji = typeof(DiscordEmoji.Prototype) & { - id: string, - name: string, - roles: { string }, - requireColons: boolean, - managed: boolean, - animated: boolean, - available: boolean, -} - -return DiscordEmoji.Interface diff --git a/Package/Classes/Objects/DiscordGuild.luau b/Package/Classes/Objects/DiscordGuild.luau deleted file mode 100644 index ba4953e..0000000 --- a/Package/Classes/Objects/DiscordGuild.luau +++ /dev/null @@ -1,1225 +0,0 @@ -local Future = require("@Vendor/Future") - -local Construct = require("@Utils/Construct") - -local Resolvable = require("@Network/Resolvable") - -local GuildWidget = require("@Objects/GuildWidget") -local GuildOnboarding = require("@Objects/GuildOnboarding") -local GuildWelcomeScreen = require("@Objects/GuildWelcomeScreen") -local GuildPreview = require("@Objects/GuildPreview") -local DiscordEmoji = require("@Objects/DiscordEmoji") -local DiscordGuildRole = require("@Objects/DiscordGuildRole") -local DiscordChannel = require("@Objects/DiscordChannel") -local DiscordUser = require("@Objects/DiscordUser") -local DiscordGuildBan = require("@Objects/DiscordGuildBan") -local DiscordGuildMember = require("@Objects/DiscordGuildMember") -local DiscordAutomoderationRule = require("@Objects/DiscordAutomoderationRule") -local DiscordInvite = require("@Objects/DiscordInvite") -local DiscordIntegration = require("@Objects/DiscordIntegration") -local DiscordSticker = require("@Objects/DiscordSticker") - -local CommandBuilder = require("@Builders/CommandBuilder") -local AutomoderationRuleBuilder = require("@Builders/AutomoderationRuleBuilder") -local GuildBuilder = require("@Builders/GuildBuilder") -local ChannelBuilder = require("@Builders/ChannelBuilder") -local MemberBuilder = require("@Builders/MemberBuilder") -local GuildRoleBuilder = require("@Builders/GuildRoleBuilder") -local WelcomeScreenBuilder = require("@Builders/WelcomeScreenBuilder") -local OnboardingBuilder = require("@Builders/OnboardingBuilder") - -local DiscordEndpoints = require("@Data/DiscordEndpoints") - -local CacheType = require("@Enums/CacheType") -local ResolvableType = require("@Enums/ResolvableType") - ---[=[ - @class Objects.DiscordGuild - - The `DiscordGuild` class represents a Discord guild (server) and provides methods to interact with and manage the guild. -]=] - ---[=[ - @prop shardId number - @within Objects.DiscordGuild -]=] - ---[=[ - @prop id string - @within Objects.DiscordGuild -]=] - ---[=[ - @prop name string - @within Objects.DiscordGuild -]=] - ---[=[ - @prop icon string? - @within Objects.DiscordGuild -]=] - ---[=[ - @prop iconHash string? - @within Objects.DiscordGuild -]=] - ---[=[ - @prop splash string? - @within Objects.DiscordGuild -]=] - ---[=[ - @prop discoverySplash string? - @within Objects.DiscordGuild -]=] - ---[=[ - @prop owner boolean - @within Objects.DiscordGuild -]=] - ---[=[ - @prop ownerId string - @within Objects.DiscordGuild -]=] - ---[=[ - @prop permissions number - @within Objects.DiscordGuild -]=] - ---[=[ - @prop afkChannelId string? - @within Objects.DiscordGuild -]=] - ---[=[ - @prop afkTimeout number - @within Objects.DiscordGuild -]=] - ---[=[ - @prop widgetEnabled boolean - @within Objects.DiscordGuild -]=] - ---[=[ - @prop widgetChannelId string? - @within Objects.DiscordGuild -]=] - ---[=[ - @prop verificationLevel number - @within Objects.DiscordGuild -]=] - ---[=[ - @prop defaultMessageNotifications number - @within Objects.DiscordGuild -]=] - ---[=[ - @prop explicitContentFilter number - @within Objects.DiscordGuild -]=] - ---[=[ - @prop roles { Objects.DiscordGuildRole } - @within Objects.DiscordGuild -]=] - ---[=[ - @prop emojis { Objects.DiscordEmoji } - @within Objects.DiscordGuild -]=] - ---[=[ - @prop features { string } - @within Objects.DiscordGuild -]=] - ---[=[ - @prop mfaLevel number - @within Objects.DiscordGuild -]=] - ---[=[ - @prop applicationId string? - @within Objects.DiscordGuild -]=] - ---[=[ - @prop systemChannelId string? - @within Objects.DiscordGuild -]=] - ---[=[ - @prop systemChannelFlags number - @within Objects.DiscordGuild -]=] - ---[=[ - @prop rulesChannelId string? - @within Objects.DiscordGuild -]=] - ---[=[ - @prop maxPresences number? - @within Objects.DiscordGuild -]=] - ---[=[ - @prop maxMembers number - @within Objects.DiscordGuild -]=] - ---[=[ - @prop vanityUrlCode string? - @within Objects.DiscordGuild -]=] - ---[=[ - @prop description string? - @within Objects.DiscordGuild -]=] - ---[=[ - @prop banner string? - @within Objects.DiscordGuild -]=] - ---[=[ - @prop premiumTier number - @within Objects.DiscordGuild -]=] - ---[=[ - @prop premiumSubscriptionCount number - @within Objects.DiscordGuild -]=] - ---[=[ - @prop preferredLocale string - @within Objects.DiscordGuild -]=] - ---[=[ - @prop publicUpdatesChannelId string? - @within Objects.DiscordGuild -]=] - ---[=[ - @prop maxVideoChannelUsers number - @within Objects.DiscordGuild -]=] - ---[=[ - @prop maxStageVideoChannelUsers number - @within Objects.DiscordGuild -]=] - ---[=[ - @prop approximateMemberCount number - @within Objects.DiscordGuild -]=] - ---[=[ - @prop approximatePresenceCount number - @within Objects.DiscordGuild -]=] - ---[=[ - @prop welcomeScreen Objects.GuildWelcomeScreen? - @within Objects.DiscordGuild -]=] - ---[=[ - @prop nsfwLevel number - @within Objects.DiscordGuild -]=] - ---[=[ - @prop stickers { Objects.DiscordSticker } - @within Objects.DiscordGuild -]=] - ---[=[ - @prop premiumProgressBarEnabled boolean - @within Objects.DiscordGuild -]=] - ---[=[ - @prop safetyAlertsChannelId string? - @within Objects.DiscordGuild -]=] - -local DiscordGuild = {} - -DiscordGuild.Prototype = {} -DiscordGuild.Interface = {} - ---[=[ - @prop type string - @within Objects.DiscordGuild - @readonly - - The type of the DiscordGuild. Default is "DiscordGuild". -]=] -DiscordGuild.Prototype.type = "DiscordGuild" - ---[=[ - Creates a new slash command in the guild asynchronously. - - @method createSlashCommandAsync - @param command CommandBuilder -- The command builder object. - @return Vendor.Future - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.createSlashCommandAsync(self: DiscordGuild, command: CommandBuilder.CommandBuilder) - return Future.try(function() - return self.discordClient.discordGateway - :postAsync( - string.format( - DiscordEndpoints.CreateGuildApplicationCommand, - self.discordClient.discordApplication.id, - self.id - ), - command:toPayloadObject() - ) - :await() - end) -end - ---[=[ - Deletes a slash command in the guild asynchronously. - - @method deleteSlashCommandAsync - @param commandId string -- The ID of the command to delete. - @return Vendor.Future - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.deleteSlashCommandAsync(self: DiscordGuild, commandId: string) - return Future.try(function() - return self.discordClient.discordGateway - :deleteAsync( - string.format( - DiscordEndpoints.DeleteGuildApplicationCommand, - self.discordClient.discordApplication.id, - self.id, - commandId - ) - ) - :await() - end) -end - ---[=[ - Edits a slash command in the guild asynchronously. - - @method editSlashCommandAsync - @param commandId string -- The ID of the command to edit. - @param command CommandBuilder -- The command builder object with updated information. - @return Vendor.Future - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.editSlashCommandAsync( - self: DiscordGuild, - commandId: string, - command: CommandBuilder.CommandBuilder -) - return Future.try(function() - return self.discordClient.discordGateway - :patchAsync( - string.format( - DiscordEndpoints.EditGuildApplicationCommand, - self.discordClient.discordApplication.id, - self.id, - commandId - ), - command:toPayloadObject() - ) - :await() - end) -end - ---[=[ - Fetches all slash commands in the guild asynchronously. - - @method fetchSlashCommandsAsync - @return Vendor.Future - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.fetchSlashCommandsAsync(self: DiscordGuild) - return Future.try(function() - return self.discordClient.discordGateway - :getAsync( - string.format( - DiscordEndpoints.GetGuildApplicationCommands, - self.discordClient.discordApplication.id, - self.id - ) - ) - :await() - end) -end - ---[=[ - Fetches all channels in the guild asynchronously. - - @method fetchGuildChannels - @return Vendor.Future - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.fetchGuildChannels(self: DiscordGuild) - return Future.try(function() - local guildChannels = self.discordClient.discordGateway - :getAsync( - string.format(DiscordEndpoints.BotGetGuildChannels, self.discordClient.discordApplication.id, self.id) - ) - :await() - - for key, channelData in guildChannels do - guildChannels[key] = DiscordChannel.from(self.discordClient, channelData) - end - - return guildChannels - end) -end - ---[=[ - Sets multiple slash commands in the guild asynchronously. - - @method setSlashCommandsAsync - @param commands {CommandBuilder} -- An array of command builder objects. - @return Vendor.Future - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.setSlashCommandsAsync(self: DiscordGuild, commands: { CommandBuilder.CommandBuilder }) - return Future.try(function() - local commandObjects = {} - - for index, commandBuilder in commands do - commandObjects[index] = commandBuilder:toPayloadObject():resolve() - end - - return self.discordClient.discordGateway - :putAsync( - string.format( - DiscordEndpoints.OverwriteGuildApplicationCommands, - self.discordClient.discordApplication.id, - self.id - ), - Resolvable.new(ResolvableType.JSON, commandObjects) - ) - :await() - end) -end - ---[=[ - Gets the audit logs of the guild asynchronously. - - @method getGuildAuditLogs - @return Vendor.Future - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.getGuildAuditLogs(self: DiscordGuild) - return Future.try(function() - local guildAuditLogs = self.discordClient.discordGateway - :getAsync(string.format(DiscordEndpoints.GetGuildAuditLogs, self.id)) - :await() - - for index, channelObject in guildAuditLogs.threads do - guildAuditLogs.threads[index] = DiscordChannel.from(self.discordClient, channelObject) - end - - for index, userObject in guildAuditLogs.users do - guildAuditLogs.users[index] = DiscordUser.new(self.discordClient, userObject) - end - - return guildAuditLogs - end) -end - ---[=[ - Modifies the guild settings asynchronously. - - @method modifyAsync - @param guildBuilder Builders.GuildBuilder - @return Vendor.Future - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.modifyAsync(self: DiscordGuild, guildBuilder: GuildBuilder.GuildBuilder) - return Future.try(function() - return self.discordClient.discordGateway - :patchAsync(string.format(DiscordEndpoints.BotModifyGuild, self.id), guildBuilder:toPayloadObject()) - :await() - end) -end - ---[=[ - Modifies the current member of the discord guild - - @method modifyCurrentMemberAsync - @param memberBuilder Builders.MemberBuilder - @return Vendor.Future - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.modifyCurrentMemberAsync(self: DiscordGuild, memberBuilder: MemberBuilder.MemberBuilder) - return Future.try(function() - return self.discordClient.discordGateway - :patchAsync( - string.format(DiscordEndpoints.BotModifyCurrentMemberGuild, self.id), - memberBuilder:toPayloadObject() - ) - :await() - end) -end - ---[=[ - Deletes the guild asynchronously. - - @method deleteAsync - @return Vendor.Future - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.deleteAsync(self: DiscordGuild) - return Future.try(function() - return self.discordClient.discordGateway - :deleteAsync(string.format(DiscordEndpoints.BotDeleteGuild, self.id)) - :await() - end) -end - ---[=[ - Gets all channels in the guild asynchronously. - - @method getChannelsAsync - @return Vendor.Future - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.getChannelsAsync(self: DiscordGuild) - return Future.try(function() - local rawChannelDatas = self.discordClient.discordGateway - :getAsync(string.format(DiscordEndpoints.BotGetGuildChannels, self.id)) - :await() - - for index, value in rawChannelDatas do - rawChannelDatas[index] = DiscordChannel.from(self.discordClient, value) - end - - return rawChannelDatas - end) -end - ---[=[ - Gets a specific member of the guild asynchronously. - - @method getMemberAsync - @param userId string -- The ID of the user to retrieve. - @return Vendor.Future - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.getMemberAsync(self: DiscordGuild, userId: string) - return Future.try(function() - local discordMember = self.discordClient.discordGateway - :getAsync(string.format(DiscordEndpoints.BotGetGuildMember, self.id, userId)) - :await() - - return DiscordGuildMember.new(self.discordClient, userId, self.id, discordMember) - end) -end - ---[=[ - Fetches members of the guild asynchronously. - - @method fetchGuildMembersAsync - @param limit number -- The maximum number of members to retrieve. - @param lastUserId number? -- The ID of the last user retrieved (for pagination). - @return Vendor.Future - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.fetchGuildMembersAsync(self: DiscordGuild, limit: number, lastUserId: number?) - return Future.try(function() - local discordMembers = self.discordClient.discordGateway - :getAsync(string.format(DiscordEndpoints.BotGetGuildMembers, self.id, limit or 100, lastUserId or "0")) - :await() - - for index, memberObject in discordMembers do - discordMembers[index] = - DiscordGuildMember.new(self.discordClient, memberObject.user.id, self.id, memberObject) - end - - return discordMembers - end) -end - ---[=[ - Searches for members in the guild asynchronously. - - @method searchGuildMembersAsync - @param query string -- The search query. - @param limit number? -- The maximum number of members to retrieve. - @return Vendor.Future - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.searchGuildMembersAsync(self: DiscordGuild, query: string, limit: number?) - return Future.try(function() - local discordMembers = self.discordClient.discordGateway - :getAsync(string.format(DiscordEndpoints.BotSearchGuildMembers, self.id, query, limit or "100")) - :await() - - for index, memberObject in discordMembers do - discordMembers[index] = - DiscordGuildMember.new(self.discordClient, memberObject.user.id, self.id, memberObject) - end - - return discordMembers - end) -end - ---[=[ - Lists all automoderation rules in the guild asynchronously. - - @method listAutomoderationRulesAsync - @return Vendor.Future - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.listAutomoderationRulesAsync(self: DiscordGuild) - return Future.try(function() - local discordAutomoderationRules = self.discordClient.discordGateway - :getAsync(string.format(DiscordEndpoints.BotListAutomoderationRules, self.id)) - :await() - - for index, value in discordAutomoderationRules do - discordAutomoderationRules[index] = DiscordAutomoderationRule.new(self.discordClient, value) - end - - return discordAutomoderationRules - end) -end - ---[=[ - Gets a specific automoderation rule in the guild asynchronously. - - @method getAutomoderationRuleAsync - @param ruleId string -- The ID of the rule to retrieve. - @return Vendor.Future - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.getAutomoderationRuleAsync(self: DiscordGuild, ruleId: string) - return Future.try(function() - local discordAutomoderationRule = self.discordClient.discordGateway - :getAsync(string.format(DiscordEndpoints.BotGetAutomoderationRule, self.id, ruleId)) - :await() - - return DiscordAutomoderationRule.new(self.discordClient, discordAutomoderationRule) - end) -end - ---[=[ - Creates an automoderation rule in the guild asynchronously. - - @method createAutomoderationRuleAsync - @param getAutomoderationRuleBuilder AutomoderationRuleBuilder -- The rule builder object. - @return Vendor.Future - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.createAutomoderationRuleAsync( - self: DiscordGuild, - getAutomoderationRuleBuilder: AutomoderationRuleBuilder.AutomoderationRuleBuilder -) - return Future.try(function() - local discordAutomoderationRule = self.discordClient.discordGateway - :postAsync( - string.format(DiscordEndpoints.BotCreateAutomoderationRule, self.id), - getAutomoderationRuleBuilder:toPayloadObject() - ) - :await() - - return DiscordAutomoderationRule.new(self.discordClient, discordAutomoderationRule) - end) -end - ---[=[ - Get the preview of the current Guild - - @method getPreviewAsync - @return Vendor.Future - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.getPreviewAsync(self: DiscordGuild) - return Future.try(function() - local previewData = self.discordClient.discordGateway - :getAsync(string.format(DiscordEndpoints.BotGetGuildPreview, self.id)) - :await() - - return GuildPreview.new(self.discordClient, previewData) - end) -end - ---[=[ - Creates a Guild Channel under a Guild. - - @method createChannelAsync - @param channelBuilder Builders.ChannelBuilder - @return Vendor.Future - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.createChannelAsync(self: DiscordGuild, channelBuilder: ChannelBuilder.ChannelBuilder) - return Future.try(function() - local channelData = self.discordClient.discordGateway - :postAsync(string.format(DiscordEndpoints.BotCreateGuildChannel, self.id), channelBuilder:toPayloadObject()) - :await() - - return DiscordChannel.from(self.discordClient, channelData) - end) -end - ---[=[ - Fetches all active guild threads. - - @method getActiveGuildThreads - @return Vendor.Future<{ Objects.DiscordChannel }> - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.getActiveGuildThreads(self: DiscordGuild) - return Future.try(function() - local threadDatas: { DiscordChannel.DiscordChannel } = self.discordClient.discordGateway - :getAsync(string.format(DiscordEndpoints.BotGetAllActiveGuildThreads, self.id)) - :await() - - for key, value in threadDatas do - threadDatas[key] = DiscordChannel.from(self.discordClient, value) - end - - return threadDatas - end) -end - ---[=[ - Adds a discordian to a guild. - - @method addMemberAsync - @param userId string - @param accessToken string - @return Vendor.Future<{ Objects.DiscordChannel }> - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.addMemberAsync(self: DiscordGuild, userId: string, accessToken: string) - return Future.try(function() - local guildMember = self.discordClient.discordGateway - :putAsync( - string.format(DiscordEndpoints.BotAddGuildMember, self.id, userId), - Resolvable.new(ResolvableType.JSON, { - access_token = accessToken, - }) - ) - :await() - - return DiscordGuildMember.new(self.discordClient, userId, self.id, guildMember) - end) -end - ---[=[ - Fetches an array of Guild Bans - - @method getGuildBansAsync - @param limit number? - @param before string? - @param after string? - @return Vendor.Future<{ Objects.DiscordGuildBan }> - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.getGuildBansAsync(self: DiscordGuild, limit: number?, before: string?, after: string?) - return Future.try(function() - local guildBans = self.discordClient.discordGateway - :getAsync(string.format(DiscordEndpoints.BotGetGuildBans, self.id, before or "", after or "", limit or 100)) - :await() - - for key, value in guildBans do - guildBans[key] = DiscordGuildBan.new(self.discordClient, value) - end - - return guildBans - end) -end - ---[=[ - Fetches the ban object for a specific user id - - @method getGuildBanAsync - @param userId string - @return Vendor.Future - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.getGuildBanAsync(self: DiscordGuild, userId: string) - return Future.try(function() - local guildBan = self.discordClient.discordGateway - :getAsync(string.format(DiscordEndpoints.BotGetGuildBan, self.id, userId)) - :await() - - return DiscordGuildBan.new(self.discordClient, guildBan) - end) -end - ---[=[ - Bans up to 200 members asynchronously - - @method bulkGuildBanAsync - @param userIds { string } - @return Vendor.Future - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.bulkGuildBanAsync(self: DiscordGuild, userIds: { string }) - return Future.try(function() - return self.discordClient.discordGateway - :postAsync( - string.format(DiscordEndpoints.BotBulkGuildBan, self.id), - Resolvable.new(ResolvableType.JSON, userIds) - ) - :await() - end) -end - ---[=[ - Allows you to get all roles within a guild. - - @method bulkGuildBanAsync - @return Vendor.Future<{ Objects.DiscordGuildRole }> - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.getGuildRolesAsync(self: DiscordGuild) - return Future.try(function() - local guildRoles: { DiscordGuildRole.DiscordGuildRole } = self.discordClient.discordGateway - :getAsync(string.format(DiscordEndpoints.BotGetGuildRoles, self.id)) - :await() - - for key, value in guildRoles do - guildRoles[key] = DiscordGuildRole.new(self.discordClient, value) - end - - return guildRoles - end) -end - ---[=[ - Allows you to get all roles within a guild. - - @method bulkGuildBanAsync - @return Vendor.Future<{ Objects.DiscordGuildRole }> - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.createGuildRoleAsync(self: DiscordGuild, roleBuilder: GuildRoleBuilder.GuildRoleBuilder) - return Future.try(function() - return self.discordClient.discordGateway - :postAsync(string.format(DiscordEndpoints.BotCreateGuildRole, self.id), roleBuilder:toPayloadObject()) - :await() - end) -end - ---[=[ - Setst he guilds MFA level. - - @method setMFALevelAsync - @param mfaLevel number - @return Vendor.Future - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.setMFALevelAsync(self: DiscordGuild, mfaLevel: number) - return Future.try(function() - return self.discordClient.discordGateway - :postAsync( - string.format(DiscordEndpoints.BotModifyGuildMFALevel, self.id), - Resolvable.new(ResolvableType.JSON, { - level = mfaLevel, - }) - ) - :await() - end) -end - ---[=[ - Fetches the prune count. - - @method getPruneCountAsync - @param days number? - @param includedRoles { string }? - @return Vendor.Future<{ pruned: number }> - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.getPruneCountAsync(self: DiscordGuild, days: number?, includedRoles: { string }?) - return Future.try(function() - return self.discordClient.discordGateway - :getAsync( - string.format( - DiscordEndpoints.BotGetGuildPruneCount, - self.id, - days or 7, - includedRoles and table.concat(includedRoles, ",") or "" - ) - ) - :await() :: { - pruned: number, - } - end) -end - ---[=[ - Requests discord to prune the Discord guild. - - @method getPruneCountAsync - @param days number? - @param computePruneCount boolean? - @param includeRoles { string }? - @return Vendor.Future<{ pruned: number }> - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.beginPruneAsync( - self: DiscordGuild, - days: number?, - computePruneCount: boolean?, - includeRoles: { string }? -) - return Future.try(function() - return self.discordClient.discordGateway - :postAsync( - string.format(DiscordEndpoints.BotBeginGuildPrune, self.id), - Resolvable.new(ResolvableType.JSON, { - days = days, - compute_prune_count = computePruneCount, - include_roles = includeRoles, - }) - ) - :await() :: { - pruned: number?, - } - end) -end - ---[=[ - Fetches the available voice regions for the guild. - - @method getGuildVoiceRegionsAsync - @return Vendor.Future<{ { id: string, name: string, optimal: boolean, deprecated: boolean, custom: boolean } }> - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.getGuildVoiceRegionsAsync(self: DiscordGuild) - return Future.try(function() - return self.discordClient.discordGateway - :getAsync(string.format(DiscordEndpoints.BotGetGuildVoiceRegions, self.id)) - :await() :: { { id: string, name: string, optimal: boolean, deprecated: boolean, custom: boolean } } - end) -end - ---[=[ - Fetches all guild invite objects. - - @method getGuildInvitesAsync - @return Vendor.Future<{ Objects.DiscordInvite }> - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.getGuildInvitesAsync(self: DiscordGuild) - return Future.try(function() - local guildInvites: { DiscordInvite.DiscordInvite } = self.discordClient.discordGateway - :getAsync(string.format(DiscordEndpoints.BotGetGuildInvites, self.id)) - :await() - - for key, value in guildInvites do - guildInvites[key] = DiscordInvite.new(self.discordClient, value) - end - - return guildInvites - end) -end - ---[=[ - Fetches all guild integrations - - @method getGuildIntegrationsAsync - @return Vendor.Future<{ Objects.DiscordIntegration }> - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.getGuildIntegrationsAsync(self: DiscordGuild) - return Future.try(function() - local guildIntegrations: { DiscordIntegration.DiscordIntegration } = self.discordClient.discordGateway - :getAsync(string.format(DiscordEndpoints.BotGetGuildIntegrations, self.id)) - :await() - - for key, value in guildIntegrations do - guildIntegrations[key] = DiscordIntegration.new(self.discordClient, self.id, value) - end - - return guildIntegrations - end) -end - ---[=[ - Fetches the widget settings for the current guild. - - @method getGuildWidgetSettingsAsync - @return Vendor.Future<{ enabled: boolean, cannelId: string }> - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.getGuildWidgetSettingsAsync(self: DiscordGuild) - return Future.try(function() - return self.discordClient.discordGateway - :getAsync(string.format(DiscordEndpoints.BotGetGuildWidgetSettings, self.id)) - :await() :: { - enabled: boolean, - cannelId: string, - } - end) -end - ---[=[ - Updates the widget settings for the current guild - - @method updateGuildWidgetSettingsAsync - @param widgetEnabled boolean - @param channelId string - @return Vendor.Future<{ enabled: boolean, cannelId: string }> - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.updateGuildWidgetSettingsAsync(self: DiscordGuild, enabled: boolean, channelId: string?) - return Future.try(function() - return self.discordClient.discordGateway - :patchAsync( - string.format(DiscordEndpoints.BotModifyGuildWidgetSettings, self.id), - Resolvable.new(ResolvableType.JSON, { - enabled = enabled, - channelId = channelId, - }) - ) - :await() :: { - enabled: boolean, - cannelId: string, - } - end) -end - ---[=[ - Gets the guild widget for this guild. - - @method getGuldWidgetAsync - @return Vendor.Future - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.getGuildWidgetAsync(self: DiscordGuild) - return Future.try(function() - local widgetObject = self.discordClient.discordGateway - :getAsync(string.format(DiscordEndpoints.BotGetGuildWidget, self.id)) - :await() - - return GuildWidget.new(self.discordClient, widgetObject) - end) -end - ---[=[ - Gets the vanity url for this guild. - - @method getGuildVanityUrlAsync - @return Vendor.Future<{ code: string, uses: number }> - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.getGuildVanityUrlAsync(self: DiscordGuild) - return Future.try(function() - return self.discordClient.discordGateway - :getAsync(string.format(DiscordEndpoints.BotGetGuildVanityURL, self.id)) - :await() :: { - code: string, - uses: number, - } - end) -end - ---[=[ - Gets the guild welcome screen for this guild. - - @method getGuildWelcomeScreenAsync - @return Vendor.Future - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.getGuildWelcomeScreenAsync(self: DiscordGuild) - return Future.try(function() - local welcomeScreen = self.discordClient.discordGateway - :getAsync(string.format(DiscordEndpoints.BotGetGuildWelcomeScreen, self.id)) - :await() - - return GuildWelcomeScreen.new(self.discordClient, welcomeScreen) - end) -end - ---[=[ - Gets the binary PNG image for a server widget - - @method getGuildWidgetImageAsync - @param style 'shield' | 'banner1' | 'banner2' | 'banner3' | 'banner4' - @return Vendor.Future - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.getGuildWidgetImageAsync( - self: DiscordGuild, - style: ("shield" | "banner1" | "banner2" | "banner3" | "banner4")? -) - return Future.try(function() - return self.discordClient.discordGateway - :getAsync(string.format(DiscordEndpoints.BotGetWidgetImage, self.id, style or "shield")) - :await() - end) -end - ---[=[ - Updates the guild welcome screen for this guild. - - @method modifyGuildWelcomeScreenAsync - @param welcomeScreenBuilder Builders.WelcomeScreenBuilder - @return Vendor.Future - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.modifyGuildWelcomeScreenAsync( - self: DiscordGuild, - welcomeScreenBuilder: WelcomeScreenBuilder.WelcomeScreenBuilder -) - return Future.try(function() - return self.discordClient.discordGateway - :patchAsync( - string.format(DiscordEndpoints.BotModifyGuildWelcomeScreen, self.id), - welcomeScreenBuilder:toPayloadObject() - ) - :await() - end) -end - ---[=[ - Gets the onboarding for this guild - - @method getGuldOnboardingAsync - @return Vendor.Future - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.getGuildOnboardingAsync(self: DiscordGuild) - return Future.try(function() - local onboarding = self.discordClient.discordGateway - :getAsync(string.format(DiscordEndpoints.BotGetGuildOnboarding, self.id)) - :await() - - return GuildOnboarding.new(self.discordClient, onboarding) - end) -end - ---[=[ - Updates the onboarding for this guild - - @method getGuldOnboardingAsync - @param guildOnboardingBuilder Builders.OnboardingBuilder - @return Vendor.Future - @within Objects.DiscordGuild -]=] -function DiscordGuild.Prototype.modifyGuildOnboardingAsync( - self: DiscordGuild, - guildOnboardingBuilder: OnboardingBuilder.OnboardingBuilder -) - return Future.try(function() - return self.discordClient.discordGateway - :putAsync( - string.format(DiscordEndpoints.BotModifyGuildOnboarding, self.id), - guildOnboardingBuilder:toPayloadObject() - ) - :await() - end) -end - ---[=[ - Creates a new DiscordGuild instance. - - @function new - @param discordClient any -- The Discord client instance. - @param guildData table -- The raw data for the guild. - @return DiscordGuild -- The newly created DiscordGuild instance. - @within Objects.DiscordGuild -]=] -function DiscordGuild.Interface.new( - discordClient: any, - guildData: { - id: string, - roles: { any }, - emojis: { any }, - } -) - local self = discordClient.discordCache:getDataOr(CacheType.DiscordGuild, guildData.id, function() - local guildStruct = { - discordClient = discordClient, - shardId = ((tonumber(guildData.id) :: number / 2 ^ 22) % discordClient.shardCount) + 1, - } - - return Construct(guildStruct, DiscordGuild.Prototype) - end) - - if guildData.roles then - for index, value in guildData.roles do - guildData.roles[index] = DiscordGuildRole.new(discordClient, value) - end - end - - if guildData.emojis then - for index, value in guildData.emojis do - guildData.emojis[index] = DiscordEmoji.new(discordClient, value) - end - end - - for index, value in guildData do - self[index] = value - end - - return self -end - -export type DiscordGuild = typeof(DiscordGuild.Prototype) & { - discordClient: any, - shardId: number, - id: string, - name: string, - icon: string?, - iconHash: string?, - splash: string?, - discoverySplash: string?, - owner: boolean, - ownerId: string, - permissions: number, - afkChannelId: string?, - afkTimeout: number, - widgetEnabled: boolean, - widgetChannelId: string?, - verificationLevel: number, - defaultMessageNotifications: number, - explicitContentFilter: number, - roles: { DiscordGuildRole.DiscordGuildRole }, - emojis: { DiscordEmoji.DiscordEmoji }, - features: { string }, - mfaLevel: number, - applicationId: string?, - systemChannelId: string?, - systemChannelFlags: number, - rulesChannelId: string?, - maxPresences: number?, - maxMembers: number, - vanityUrlCode: string?, - description: string?, - banner: string?, - premiumTier: number, - premiumSubscriptionCount: number, - preferredLocale: string, - publicUpdatesChannelId: string?, - maxVideoChannelUsers: number, - maxStageVideoChannelUsers: number, - approximateMemberCount: number, - approximatePresenceCount: number, - welcomeScreen: GuildWelcomeScreen.GuildWelcomeScreen?, - nsfwLevel: number, - stickers: { DiscordSticker.DiscordSticker }, - premiumProgressBarEnabled: boolean, - safetyAlertsChannelId: string?, -} - -return DiscordGuild.Interface diff --git a/Package/Classes/Objects/DiscordGuildBan.luau b/Package/Classes/Objects/DiscordGuildBan.luau deleted file mode 100644 index 32c95cc..0000000 --- a/Package/Classes/Objects/DiscordGuildBan.luau +++ /dev/null @@ -1,61 +0,0 @@ -local Construct = require("@Utils/Construct") - -local DiscordUser = require("@Objects/DiscordUser") - ---[=[ - @class Objects.DiscordGuildBan - - The `DiscordGuildBan` class provides a way for developers to interact with guild bans. -]=] - ---[=[ - @prop user Objects.DiscordUser - @within Objects.DiscordGuildBan -]=] - ---[=[ - @prop reason string? - @within Objects.DiscordGuildBan -]=] - -local DiscordGuildBan = {} - -DiscordGuildBan.Prototype = {} -DiscordGuildBan.Interface = {} - ---[=[ - @prop type string - @within Objects.DiscordGuildBan - @readonly - - The type of the DiscordGuildBan. Default is "DiscordGuildBan". -]=] -DiscordGuildBan.Prototype.type = "DiscordGuildBan" - ---[=[ - Creates a new DiscordGuildBan instance. - - @function new - @return DiscordGuildBan - @within Objects.DiscordGuildBan -]=] -function DiscordGuildBan.Interface.new(discordClient: any, discordBan: { [string]: any }) - local self = Construct({}, DiscordGuildBan.Prototype) - - if discordBan.user then - discordBan.user = DiscordUser.new(discordClient, discordBan.user) - end - - for key, value in discordBan do - (self :: {})[key] = value - end - - return self -end - -export type DiscordGuildBan = typeof(DiscordGuildBan.Prototype) & { - user: DiscordUser.DiscordUser, - reason: string?, -} - -return DiscordGuildBan.Interface diff --git a/Package/Classes/Objects/DiscordGuildMember.luau b/Package/Classes/Objects/DiscordGuildMember.luau deleted file mode 100644 index 28fee1e..0000000 --- a/Package/Classes/Objects/DiscordGuildMember.luau +++ /dev/null @@ -1,244 +0,0 @@ -local Construct = require("@Utils/Construct") - -local Resolvable = require("@Network/Resolvable") - -local DiscordUser = require("@Objects/DiscordUser") - -local MemberBuilder = require("@Builders/MemberBuilder") - -local DiscordEndpoints = require("@Data/DiscordEndpoints") - -local ResolvableType = require("@Enums/ResolvableType") -local CacheType = require("@Enums/CacheType") - ---[=[ - @class Objects.DiscordGuildMember - - The `DiscordGuildMember` class represents a member of a Discord guild (server) and provides methods to interact with and manage the guild member. -]=] - ---[=[ - @prop user Objects.DiscordUser - @within Objects.DiscordGuildMember -]=] - ---[=[ - @prop guildId string - @within Objects.DiscordGuildMember -]=] - -local DiscordGuildMember = {} - -DiscordGuildMember.Prototype = {} -DiscordGuildMember.Interface = {} - ---[=[ - @prop type string - @within Objects.DiscordGuildMember - @readonly - - The type of the DiscordGuildMember. Default is "DiscordGuildMember". -]=] -DiscordGuildMember.Prototype.type = "DiscordGuildMember" - ---[=[ - Kicks the guild member asynchronously. - - @method kickAsync - @param deleteMessagesSeconds number? -- The number of seconds of messages to delete. - @return Vendor.Future - @within Objects.DiscordGuildMember -]=] -function DiscordGuildMember.Prototype.kickAsync(self: DiscordGuildMember, deleteMessagesSeconds: number?) - return self.discordClient.discordGateway - :putAsync( - string.format(DiscordEndpoints.BotRemoveGuildMember, self.guildId, self.user.id), - Resolvable.new(ResolvableType.JSON, { - delete_message_seconds = deleteMessagesSeconds or 0, - }) - ) - :await() -end - ---[=[ - Bans the guild member asynchronously. - - @method banAsync - @return Vendor.Future - @within Objects.DiscordGuildMember -]=] -function DiscordGuildMember.Prototype.banAsync(self: DiscordGuildMember) - return self.discordClient.discordGateway - :deleteAsync(string.format(DiscordEndpoints.BotBanGuildMember, self.guildId, self.user.id)) - :await() -end - ---[=[ - Unbans the guild member asynchronously. - - @method unbanAsync - @return Vendor.Future - @within Objects.DiscordGuildMember -]=] -function DiscordGuildMember.Prototype.unbanAsync(self: DiscordGuildMember) - return self.discordClient.discordGateway - :deleteAsync(string.format(DiscordEndpoints.BotRemoveGuildMemberBan, self.guildId, self.user.id)) - :await() -end - ---[=[ - Adds a role to the guild member asynchronously. - - @method addRoleAsync - @param roleId string -- The ID of the role to add. - @return Vendor.Future - @within Objects.DiscordGuildMember -]=] -function DiscordGuildMember.Prototype.addRoleAsync(self: DiscordGuildMember, roleId: string) - return self.discordClient.discordGateway - :putAsync(string.format(DiscordEndpoints.BotAddGuildMemberRole, self.guildId, self.user.id, roleId)) - :await() -end - ---[=[ - Removes a role from the guild member asynchronously. - - @method removeRoleAsync - @param roleId string -- The ID of the role to remove. - @return Vendor.Future - @within Objects.DiscordGuildMember -]=] -function DiscordGuildMember.Prototype.removeRoleAsync(self: DiscordGuildMember, roleId: string) - return self.discordClient.discordGateway - :deleteAsync(string.format(DiscordEndpoints.BotRemoveGuildMemberRole, self.guildId, self.user.id, roleId)) - :await() -end - ---[=[ - Times out the guild member asynchronously. - - @method timeoutAsync - @param seconds number -- The duration of the timeout in seconds. - @return Vendor.Future - @within Objects.DiscordGuildMember -]=] -function DiscordGuildMember.Prototype.timeoutAsync(self: DiscordGuildMember, seconds: number) - return self:modifyAsync(MemberBuilder.new():setTimeoutFor(seconds)) -end - ---[=[ - Mutes the guild member asynchronously. - - @method muteAsync - @return Vendor.Future - @within Objects.DiscordGuildMember -]=] -function DiscordGuildMember.Prototype.muteAsync(self: DiscordGuildMember) - return self:modifyAsync(MemberBuilder.new():setMuted(true)) -end - ---[=[ - Unmutes the guild member asynchronously. - - @method unmuteAsync - @return Vendor.Future - @within Objects.DiscordGuildMember -]=] -function DiscordGuildMember.Prototype.unmuteAsync(self: DiscordGuildMember) - return self:modifyAsync(MemberBuilder.new():setMuted(false)) -end - ---[=[ - Deafens the guild member asynchronously. - - @method deafenAsync - @return Vendor.Future - @within Objects.DiscordGuildMember -]=] -function DiscordGuildMember.Prototype.deafenAsync(self: DiscordGuildMember) - return self:modifyAsync(MemberBuilder.new():setDeafened(true)) -end - ---[=[ - Undeafens the guild member asynchronously. - - @method undeafenAsync - @return Vendor.Future - @within Objects.DiscordGuildMember -]=] -function DiscordGuildMember.Prototype.undeafenAsync(self: DiscordGuildMember) - return self:modifyAsync(MemberBuilder.new():setDeafened(false)) -end - ---[=[ - Sets the nickname of the guild member asynchronously. - - @method setNicknameAsync - @param nickname string -- The new nickname. - @return Vendor.Future - @within Objects.DiscordGuildMember -]=] -function DiscordGuildMember.Prototype.setNicknameAsync(self: DiscordGuildMember, nickname: string) - return self:modifyAsync(MemberBuilder.new():setNickname(nickname)) -end - ---[=[ - Modifies the guild member asynchronously. - - @method modifyAsync - @param memberBuilder Builders.MemberBuilder - @return Vendor.Future - @within Objects.DiscordGuildMember -]=] -function DiscordGuildMember.Prototype.modifyAsync(self: DiscordGuildMember, memberBuilder: MemberBuilder.MemberBuilder) - return self.discordClient.discordGateway - :patchAsync( - string.format(DiscordEndpoints.BotModifyGuildMember, self.guildId, self.user.id), - memberBuilder:toPayloadObject() - ) - :await() -end - ---[=[ - Creates a new DiscordGuildMember instance. - - @function new - @param discordClient any -- The Discord client instance. - @param userId string -- The ID of the user. - @param guildId string -- The ID of the guild. - @param memberData table -- The raw data for the guild member. - @return DiscordGuildMember -- The newly created DiscordGuildMember instance. - @within Objects.DiscordGuildMember -]=] -function DiscordGuildMember.Interface.new( - discordClient: any, - userId: string, - guildId: string, - memberData: { [any]: any } -) - local self = discordClient.discordCache:getDataOr(CacheType.DiscordMember, `{guildId}-{userId}`, function() - return Construct({ - discordClient = discordClient, - }, DiscordGuildMember.Prototype) - end) - - if memberData.user then - memberData.user = DiscordUser.new(self.discordClient, memberData.user) - end - - memberData.guildId = guildId - - for index, value in memberData do - self[index] = value - end - - return self -end - -export type DiscordGuildMember = typeof(DiscordGuildMember.Prototype) & { - discordClient: any, - user: DiscordUser.DiscordUser, - guildId: number, -} - -return DiscordGuildMember.Interface diff --git a/Package/Classes/Objects/DiscordGuildRole.luau b/Package/Classes/Objects/DiscordGuildRole.luau deleted file mode 100644 index e7e6d18..0000000 --- a/Package/Classes/Objects/DiscordGuildRole.luau +++ /dev/null @@ -1,187 +0,0 @@ -local Future = require("@Vendor/Future") - -local Construct = require("@Utils/Construct") - -local Resolvable = require("@Network/Resolvable") - -local GuildRoleBuilder = require("@Builders/GuildRoleBuilder") -local PermissionsBuilder = require("@Builders/PermissionsBuilder") - -local DiscordEndpoints = require("@Data/DiscordEndpoints") - -local CacheType = require("@Enums/CacheType") -local ResolvableType = require("@Enums/ResolvableType") - ---[=[ - @class Objects.DiscordGuildRole - - The `DiscordGuildRole` class represents a role in a Discord guild (server) and provides a structure for role data. -]=] - ---[=[ - @prop guildId string - @within Objects.DiscordGuildRole -]=] - ---[=[ - @prop id string - @within Objects.DiscordGuildRole -]=] - ---[=[ - @prop name string - @within Objects.DiscordGuildRole -]=] - ---[=[ - @prop permissions string - @within Objects.DiscordGuildRole -]=] - ---[=[ - @prop position number - @within Objects.DiscordGuildRole -]=] - ---[=[ - @prop color number - @within Objects.DiscordGuildRole -]=] - ---[=[ - @prop hoist boolean - @within Objects.DiscordGuildRole -]=] - ---[=[ - @prop managed boolean - @within Objects.DiscordGuildRole -]=] - ---[=[ - @prop mentionable boolean - @within Objects.DiscordGuildRole -]=] - -local DiscordGuildRole = {} - -DiscordGuildRole.Prototype = {} -DiscordGuildRole.Interface = {} - ---[=[ - @prop type string - @within Objects.DiscordGuildRole - @readonly - - The type of the DiscordGuildRole. Default is "DiscordGuildRole". -]=] -DiscordGuildRole.Prototype.type = "DiscordGuildRole" - ---[=[ - Creates a new DiscordGuildRole instance. - - @function updatePositionAsync - @param position number - @return Vendor.Future<> - @within Objects.DiscordGuildRole -]=] -function DiscordGuildRole.Prototype.updatePositionAsync(self: DiscordGuildRole, position: number) - return Future.try(function() - return self.discordClient.discordGateway - :patchAsync( - string.format(DiscordEndpoints.BotModifyGuildRolePositions, self.guildId), - Resolvable.new(ResolvableType.JSON, { - id = self.id, - position = position, - }) - ) - :await() - end) -end - ---[=[ - Allows the developer to update/modify a discord role - - @function modifyAsync - @param position Objects.GuildRoleBuilder - @return Vendor.Future<> - @within Objects.DiscordGuildRole -]=] -function DiscordGuildRole.Prototype.modifyAsync(self: DiscordGuildRole, roleBuilder: GuildRoleBuilder.GuildRoleBuilder) - return Future.try(function() - return self.discordClient.discordGateway - :patchAsync( - string.format(DiscordEndpoints.BotModifyGuildRolePositions, self.guildId, self.id), - roleBuilder:toPayloadObject() - ) - :await() - end) -end - ---[=[ - Deletes the Role from the discord guild asynchronously. - - @function deleteAsync - @return Vendor.Future<> - @within Objects.DiscordGuildRole -]=] -function DiscordGuildRole.Prototype.deleteAsync(self: DiscordGuildRole) - return Future.try(function(): ...any - return self.discordClient.discordGateway - :deleteAsync(string.format(DiscordEndpoints.BotDeleteGuildRole, self.guildId, self.id)) - :await() - end) -end - ---[=[ - Creates a new DiscordGuildRole instance. - - @function new - @param discordClient any -- The Discord client instance. - @param roleData table -- The raw data for the role. - @return DiscordGuildRole -- The newly created DiscordGuildRole instance. - @within Objects.DiscordGuildRole -]=] -function DiscordGuildRole.Interface.new( - discordClient: any, - roleData: { - id: string, - - [string]: any, - } -) - local self = discordClient.discordCache:getDataOr(CacheType.DiscordGuildRole, roleData.id, function() - local roleStruct = { - discordClient = discordClient, - } - - return Construct(roleStruct, DiscordGuildRole.Prototype) - end) - - if roleData.permissions then - roleData.permissions = PermissionsBuilder.from(roleData.permissions) - end - - for index, value in roleData do - self[index] = value - end - - return self -end - -export type DiscordGuildRole = typeof(DiscordGuildRole.Prototype) & { - discordClient: any, - - guildId: string, - - id: string, - name: string, - permissions: PermissionsBuilder.PermissionsBuilder, - position: number, - color: number, - hoist: boolean, - managed: boolean, - mentionable: boolean, -} - -return DiscordGuildRole.Interface diff --git a/Package/Classes/Objects/DiscordIntegration.luau b/Package/Classes/Objects/DiscordIntegration.luau deleted file mode 100644 index 338320e..0000000 --- a/Package/Classes/Objects/DiscordIntegration.luau +++ /dev/null @@ -1,192 +0,0 @@ -local Future = require("@Vendor/Future") - -local Construct = require("@Utils/Construct") - -local DiscordUser = require("@Objects/DiscordUser") -local DiscordApplication = require("@Objects/DiscordApplication") - -local DiscordEndpoints = require("@Data/DiscordEndpoints") - -local CacheType = require("@Enums/CacheType") - ---[=[ - @class Objects.DiscordIntegration - - DiscordIntegration represents a guild integration. -]=] - ---[=[ - @prop guildId string - @within Objects.DiscordIntegration -]=] - ---[=[ - @prop id string - @within Objects.DiscordIntegration -]=] - ---[=[ - @prop name string - @within Objects.DiscordIntegration -]=] - ---[=[ - @prop type number - @within Objects.DiscordIntegration -]=] - ---[=[ - @prop enabled boolean - @within Objects.DiscordIntegration -]=] - ---[=[ - @prop syncing boolean? - @within Objects.DiscordIntegration -]=] - ---[=[ - @prop roleId string? - @within Objects.DiscordIntegration -]=] - ---[=[ - @prop enableEmoticons boolean? - @within Objects.DiscordIntegration -]=] - ---[=[ - @prop expireBehaviour number? - @within Objects.DiscordIntegration -]=] - ---[=[ - @prop expireGracePeriod number? - @within Objects.DiscordIntegration -]=] - ---[=[ - @prop user Objects.DiscordUser? - @within Objects.DiscordIntegration -]=] - ---[=[ - @prop account { id: string, name: string }? - @within Objects.DiscordIntegration -]=] - ---[=[ - @prop syncedAt string? - @within Objects.DiscordIntegration -]=] - ---[=[ - @prop subscriberCount number? - @within Objects.DiscordIntegration -]=] - ---[=[ - @prop revoked boolean? - @within Objects.DiscordIntegration -]=] - ---[=[ - @prop application Objects.Application? - @within Objects.DiscordIntegration -]=] - ---[=[ - @prop scopes { string }? - @within Objects.DiscordIntegration -]=] - -local DiscordIntegration = {} - -DiscordIntegration.Prototype = {} -DiscordIntegration.Interface = {} - -DiscordIntegration.Prototype.type = "DiscordIntegration" - ---[=[ - Deletes the invite asynchronously. - - @method deleteAsync - @return Vendor.Future -- A future that resolves when the invite is deleted. - @within Objects.DiscordIntegration -]=] -function DiscordIntegration.Prototype.deleteAsync(self: DiscordIntegration) - return Future.try(function() - return self.discordClient.discordGateway - :deleteAsync(string.format(DiscordEndpoints.BotDeleteGuildIntegration, self.guildId, self.id)) - :await() - end) -end - ---[=[ - Creates a new instance of DiscordIntegration. - - @function new - @param discordClient any -- The Discord client instance. - @param integrationData table -- The data for the invite to be created. - @return DiscordIntegration -- A new instance of DiscordIntegration. - @within Objects.DiscordIntegration -]=] -function DiscordIntegration.Interface.new( - discordClient: any, - guildId: string, - integrationData: { - id: string, - - [string]: any, - } -) - local self = discordClient.discordCache:getDataOr( - CacheType.DiscordIntegration, - `{guildId}-{integrationData.id}`, - function() - return Construct({ - discordClient = discordClient, - - guildId = guildId, - }, DiscordIntegration.Prototype) - end - ) - - if integrationData.user then - integrationData.user = DiscordUser.new(discordClient, integrationData.user) - end - - if integrationData.application then - integrationData.application = DiscordApplication.new(discordClient, integrationData.application) - end - - for index, value in integrationData do - self[index] = value - end - - return self -end - -export type DiscordIntegration = typeof(DiscordIntegration.Prototype) & { - discordClient: any, - guildId: string, - - id: string, - name: string, - type: number, - enabled: boolean, - syncing: boolean?, - roleId: string?, - enableEmoticons: boolean?, - expireBehaviour: number?, - expireGracePeriod: number?, - user: DiscordUser.DiscordUser?, - account: { id: string, name: string }?, - syncedAt: string?, - subscriberCount: number?, - revoked: boolean?, - application: DiscordApplication.DiscordApplication?, - scopes: { string }?, -} - -return DiscordIntegration.Interface diff --git a/Package/Classes/Objects/DiscordInteraction.luau b/Package/Classes/Objects/DiscordInteraction.luau deleted file mode 100644 index 6b30b08..0000000 --- a/Package/Classes/Objects/DiscordInteraction.luau +++ /dev/null @@ -1,291 +0,0 @@ -local DiscordEndpoints = require("@Data/DiscordEndpoints") - -local Future = require("@Vendor/Future") - -local Construct = require("@Utils/Construct") - -local ModalBuilder = require("@Builders/ModalBuilder") -local PermissionsBuilder = require("@Builders/PermissionsBuilder") -local MessageBuilder = require("@Builders/MessageBuilder") - -local DiscordGuildMember = require("@Objects/DiscordGuildMember") - -local DiscordChannel = require("@Objects/DiscordChannel") -local DiscordUser = require("@Objects/DiscordUser") -local DiscordGuild = require("@Objects/DiscordGuild") - -local Resolvable = require("@Network/Resolvable") -local ResolvableType = require("@Enums/ResolvableType") - ---[=[ - @class Objects.DiscordInteraction - - The `DiscordInteraction` class represents an interaction with the Discord API, such as a message interaction, a command, or a modal submission. -]=] - ---[=[ - @prop channel Objects.DiscordChannel - @within Objects.DiscordInteraction -]=] - ---[=[ - @prop guild objects.DiscordGuild - @within Objects.DiscordInteraction -]=] - ---[=[ - @prop user objects.DiscordUser - @within Objects.DiscordInteraction -]=] - ---[=[ - @prop token string - @within Objects.DiscordInteraction -]=] - ---[=[ - @prop applicationId string - @within Objects.DiscordInteraction -]=] - ---[=[ - @prop id string - @within Objects.DiscordInteraction -]=] - ---[=[ - @prop data { [any]: any } - @within Objects.DiscordInteraction -]=] - ---[=[ - @prop channelId string - @within Objects.DiscordInteraction -]=] - ---[=[ - @prop guilldId string - @within Objects.DiscordInteraction -]=] - ---[=[ - @prop version number - @within Objects.DiscordInteraction -]=] - ---[=[ - @prop appPermissions Builders.PermissionsBuilder - @within Objects.DiscordInteraction -]=] - ---[=[ - @prop guildLocale string - @within Objects.DiscordInteraction -]=] - ---[=[ - @prop locale string - @within Objects.DiscordInteraction -]=] - ---[=[ - @prop deferred boolean - @within Objects.DiscordInteraction -]=] - -local DiscordInteraction = {} - -DiscordInteraction.Interface = {} -DiscordInteraction.Prototype = {} - ---[=[ - @prop type string - @within Objects.DiscordInteraction - @readonly - - The type of the DiscordInteraction. Default is "DiscordInteraction". -]=] -DiscordInteraction.Prototype.type = "DiscordInteraction" - ---[=[ - Edits the original interaction response message asynchronously. - - @method editMessageAsync - @param messageBuilder MessageBuilder -- The message builder object. - @return Vendor.Future - @within Objects.DiscordInteraction -]=] -function DiscordInteraction.Prototype.editMessageAsync( - self: DiscordInteraction, - messageBuilder: MessageBuilder.MessageBuilder -) - return Future.new(function() - return self.discordClient.discordGateway - :patchAsync( - string.format( - DiscordEndpoints.EditOriginalInteractionResponse, - self.discordClient.discordApplication.id, - self.token - ), - messageBuilder:toPayloadObject() - ) - :await() - end) -end - ---[=[ - Sends a modal in response to the interaction asynchronously. - - @method sendModalAsync - @param modalObject ModalBuilder -- The modal builder object. - @return Vendor.Future - @within Objects.DiscordInteraction -]=] -function DiscordInteraction.Prototype.sendModalAsync(self: DiscordInteraction, modalObject: ModalBuilder.ModalBuilder) - return Future.new(function() - return self.discordClient.discordGateway - :postAsync( - string.format(DiscordEndpoints.CreateInteractionResponse, self.id, self.token), - Resolvable.new(ResolvableType.JSON, { - type = 9, - data = modalObject:toPayloadObject():resolve(), - }) - ) - :await() - end) -end - ---[=[ - Sends a message in response to the interaction asynchronously. - - :::caution - MessageBuilder cannot support Attachments UNLESS deferred, so if you're wanting to send attachments, please call `deferAsync` first! - ::: - - @method sendMessageAsync - @param messageBuilder MessageBuilder -- The message builder object. - @return Vendor.Future - @within Objects.DiscordInteraction -]=] -function DiscordInteraction.Prototype.sendMessageAsync( - self: DiscordInteraction, - messageBuilder: MessageBuilder.MessageBuilder -) - if self.deferred then - return self:editMessageAsync(messageBuilder) - else - return Future.new(function() - local payloadObject = messageBuilder:toPayloadObject() - - return self.discordClient.discordGateway - :postAsync( - string.format(DiscordEndpoints.CreateInteractionResponse, self.id, self.token), - payloadObject:update(function(data) - return { - type = 4, - data = data, - } - end) - ) - :await() - end) - end -end - ---[=[ - Defers the interaction response asynchronously. - - @method deferAsync - @return Vendor.Future - @within Objects.DiscordInteraction -]=] -function DiscordInteraction.Prototype.deferAsync(self: DiscordInteraction) - return Future.try(function() - self.deferred = true - - self.discordClient.discordGateway - :postAsync( - string.format(DiscordEndpoints.CreateInteractionResponse, self.id, self.token), - Resolvable.new(ResolvableType.JSON, { - type = 5, - data = {}, - }) - ) - :await() - end) -end - ---[=[ - Creates a new DiscordInteraction instance. - - @function new - @param discordClient any -- The Discord client instance. - @param jsonData table -- The raw data for the interaction. - @return DiscordInteraction -- The newly created DiscordInteraction instance. - @within Objects.DiscordInteraction -]=] -function DiscordInteraction.Interface.new(discordClient: any, jsonData: { [any]: any }) - local interactionStruct = { - discordClient = discordClient, - - deferred = false, - } - - if jsonData.channel then - jsonData.channel = DiscordChannel.from(discordClient, jsonData.channel) - end - - if jsonData.guild then - jsonData.guild = DiscordGuild.new(discordClient, jsonData.guild) - end - - if jsonData.user then - jsonData.user = DiscordUser.new(discordClient, jsonData.user) - end - - if jsonData.member then - jsonData.member = - DiscordGuildMember.new(discordClient, jsonData.member.user.id, jsonData.guild.id, jsonData.member) - - jsonData.member.user = nil - end - - if jsonData.appPermissions then - jsonData.appPermissions = PermissionsBuilder.from(jsonData.appPermissions) - end - - for index, value in jsonData do - interactionStruct[index] = value - end - - return Construct(interactionStruct, DiscordInteraction.Prototype) -end - -export type DiscordInteraction = typeof(DiscordInteraction.Prototype) & { - discordClient: any, - - channel: DiscordChannel.DiscordChannel, - guild: DiscordGuild.DiscordGuild, - user: DiscordUser.DiscordUser, - - token: string, - applicationId: string, - id: string, - data: { - name: string, - types: number, - id: string, - }, - channelId: string, - guilldId: string, - version: number, - appPermissions: PermissionsBuilder.PermissionsBuilder, -- todo: implement discord permission - guildLocale: string, - locale: string, - -- entitlements: { unknown }, -- todo: support interaction entitlements - -- entitlementSkuIds: { unknown }, -- todo: support interaction entitlements - - deferred: boolean, -} - -return DiscordInteraction.Interface diff --git a/Package/Classes/Objects/DiscordInvite.luau b/Package/Classes/Objects/DiscordInvite.luau deleted file mode 100644 index d5d1850..0000000 --- a/Package/Classes/Objects/DiscordInvite.luau +++ /dev/null @@ -1,130 +0,0 @@ -local Future = require("@Vendor/Future") - -local Construct = require("@Utils/Construct") - -local DiscordUser = require("@Objects/DiscordUser") - -local DiscordEndpoints = require("@Data/DiscordEndpoints") - -local CacheType = require("@Enums/CacheType") - ---[=[ - @class Objects.DiscordInvite - - DiscordInvite represents an invite link to a Discord server, providing methods to delete the invite. -]=] - ---[=[ - @prop code string - @within Objects.DiscordInvite -]=] - ---[=[ - @prop type number - @within Objects.DiscordInvite -]=] - ---[=[ - @prop guild { id: string, name: string }? - @within Objects.DiscordInvite -]=] - ---[=[ - @prop inviter Objects.DiscordUser? - @within Objects.DiscordInvite -]=] - ---[=[ - @prop targetType number - @within Objects.DiscordInvite -]=] - ---[=[ - @prop targetUser Objects.DiscordUser? - @within Objects.DiscordInvite -]=] - ---[=[ - @prop targetApplication { id: string }? - @within Objects.DiscordInvite -]=] - -local DiscordInvite = {} - -DiscordInvite.Prototype = {} -DiscordInvite.Interface = {} - -DiscordInvite.Prototype.type = "DiscordInvite" - ---[=[ - Deletes the invite asynchronously. - - @method deleteAsync - @return Vendor.Future -- A future that resolves when the invite is deleted. - @within Objects.DiscordInvite -]=] -function DiscordInvite.Prototype.deleteAsync(self: DiscordInvite) - return Future.try(function() - return self.discordClient.discordGateway - :deleteAsync(string.format(DiscordEndpoints.BotDeleteInvite, self.code)) - :await() - end) -end - ---[=[ - Creates a new instance of DiscordInvite. - - @function new - @param discordClient any -- The Discord client instance. - @param inviteData table -- The data for the invite to be created. - @return DiscordInvite -- A new instance of DiscordInvite. - @within Objects.DiscordInvite -]=] -function DiscordInvite.Interface.new( - discordClient: any, - inviteData: { - code: string, - - [string]: any, - } -) - local self = discordClient.discordCache:getDataOr(CacheType.DiscordInvite, inviteData.code, function() - return Construct({ - discordClient = discordClient, - }, DiscordInvite.Prototype) - end) - - if inviteData then - if inviteData.inviter then - inviteData.inviter = DiscordUser.new(discordClient, inviteData.inviter) - end - - if inviteData.targetUser then - inviteData.targetUser = DiscordUser.new(discordClient, inviteData.targetUser) - end - - for index, value in inviteData do - self[index] = value - end - end - - return self -end - -export type DiscordInvite = typeof(DiscordInvite.Prototype) & { - discordClient: any, - code: string, - type: number, - guild: { - id: string, - name: string, - }?, - inviter: DiscordUser.DiscordUser?, - targetType: number, - targetUser: DiscordUser.DiscordUser?, - targetApplication: { - id: string, - }?, -} - -return DiscordInvite.Interface diff --git a/Package/Classes/Objects/DiscordMessage.luau b/Package/Classes/Objects/DiscordMessage.luau deleted file mode 100644 index e989b23..0000000 --- a/Package/Classes/Objects/DiscordMessage.luau +++ /dev/null @@ -1,489 +0,0 @@ -local Future = require("@Vendor/Future") - -local Net = require("@Std/Net") - -local Construct = require("@Utils/Construct") - -local DiscordEndpoints = require("@Data/DiscordEndpoints") - -local Resolvable = require("@Network/Resolvable") - -local MessageBuilder = require("@Builders/MessageBuilder") -local DiscordGuildMember = require("@Objects/DiscordGuildMember") - -local ActionRowBuilder = require("@Builders/Interface/ActionRowBuilder") -local ButtonBuilder = require("@Builders/Interface/ButtonBuilder") -local SelectionBuilder = require("@Builders/Interface/SelectionBuilder") -local TextInputBuilder = require("@Builders/Interface/TextInputBuilder") - -local ResolvableType = require("@Enums/ResolvableType") -local CacheType = require("@Enums/CacheType") - ---[=[ - @class Objects.DiscordMessage - - DiscordMessage represents a message in a Discord channel, providing methods to reply, delete, pin, unpin, edit, and manage reactions. -]=] - ---[=[ - @prop mentionRoles { string } - @within Objects.DiscordMessage -]=] - ---[=[ - @prop tts boolean - @within Objects.DiscordMessage -]=] - ---[=[ - @prop mentionEveryone boolean - @within Objects.DiscordMessage -]=] - ---[=[ - @prop nonce string - @within Objects.DiscordMessage -]=] - ---[=[ - @prop id string - @within Objects.DiscordMessage -]=] - ---[=[ - @prop author { username: string, globalName: string, avatar: string, id: string, publicFlags: number, discriminator: string } - @within Objects.DiscordMessage -]=] - ---[=[ - @prop content string - @within Objects.DiscordMessage -]=] - ---[=[ - @prop mentions { string } - @within Objects.DiscordMessage -]=] - ---[=[ - @prop flags string - @within Objects.DiscordMessage -]=] - ---[=[ - @prop timestamp string - @within Objects.DiscordMessage -]=] - ---[=[ - @prop type number - @within Objects.DiscordMessage -]=] - ---[=[ - @prop pinned boolean - @within Objects.DiscordMessage -]=] - ---[=[ - @prop guildId string - @within Objects.DiscordMessage -]=] - ---[=[ - @prop channelId string - @within Objects.DiscordMessage -]=] - ---[=[ - @prop member { flags: number, deaf: boolean, roles: { [string]: string }, pending: boolean, mute: boolean, joinedAt: string } - @within Objects.DiscordMessage -]=] - -local DiscordMessage = {} - -DiscordMessage.Prototype = {} -DiscordMessage.Interface = {} - -DiscordMessage.Prototype.type = "DiscordMessage" - ---[=[ - Crossposts the message in an Announcement Channel to all following channels. - - @method crosspostAsync - @return Vendor.Future - @within Objects.DiscordMessage -]=] -function DiscordMessage.Prototype.crosspostAsync(self: DiscordMessage) - return Future.try(function() - local messageObject = self.discordClient.discordGateway - :postAsync(string.format(DiscordEndpoints.BotCrosspostMessage, self.channelId, self.id)) - :await() - - return DiscordMessage.Interface.new(self.discordClient, messageObject) - end) -end - ---[=[ - Replies to the message asynchronously. - - @method replyAsync - @param messageBuilder MessageBuilder -- The message builder containing the reply content. - @return Vendor.Future -- A future that resolves when the reply is sent. - @within Objects.DiscordMessage -]=] -function DiscordMessage.Prototype.replyAsync(self: DiscordMessage, messageBuilder: MessageBuilder.MessageBuilder) - return Future.try(function() - return self.discordClient.discordGateway - :postAsync( - string.format(DiscordEndpoints.BotCreateMessage, self.channelId), - messageBuilder:toPayloadObject() - ) - :await() - end) -end - ---[=[ - Start a thread from the current message object. - - @method startThreadAsync - @param name string - @param autoArchiveDuration: number - @param rateLimitPerUser: number? - @return Vendor.Future - @within Objects.DiscordMessage -]=] -function DiscordMessage.Prototype.startThreadAsync( - self: DiscordMessage, - name: string, - autoArchiveDuration: number, - rateLimitPerUser: number? -) - return Future.try(function() - local threadChannel = self.discordClient.discordGateway - :postAsync( - string.format(DiscordEndpoints.BotCreateThreadFromMessage, self.channelId, self.id), - Resolvable.new(ResolvableType.JSON, { - name = name, - auto_archive_duration = autoArchiveDuration, - rate_limit_per_user = rateLimitPerUser, - }) - ) - :await() - - -- fixme: man this sucks, discordChannel requires DiscordMessage - so we can't create the object for you - -- you'll need to call :getChannelAsync through the client.. sorry. - - return threadChannel.id - end) -end - ---[=[ - Deletes the message asynchronously. - - @method deleteAsync - @return Vendor.Future -- A future that resolves when the message is deleted. - @within Objects.DiscordMessage -]=] -function DiscordMessage.Prototype.deleteAsync(self: DiscordMessage) - return Future.try(function() - return self.discordClient.discordGateway - :deleteAsync(string.format(DiscordEndpoints.BotDeleteMessage, self.channelId, self.id)) - :await() - end) -end - ---[=[ - Pins the message asynchronously. - - @method pinAsync - @return Vendor.Future -- A future that resolves when the message is pinned. - @within Objects.DiscordMessage -]=] -function DiscordMessage.Prototype.pinAsync(self: DiscordMessage) - return Future.try(function() - return self.discordClient.discordGateway - :putAsync(string.format(DiscordEndpoints.BotPinMessage, self.channelId, self.id)) - :await() - end) -end - ---[=[ - Unpins the message asynchronously. - - @method unpinAsync - @return Vendor.Future -- A future that resolves when the message is unpinned. - @within Objects.DiscordMessage -]=] -function DiscordMessage.Prototype.unpinAsync(self: DiscordMessage) - return Future.try(function() - return self.discordClient.discordGateway - :deleteAsync(string.format(DiscordEndpoints.BotUnpinMessage, self.channelId, self.id)) - :await() - end) -end - ---[=[ - Edits the message asynchronously. - - @method editAsync - @param messageBuilder MessageBuilder -- The message builder containing the new content. - @within Objects.DiscordMessage - @return Vendor.Future -- A future that resolves when the message is edited. -]=] -function DiscordMessage.Prototype.editAsync(self: DiscordMessage, messageBuilder: MessageBuilder.MessageBuilder) - return Future.try(function() - return self.discordClient.discordGateway - :patchAsync( - string.format(DiscordEndpoints.BotEditMessage, self.channelId, self.id), - messageBuilder:toPayloadObject() - ) - :await() - end) -end - ---[=[ - Adds a reaction to the message asynchronously. - - @method addReactionAsync - @param reaction string -- The reaction to be added. - @within Objects.DiscordMessage - @return Vendor.Future -- A future that resolves when the reaction is added. -]=] -function DiscordMessage.Prototype.addReactionAsync(self: DiscordMessage, reaction: string) - return Future.try(function() - local isBinary = string.find(reaction, ":") == nil - - return self.discordClient.discordGateway - :putAsync( - string.format( - DiscordEndpoints.BotCreateReaction, - self.channelId, - self.id, - Net.urlEncode(reaction, isBinary) - ) - ) - :await() - end) -end - ---[=[ - Removes a reaction from the message asynchronously. - - @method removeReactionAsync - @param reaction string -- The reaction to be removed. - @within Objects.DiscordMessage - @return Vendor.Future -- A future that resolves when the reaction is removed. -]=] -function DiscordMessage.Prototype.removeReactionAsync(self: DiscordMessage, reaction: string) - return Future.try(function() - local isBinary = string.find(reaction, ":") == nil - - return self.discordClient.discordGateway - :deleteAsync( - string.format( - DiscordEndpoints.BotDeleteReaction, - self.channelId, - self.id, - Net.urlEncode(reaction, isBinary) - ) - ) - :await() - end) -end - ---[=[ - - Removes a reaction from a specific user on the message asynchronously. - - @method removeUserReactionAsync - @param userId string -- The ID of the user whose reaction is to be removed. - @param reaction string -- The reaction to be removed. - @within Objects.DiscordMessage - @return Vendor.Future -- A future that resolves when the user's reaction is removed. -]=] -function DiscordMessage.Prototype.removeUserReactionAsync(self: DiscordMessage, userId: string, reaction: string) - return Future.try(function() - local isBinary = string.find(reaction, ":") == nil - - return self.discordClient.discordGateway - :deleteAsync( - string.format( - DiscordEndpoints.BotDeleteUserReaction, - self.channelId, - self.id, - Net.urlEncode(reaction, isBinary), - userId - ) - ) - :await() - end) -end - ---[=[ - Gets the reactions on the message asynchronously. - - @method getReactionsAsync - @param reaction string -- The reaction to retrieve. - @within Objects.DiscordMessage - @return Vendor.Future -- A future that resolves to the reactions. -]=] -function DiscordMessage.Prototype.getReactionsAsync(self: DiscordMessage, reaction: string) - return Future.try(function() - local isBinary = string.find(reaction, ":") == nil - - return self.discordClient.discordGateway - :getAsync( - string.format( - DiscordEndpoints.BotGetReactions, - self.channelId, - self.id, - Net.urlEncode(reaction, isBinary) - ) - ) - :await() - end) -end - ---[=[ - Removes all reactions from the message asynchronously. - - @method removeAllReactionsAsync - @return Vendor.Future -- A future that resolves when all reactions are removed. - @within Objects.DiscordMessage -]=] -function DiscordMessage.Prototype.removeAllReactionsAsync(self: DiscordMessage) - return Future.try(function() - return self.discordClient.discordGateway - :deleteAsync(string.format(DiscordEndpoints.BotDeleteAllReactions, self.channelId, self.id)) - :await() - end) -end - ---[=[ - Removes all reactions for a specific emoji from the message asynchronously. - - @method removeAllReactionsForEmojiAsync - @param reaction string -- The emoji whose reactions are to be removed. - @within Objects.DiscordMessage - @return Vendor.Future -- A future that resolves when the reactions for the emoji are removed. -]=] -function DiscordMessage.Prototype.removeAllReactionsForEmojiAsync(self: DiscordMessage, reaction: string) - return Future.try(function() - local isBinary = string.find(reaction, ":") == nil - - return self.discordClient.discordGateway - :deleteAsync( - string.format( - DiscordEndpoints.BotDeleteAllReactionsForEmoji, - self.channelId, - self.id, - Net.urlEncode(reaction, isBinary) - ) - ) - :await() - end) -end - ---[=[ - Creates a new instance of DiscordMessage. - - @function new - @param discordClient any -- The Discord client instance. - @param messageData table -- The data for the message to be created. - @within Objects.DiscordMessage - @return DiscordMessage -- A new instance of DiscordMessage. -]=] -function DiscordMessage.Interface.new( - discordClient: any, - messageData: { - id: string, - - guildId: string, - channelId: string, - - author: { - username: string, - avatar: string, - id: string, - bot: boolean, - discriminator: string, - publicFlags: number, - }, - - member: { - [any]: any, - }, - } -) - local self = discordClient.discordCache:getDataOr(CacheType.DiscordMessage, messageData.id, function() - local messageStruct = { - discordClient = discordClient, - } - - if messageData.member then - messageData.member = - DiscordGuildMember.new(discordClient, messageData.author.id, messageData.guildId, messageData.member) - end - if messageData.guildId then - local discordChannel = discordClient.discordCache:getData(CacheType.DiscordChannel, messageData.guildId) - - if discordChannel then - discordChannel.lastMessageId = messageData.id - end - end - - return Construct(messageStruct, DiscordMessage.Prototype) - end) - - for index, value in messageData do - self[index] = value - end - - return self -end - -type InterfaceBuidler = - ActionRowBuilder.ActionRowBuilder - | ButtonBuilder.ButtonBuilder - | SelectionBuilder.SelectionBuilder - | TextInputBuilder.TextInputBuilder - -export type DiscordMessage = typeof(DiscordMessage.Prototype) & { - discordClient: any, - - mentionRoles: { string }, - -- components: { InterfaceBuidler }, -- todo: add components to message - -- attachments: { unknown }, -- todo: add attachments from messages - -- embeds: { unknown }, -- todo: add embeds from messages - tts: boolean, - mentionEveryone: boolean, - nonce: string, - id: string, - author: { - username: string, - globalName: string, - avatar: string, - id: string, - publicFlags: number, - discriminator: string, - }, - content: string, - mentions: { string }, - flags: string, - timestamp: string, - type: number, - pinned: boolean, - guildId: string, - channelId: string, - member: { - flags: number, - deaf: boolean, - roles: { [string]: string }, - pending: boolean, - mute: boolean, - joinedAt: string, - }, -} - -return DiscordMessage.Interface diff --git a/Package/Classes/Objects/DiscordPermission.luau b/Package/Classes/Objects/DiscordPermission.luau deleted file mode 100644 index 5e540bd..0000000 --- a/Package/Classes/Objects/DiscordPermission.luau +++ /dev/null @@ -1,46 +0,0 @@ -local Construct = require("@Utils/Construct") - -local PermissionsBuilder = require("@Builders/PermissionsBuilder") - ---[=[ - @class Builders.DiscordPermission - - Represents a set of Discord permissions. -]=] - ---[=[ - @prop permissions { number } - @within Builders.DiscordPermission -]=] - -local DiscordPermission = {} - -DiscordPermission.Interface = {} -DiscordPermission.Prototype = {} - -DiscordPermission.Prototype.type = "DiscordPermission" - -function DiscordPermission.Prototype.hasPermission(self: DiscordPermission, permission: number) - return table.find(self.permissions, permission) ~= nil -end - -function DiscordPermission.Interface.from(bitfield: string) - local permissions = Construct({ permissions = {} }, DiscordPermission.Prototype) - local numberBitfield = tonumber(bitfield) - - assert(numberBitfield, `Invalid BitField '{bitfield}'`) - - for _, value in PermissionsBuilder.Permissions :: { number } do - if bit32.band(numberBitfield, value) == value then - table.insert(permissions.permissions, value) - end - end - - return permissions -end - -export type DiscordPermission = typeof(DiscordPermission.Prototype) & { - permissions: { number }, -} - -return DiscordPermission.Interface diff --git a/Package/Classes/Objects/DiscordSticker.luau b/Package/Classes/Objects/DiscordSticker.luau deleted file mode 100644 index c19682c..0000000 --- a/Package/Classes/Objects/DiscordSticker.luau +++ /dev/null @@ -1,132 +0,0 @@ -local Construct = require("@Utils/Construct") - -local DiscordUser = require("@Objects/DiscordUser") - -local CacheType = require("@Enums/CacheType") - ---[=[ - @class Objects.DiscordSticker - - The `DiscordSticker` class represents a Discord sticker and provides a structure for sticker data. -]=] - ---[=[ - @prop id string - @within Objects.DiscordSticker -]=] - ---[=[ - @prop packId string - @within Objects.DiscordSticker -]=] - ---[=[ - @prop name string - @within Objects.DiscordSticker -]=] - ---[=[ - @prop description string - @within Objects.DiscordSticker -]=] - ---[=[ - @prop tags string - @within Objects.DiscordSticker -]=] - ---[=[ - @prop type number - @within Objects.DiscordSticker -]=] - ---[=[ - @prop formatType number - @within Objects.DiscordSticker -]=] - ---[=[ - @prop available boolean? - @within Objects.DiscordSticker -]=] - ---[=[ - @prop guildId string? - @within Objects.DiscordSticker -]=] - ---[=[ - @prop user Objects.DiscordUser? - @within Objects.DiscordSticker -]=] - ---[=[ - @prop sortValue number? - @within Objects.DiscordSticker -]=] - -local DiscordSticker = {} - -DiscordSticker.Prototype = {} -DiscordSticker.Interface = {} - ---[=[ - @prop type string - @within Objects.DiscordSticker - @readonly - - The type of the DiscordSticker. Default is "DiscordSticker". -]=] -DiscordSticker.Prototype.type = "DiscordSticker" - ---[=[ - Creates a new DiscordSticker instance. - - @function new - @param discordClient any -- The Discord client instance. - @param emojiData table -- The raw data for the emoji. - @return DiscordSticker -- The newly created DiscordSticker instance. - @within Objects.DiscordSticker -]=] -function DiscordSticker.Interface.new( - discordClient: any, - stickerData: { - id: string, - - [string]: any, - } -) - local self = discordClient.discordCache:getDataOr(CacheType.DiscordSticker, stickerData.id, function() - local emojiStruct = { - discordClient = discordClient, - } - - return Construct(emojiStruct, DiscordSticker.Prototype) - end) - - if stickerData.user then - stickerData.user = DiscordUser.new(discordClient, stickerData.user) - end - - for index, value in stickerData do - self[index] = value - end - - return self -end - -export type DiscordSticker = typeof(DiscordSticker.Prototype) & { - id: string, - packId: string, - name: string, - description: string, - tags: string, - type: number, - formatType: number, - available: boolean?, - guildId: string?, - user: DiscordUser.DiscordUser?, - sortValue: number?, -} - -return DiscordSticker.Interface diff --git a/Package/Classes/Objects/DiscordUser.luau b/Package/Classes/Objects/DiscordUser.luau deleted file mode 100644 index 532928f..0000000 --- a/Package/Classes/Objects/DiscordUser.luau +++ /dev/null @@ -1,108 +0,0 @@ -local Construct = require("@Utils/Construct") - -local CacheType = require("@Enums/CacheType") - ---[=[ - @class Objects.DiscordUser - - DiscordUser represents a user on Discord, encapsulating user data such as username, avatar, and various flags. -]=] - ---[=[ - @prop username string - @within Objects.DiscordUser -]=] - ---[=[ - @prop flags string - @within Objects.DiscordUser -]=] - ---[=[ - @prop mfaEnabled boolean - @within Objects.DiscordUser -]=] - ---[=[ - @prop avatar string - @within Objects.DiscordUser -]=] - ---[=[ - @prop id string - @within Objects.DiscordUser -]=] - ---[=[ - @prop bot boolean - @within Objects.DiscordUser -]=] - ---[=[ - @prop verified boolean - @within Objects.DiscordUser -]=] - ---[=[ - @prop discriminator string - @within Objects.DiscordUser -]=] - -local DiscordUser = {} - -DiscordUser.Prototype = {} -DiscordUser.Interface = {} - -DiscordUser.Prototype.type = "DiscordUser" - ---[=[ - Creates a new instance of DiscordUser. - - @function new - @param discordClient any -- The Discord client instance. - @param partialUserData table -- Partial data for the user to be created. - @within Objects.DiscordUser - @return DiscordUser -- A new instance of DiscordUser. -]=] -function DiscordUser.Interface.new( - discordClient: any, - partialUserData: { - username: string?, - flags: number?, - mfaEnabled: boolean?, - avatar: string?, - id: string, - bot: boolean?, - verified: boolean?, - discriminator: string?, - } -) - local self = discordClient.discordCache:getDataOr(CacheType.DiscordUser, partialUserData.id, function() - return Construct({ - id = partialUserData.id, - - discordClient = discordClient, - }, DiscordUser.Prototype) - end) - - if partialUserData then - for index, value in partialUserData do - self[index] = value - end - end - - return self -end - -export type DiscordUser = typeof(DiscordUser.Prototype) & { - username: string?, - flags: string?, - mfaEnabled: boolean?, - avatar: string?, - id: string, - bot: boolean?, - verified: boolean?, - discriminator: string?, -} - -return DiscordUser.Interface diff --git a/Package/Classes/Objects/EndpointCache.luau b/Package/Classes/Objects/EndpointCache.luau deleted file mode 100644 index 070d04e..0000000 --- a/Package/Classes/Objects/EndpointCache.luau +++ /dev/null @@ -1,89 +0,0 @@ -local Task = require("@Std/Task") - -local Construct = require("@Utils/Construct") - ---[=[ - @class Objects.EndpointCache - - EndpointCache is a caching mechanism designed to store data temporarily with an expiration time. -]=] - ---[=[ - @prop expiry number - @within Objects.EndpointCache -]=] - -local EndpointCache = {} - -EndpointCache.Prototype = {} -EndpointCache.Interface = {} - -EndpointCache.Prototype.type = "EndpointCache" - ---[=[ - Sets the expiry time for the cache. - - @method setExpiry - @within Objects.EndpointCache - @param seconds number -- The number of seconds before the cached data expires. -]=] -function EndpointCache.Prototype.setExpiry(self: EndpointCache, seconds: number) - self.expiry = seconds -end - ---[=[ - Sets the value in the cache and starts the expiry timer. - - @method set - @within Objects.EndpointCache - @param value any -- The value to be cached. -]=] -function EndpointCache.Prototype.set(self: EndpointCache, value: any) - self.data = value - - if self.expiryThread then - Task.cancel(self.expiryThread) - end - - self.expiryThread = Task.spawn(function() - Task.wait(self.expiry) - - self.data = nil - self.expiryThread = nil - end) -end - ---[=[ - Gets the current value from the cache. - - @method get - @within Objects.EndpointCache - @return any -- The cached value, or nil if the cache is empty or expired. -]=] -function EndpointCache.Prototype.get(self: EndpointCache) - return self.data -end - ---[=[ - - Creates a new instance of EndpointCache. - - @function new - @param expiryTime number? -- (Optional) The expiry time for the cache in seconds. Defaults to 5 seconds if not provided. - @within Objects.EndpointCache - @return EndpointCache -- A new instance of EndpointCache. -]=] -function EndpointCache.Interface.new(expiryTime: number?) - return Construct({ - expiry = expiryTime or 5, - }, EndpointCache.Prototype) -end - -export type EndpointCache = typeof(EndpointCache.Prototype) & { - expiry: number, - - data: any?, - expiryThread: thread?, -} - -return EndpointCache.Interface diff --git a/Package/Classes/Objects/EventManager.luau b/Package/Classes/Objects/EventManager.luau deleted file mode 100644 index c703525..0000000 --- a/Package/Classes/Objects/EventManager.luau +++ /dev/null @@ -1,247 +0,0 @@ -local Signal = require("@Vendor/Signal") - -local Construct = require("@Utils/Construct") - -local DiscordMessage = require("@Objects/DiscordMessage") -local DiscordChannel = require("@Objects/DiscordChannel") - -local DiscordUser = require("@Objects/DiscordUser") -local DiscordGuild = require("@Objects/DiscordGuild") -local DiscordGuildMember = require("@Objects/DiscordGuildMember") - -local DiscordInteraction = require("@Objects/DiscordInteraction") - -local DiscordEvents = require("@Enums/DiscordEvents") - ---[=[ - @class Objects.EventManager - - EventManager is responsible for handling Discord events and emitting signals - for various Discord activities such as message events, channel events, user updates, - guild events, and interactions. -]=] -local EventManager = {} - -EventManager.Prototype = {} -EventManager.Interface = {} - -EventManager.Prototype.type = "EventManager" - ---[=[ - - Creates a new instance of EventManager and connects it to the provided Discord client. - - @function new - @param discordClient any - @within Objects.EventManager - @return EventManager -]=] - ---[=[ - @prop onReady Vendor.Signal<> - @within Objects.EventManager -]=] - ---[=[ - @prop onMessage Vendor.Signal - @within Objects.EventManager -]=] - ---[=[ - @prop onMessageChanged Vendor.Signal - @within Objects.EventManager -]=] - ---[=[ - @prop onMessageDeleted Vendor.Signal - @within Objects.EventManager -]=] - ---[=[ - @prop onMessageBulkDeleted Vendor.Signal<{ channelId: string, guildId: string, ids: { string } }> - @within Objects.EventManager -]=] - ---[=[ - @prop onChannelCreate Vendor.Signal - @within Objects.EventManager -]=] - ---[=[ - @prop onChannelUpdate Vendor.Signal - @within Objects.EventManager -]=] - ---[=[ - @prop onChannelDelete Vendor.Signal - @within Objects.EventManager -]=] - ---[=[ - @prop onUserUpdated Vendor.Signal - @within Objects.EventManager -]=] - ---[=[ - @prop onChannelPinsUpdate Vendor.Signal<{ guildId: string, channelId: string, lastPinTimestamp: string }> - @within Objects.EventManager -]=] - ---[=[ - @prop onGuildCreate Vendor.Signal - @within Objects.EventManager -]=] - ---[=[ - @prop onGuildUpdate Vendor.Signal - @within Objects.EventManager -]=] - ---[=[ - @prop onGuildDelete Vendor.Signal - @within Objects.EventManager -]=] - ---[=[ - @prop onGuildMemberBanned Vendor.Signal<{ guildId: string, user: Objects.DiscordUser }> - @within Objects.EventManager -]=] - ---[=[ - @prop onGuildMemberUnbanned Vendor.Signal<{ guildId: string, user: Objects.DiscordUser }> - @within Objects.EventManager -]=] - ---[=[ - @prop onGuildMemberJoined Vendor.Signal - @within Objects.EventManager -]=] - ---[=[ - @prop onGuildMemberLeft Vendor.Signal<{ guildId: string, user: Objects.DiscordUser }> - @within Objects.EventManager -]=] - ---[=[ - @prop onGuildMemberUpdated Vendor.Signal<{ ... }> - @within Objects.EventManager -]=] - ---[=[ - @prop onInteraction Vendor.Signal - @within Objects.EventManager - - Fires when an interaction occurs. -]=] - -function EventManager.Interface.new(discordClient): EventManager - local self = Construct({ - onReady = Signal.new(), - onMessage = Signal.new(), - onMessageChanged = Signal.new(), - onMessageDeleted = Signal.new(), - onMessageBulkDeleted = Signal.new(), - onChannelCreate = Signal.new(), - onChannelUpdate = Signal.new(), - onChannelDelete = Signal.new(), - onUserUpdated = Signal.new(), - onChannelPinsUpdate = Signal.new(), - onGuildCreate = Signal.new(), - onGuildUpdate = Signal.new(), - onGuildDelete = Signal.new(), - onGuildMemberBanned = Signal.new(), - onGuildMemberUnbanned = Signal.new(), - onGuildMemberJoined = Signal.new(), - onGuildMemberLeft = Signal.new(), - onGuildMemberUpdated = Signal.new(), - onInteraction = Signal.new(), - }, EventManager.Prototype) - - discordClient.onEvent:connect(function(eventName: DiscordEvents.DiscordEvents, ...) - if eventName == DiscordEvents.Ready then - self.onReady:fire(...) - elseif eventName == DiscordEvents.Message then - self.onMessage:fire(...) - elseif eventName == DiscordEvents.MessageChanged then - self.onMessageChanged:fire(...) - elseif eventName == DiscordEvents.MessageDeleted then - self.onMessageDeleted:fire(...) - elseif eventName == DiscordEvents.MessageBulkDeleted then - self.onMessageBulkDeleted:fire(...) - elseif eventName == DiscordEvents.ChannelCreate then - self.onChannelCreate:fire(...) - elseif eventName == DiscordEvents.ChannelUpdate then - self.onChannelUpdate:fire(...) - elseif eventName == DiscordEvents.ChannelDelete then - self.onChannelDelete:fire(...) - elseif eventName == DiscordEvents.UserUpdated then - self.onUserUpdated:fire(...) - elseif eventName == DiscordEvents.ChannelPinsUpdate then - self.onChannelPinsUpdate:fire(...) - elseif eventName == DiscordEvents.GuildCreate then - self.onGuildCreate:fire(...) - elseif eventName == DiscordEvents.GuildUpdate then - self.onGuildUpdate:fire(...) - elseif eventName == DiscordEvents.GuildDelete then - self.onGuildDelete:fire(...) - elseif eventName == DiscordEvents.GuildMemberBanned then - self.onGuildMemberBanned:fire(...) - elseif eventName == DiscordEvents.GuildMemberUnbanned then - self.onGuildMemberUnbanned:fire(...) - elseif eventName == DiscordEvents.GuildMemberJoined then - self.onGuildMemberJoined:fire(...) - elseif eventName == DiscordEvents.GuildMemberLeft then - self.onGuildMemberLeft:fire(...) - elseif eventName == DiscordEvents.GuildMemberUpdated then - self.onGuildMemberUpdated:fire(...) - elseif eventName == DiscordEvents.Interaction then - self.onInteraction:fire(...) - end - end) - - return self :: any -end - -export type EventManager = typeof(EventManager.Prototype) & { - onReady: Signal.Signal, - - onMessage: Signal.Signal, - onMessageChanged: Signal.Signal, - onMessageDeleted: Signal.Signal, - onMessageBulkDeleted: Signal.Signal<{ channelId: string, guildId: string, ids: { string } }>, - - onChannelCreate: Signal.Signal, - onChannelUpdate: Signal.Signal, - onChannelDelete: Signal.Signal, - - onUserUpdated: Signal.Signal, - - onChannelPinsUpdate: Signal.Signal<{ guildId: string, channelId: string, lastPinTimestamp: string }>, - - onGuildCreate: Signal.Signal, - onGuildUpdate: Signal.Signal, - onGuildDelete: Signal.Signal, - - onGuildMemberBanned: Signal.Signal<{ guildId: string, user: DiscordUser.DiscordUser }>, - onGuildMemberUnbanned: Signal.Signal<{ guildId: string, user: DiscordUser.DiscordUser }>, - - onGuildMemberJoined: Signal.Signal, - onGuildMemberLeft: Signal.Signal<{ guildId: string, user: DiscordUser.DiscordUser }>, - onGuildMemberUpdated: Signal.Signal<{ - guildId: string, - roles: { string }, - user: DiscordUser.DiscordUser, - nick: string?, - avatar: string, - joinedAt: string, - premiumSince: string, - deaf: boolean?, - mute: boolean?, - pending: boolean?, - communicationDisabledUntil: string?, - }>, - - onInteraction: Signal.Signal, -} - -return EventManager.Interface diff --git a/Package/Classes/Objects/GuildAnnouncementChannel.luau b/Package/Classes/Objects/GuildAnnouncementChannel.luau deleted file mode 100644 index 8371eb8..0000000 --- a/Package/Classes/Objects/GuildAnnouncementChannel.luau +++ /dev/null @@ -1,47 +0,0 @@ -local Construct = require("@Utils/Construct") -local Extend = require("@Utils/Extend") - -local BaseGuildTextChannel = require("@Objects/BaseGuildTextChannel") - -local CacheType = require("@Enums/CacheType") - ---[=[ - @class Objects.GuildAnnouncementChannel - - Represents the implementation of a Discord Announcement channel. - - @tag inherit Objects.BaseGuildTextChannel -]=] -local GuildAnnouncementChannel = {} - -GuildAnnouncementChannel.Prototype = {} -GuildAnnouncementChannel.Interface = {} - -GuildAnnouncementChannel.Prototype.type = "GuildAnnouncementChannel" - -function GuildAnnouncementChannel.Interface.new( - discordClient, - channelData: { - id: string, - guildId: string, - } -) - local self = discordClient.discordCache:getDataOr(CacheType.DiscordChannel, channelData.id, function() - local channelStruct = { - discordClient = discordClient, - } - - return Construct(channelStruct, GuildAnnouncementChannel.Prototype) - end) - - return (Extend(self, BaseGuildTextChannel.new(discordClient, channelData)) :: unknown) :: GuildAnnouncementChannel -end - -export type GuildAnnouncementChannel = - BaseGuildTextChannel.BaseGuildTextChannel - & typeof(GuildAnnouncementChannel.Prototype) - & { - discordClient: any, - } - -return GuildAnnouncementChannel.Interface diff --git a/Package/Classes/Objects/GuildAnnouncementThreadChannel.luau b/Package/Classes/Objects/GuildAnnouncementThreadChannel.luau deleted file mode 100644 index 0c44798..0000000 --- a/Package/Classes/Objects/GuildAnnouncementThreadChannel.luau +++ /dev/null @@ -1,47 +0,0 @@ -local Construct = require("@Utils/Construct") -local Extend = require("@Utils/Extend") - -local BaseGuildThread = require("@Objects/BaseGuildThread") - -local CacheType = require("@Enums/CacheType") - ---[=[ - @class Objects.GuildAnnouncementThreadChannel - - Represents the implementation of a Discord Announcement thread channel. - - @tag inherit Objects.BaseGuildThread -]=] -local GuildAnnouncementThreadChannel = {} - -GuildAnnouncementThreadChannel.Prototype = {} -GuildAnnouncementThreadChannel.Interface = {} - -GuildAnnouncementThreadChannel.Prototype.type = "GuildAnnouncementThreadChannel" - -function GuildAnnouncementThreadChannel.Interface.new( - discordClient, - channelData: { - id: string, - guildId: string, - } -) - local self = discordClient.discordCache:getDataOr(CacheType.DiscordChannel, channelData.id, function() - local channelStruct = { - discordClient = discordClient, - } - - return Construct(channelStruct, GuildAnnouncementThreadChannel.Prototype) - end) - - return (Extend(self, BaseGuildThread.new(discordClient, channelData)) :: unknown) :: GuildAnnouncementThreadChannel -end - -export type GuildAnnouncementThreadChannel = - BaseGuildThread.BaseGuildThread - & typeof(GuildAnnouncementThreadChannel.Prototype) - & { - discordClient: any, - } - -return GuildAnnouncementThreadChannel.Interface diff --git a/Package/Classes/Objects/GuildCategoryChannel.luau b/Package/Classes/Objects/GuildCategoryChannel.luau deleted file mode 100644 index a4b4ad7..0000000 --- a/Package/Classes/Objects/GuildCategoryChannel.luau +++ /dev/null @@ -1,44 +0,0 @@ -local Construct = require("@Utils/Construct") -local Extend = require("@Utils/Extend") - -local BaseDiscordChannel = require("@Objects/BaseDiscordChannel") - -local CacheType = require("@Enums/CacheType") - ---[=[ - @class Objects.GuildCategoryChannel - - Represents the implementation of a Discord Category channel. - - @tag inherit Objects.BaseDiscordChannel -]=] -local GuildCategoryChannel = {} - -GuildCategoryChannel.Prototype = {} -GuildCategoryChannel.Interface = {} - -GuildCategoryChannel.Prototype.type = "GuildCategoryChannel" - -function GuildCategoryChannel.Interface.new( - discordClient, - channelData: { - id: string, - guildId: string, - } -) - local channelObject = discordClient.discordCache:getDataOr(CacheType.DiscordChannel, channelData.id, function() - local channelStruct = { - discordClient = discordClient, - } - - return Construct(channelStruct, GuildCategoryChannel.Prototype) - end) - - return (Extend(channelObject, BaseDiscordChannel.new(discordClient, channelData)) :: unknown) :: GuildCategoryChannel -end - -export type GuildCategoryChannel = BaseDiscordChannel.BaseDiscordChannel & typeof(GuildCategoryChannel.Prototype) & { - discordClient: any, -} - -return GuildCategoryChannel.Interface diff --git a/Package/Classes/Objects/GuildDirectoryChannel.luau b/Package/Classes/Objects/GuildDirectoryChannel.luau deleted file mode 100644 index b2a7467..0000000 --- a/Package/Classes/Objects/GuildDirectoryChannel.luau +++ /dev/null @@ -1,44 +0,0 @@ -local Construct = require("@Utils/Construct") -local Extend = require("@Utils/Extend") - -local BaseGuildTextChannel = require("@Objects/BaseGuildTextChannel") - -local CacheType = require("@Enums/CacheType") - ---[=[ - @class Objects.GuildDirectoryChannel - - Represents the implementation of a Discord Directory channel. - - @tag inherit Objects.BaseGuildTextChannel -]=] -local GuildDirectoryChannel = {} - -GuildDirectoryChannel.Prototype = {} -GuildDirectoryChannel.Interface = {} - -GuildDirectoryChannel.Prototype.type = "GuildDirectoryChannel" - -function GuildDirectoryChannel.Interface.new( - discordClient, - channelData: { - id: string, - guildId: string, - } -) - local self = discordClient.discordCache:getDataOr(CacheType.DiscordChannel, channelData.id, function() - local channelStruct = { - discordClient = discordClient, - } - - return Construct(channelStruct, GuildDirectoryChannel.Prototype) - end) - - return (Extend(self, BaseGuildTextChannel.new(discordClient, channelData)) :: unknown) :: GuildDirectoryChannel -end - -export type GuildDirectoryChannel = BaseGuildTextChannel.BaseGuildTextChannel & typeof(GuildDirectoryChannel.Prototype) & { - discordClient: any, -} - -return GuildDirectoryChannel.Interface diff --git a/Package/Classes/Objects/GuildForumChannel.luau b/Package/Classes/Objects/GuildForumChannel.luau deleted file mode 100644 index e193cd7..0000000 --- a/Package/Classes/Objects/GuildForumChannel.luau +++ /dev/null @@ -1,56 +0,0 @@ -local Construct = require("@Utils/Construct") -local Extend = require("@Utils/Extend") - -local BaseDiscordChannel = require("@Objects/BaseDiscordChannel") - -local CacheType = require("@Enums/CacheType") - ---[=[ - @class Objects.GuildForumChannel - - Represents the implementation of a Discord Forum channel. - - @tag inherit Objects.BaseDiscordChannel -]=] - ---[=[ - @prop defaultSortOrder number - @within Objects.GuildForumChannel -]=] - -local GuildForumChannel = {} - -GuildForumChannel.Prototype = {} -GuildForumChannel.Interface = {} - -GuildForumChannel.Prototype.type = "GuildForumChannel" - -function GuildForumChannel.Interface.new( - discordClient, - channelData: { - id: string, - guildId: string, - - [string]: any, - } -) - local self = discordClient.discordCache:getDataOr(CacheType.DiscordChannel, channelData.id, function() - local channelStruct = { - discordClient = discordClient, - } - - return Construct(channelStruct, GuildForumChannel.Prototype) - end) - - self.defaultSortOrder = channelData.defaultSortOrder - - return (Extend(self, BaseDiscordChannel.new(discordClient, channelData)) :: unknown) :: GuildForumChannel -end - -export type GuildForumChannel = BaseDiscordChannel.BaseDiscordChannel & typeof(GuildForumChannel.Prototype) & { - discordClient: any, - - defaultSortOrder: number, -} - -return GuildForumChannel.Interface diff --git a/Package/Classes/Objects/GuildMediaChannel.luau b/Package/Classes/Objects/GuildMediaChannel.luau deleted file mode 100644 index 8f66f8d..0000000 --- a/Package/Classes/Objects/GuildMediaChannel.luau +++ /dev/null @@ -1,56 +0,0 @@ -local Construct = require("@Utils/Construct") -local Extend = require("@Utils/Extend") - -local BaseDiscordChannel = require("@Objects/BaseDiscordChannel") - -local CacheType = require("@Enums/CacheType") - ---[=[ - @class Objects.GuildMediaChannel - - Represents the implementation of a Discord Media channel. - - @tag inherit Objects.BaseDiscordChannel -]=] - ---[=[ - @prop defaultForumLayout number - @within Objects.GuildMediaChannel -]=] - -local GuildMediaChannel = {} - -GuildMediaChannel.Prototype = {} -GuildMediaChannel.Interface = {} - -GuildMediaChannel.Prototype.type = "GuildMediaChannel" - -function GuildMediaChannel.Interface.new( - discordClient, - channelData: { - id: string, - guildId: string, - - [string]: any, - } -) - local self = discordClient.discordCache:getDataOr(CacheType.DiscordChannel, channelData.id, function() - local channelStruct = { - discordClient = discordClient, - } - - return Construct(channelStruct, GuildMediaChannel.Prototype) - end) - - self.defaultForumLayout = channelData.defaultForumLayout - - return (Extend(self, BaseDiscordChannel.new(discordClient, channelData)) :: unknown) :: GuildMediaChannel -end - -export type GuildMediaChannel = BaseDiscordChannel.BaseDiscordChannel & typeof(GuildMediaChannel.Prototype) & { - discordClient: any, - - defaultForumLayout: number, -} - -return GuildMediaChannel.Interface diff --git a/Package/Classes/Objects/GuildOnboarding.luau b/Package/Classes/Objects/GuildOnboarding.luau deleted file mode 100644 index 42e7b6d..0000000 --- a/Package/Classes/Objects/GuildOnboarding.luau +++ /dev/null @@ -1,96 +0,0 @@ -local Construct = require("@Utils/Construct") - -local GuildOnboardingPrompt = require("@Objects/GuildOnboardingPrompt") - -local CacheType = require("@Enums/CacheType") - ---[=[ - @class Objects.GuildOnboarding - - The `GuildOnboarding` class provides a way for developers to interact with a Guild Onboarding. -]=] - ---[=[ - @prop guildId string - @within Objects.GuildOnboarding -]=] - ---[=[ - @prop prompts { Objects.GuildOnboardingPrompt } - @within Objects.GuildOnboarding -]=] - ---[=[ - @prop defaultChannelIds { string } - @within Objects.GuildOnboarding -]=] - ---[=[ - @prop enabled boolean - @within Objects.GuildOnboarding -]=] - ---[=[ - @prop mode number - @within Objects.GuildOnboarding -]=] - -local GuildOnboarding = {} - -GuildOnboarding.Prototype = {} -GuildOnboarding.Interface = {} - ---[=[ - @prop type string - @within Objects.GuildOnboarding - @readonly - - The type of the GuildOnboarding. Default is "GuildOnboarding". -]=] -GuildOnboarding.Prototype.type = "GuildOnboarding" - ---[=[ - Creates a new GuildOnboarding instance. - - @function new - @return GuildOnboarding - @within Objects.GuildOnboarding -]=] -function GuildOnboarding.Interface.new( - discordClient: any, - onboardingData: { - guildId: string, - - [string]: any, - } -) - local self = discordClient.discordCache:getDataOr(CacheType.OnboardingPrompt, onboardingData.guildId, function() - return Construct({ - id = onboardingData.guildId, - - discordClient = discordClient, - }, discordClient.Prototype) - end) - - if onboardingData.prompts then - for index, value in onboardingData.options do - onboardingData.options[index] = GuildOnboardingPrompt.new(discordClient, value) - end - end - - for index, value in onboardingData do - self[index] = value - end - - return self -end - -export type GuildOnboarding = typeof(GuildOnboarding.Prototype) & { - guildId: string, - prompts: { GuildOnboardingPrompt.GuildOnboardingPrompt }, - defaultChannelIds: { string }, - enabled: boolean, - mode: number, -} - -return GuildOnboarding.Interface diff --git a/Package/Classes/Objects/GuildOnboardingPrompt.luau b/Package/Classes/Objects/GuildOnboardingPrompt.luau deleted file mode 100644 index 6df7cbc..0000000 --- a/Package/Classes/Objects/GuildOnboardingPrompt.luau +++ /dev/null @@ -1,108 +0,0 @@ -local Construct = require("@Utils/Construct") - -local GuildOnboardingPromptOption = require("@Objects/GuildOnboardingPromptOption") - -local CacheType = require("@Enums/CacheType") - ---[=[ - @class Objects.GuildOnboardingPrompt - - The `GuildOnboardingPrompt` class provides a way for developers to interact with a guild onboarding prompt. -]=] - ---[=[ - @prop id string - @within Objects.GuildOnboardingPrompt -]=] - ---[=[ - @prop type number - @within Objects.GuildOnboardingPrompt -]=] - ---[=[ - @prop options { Objects.GuildOnboardingPromptOption } - @within Objects.GuildOnboardingPrompt -]=] - ---[=[ - @prop title string - @within Objects.GuildOnboardingPrompt -]=] - ---[=[ - @prop singleSelect boolean - @within Objects.GuildOnboardingPrompt -]=] - ---[=[ - @prop required boolean - @within Objects.GuildOnboardingPrompt -]=] - ---[=[ - @prop inOboarding boolean - @within Objects.GuildOnboardingPrompt -]=] - -local GuildOnboardingPrompt = {} - -GuildOnboardingPrompt.Prototype = {} -GuildOnboardingPrompt.Interface = {} - ---[=[ - @prop type string - @within Objects.GuildOnboardingPrompt - @readonly - - The type of the GuildOnboardingPrompt. Default is "GuildOnboardingPrompt". -]=] -GuildOnboardingPrompt.Prototype.type = "GuildOnboardingPrompt" - ---[=[ - Creates a new GuildOnboardingPrompt instance. - - @function new - @return GuildOnboardingPrompt - @within Objects.GuildOnboardingPrompt -]=] -function GuildOnboardingPrompt.Interface.new( - discordClient: any, - onboardingPromptData: { - id: string, - - [string]: any, - } -) - local self = discordClient.discordCache:getDataOr(CacheType.OnboardingPrompt, onboardingPromptData.id, function() - return Construct({ - id = onboardingPromptData.id, - - discordClient = discordClient, - }, discordClient.Prototype) - end) - - if onboardingPromptData.options then - for index, value in onboardingPromptData.options do - onboardingPromptData.options[index] = GuildOnboardingPromptOption.new(discordClient, value) - end - end - - for index, value in onboardingPromptData do - self[index] = value - end - - return self -end - -export type GuildOnboardingPrompt = typeof(GuildOnboardingPrompt.Prototype) & { - id: string, - type: number, - options: { GuildOnboardingPromptOption.GuildOnboardingPromptOption }, - title: string, - singleSelect: boolean, - required: boolean, - inOboarding: boolean, -} - -return GuildOnboardingPrompt.Interface diff --git a/Package/Classes/Objects/GuildOnboardingPromptOption.luau b/Package/Classes/Objects/GuildOnboardingPromptOption.luau deleted file mode 100644 index e6f953c..0000000 --- a/Package/Classes/Objects/GuildOnboardingPromptOption.luau +++ /dev/null @@ -1,99 +0,0 @@ -local Construct = require("@Utils/Construct") - -local DiscordEmoji = require("@Objects/DiscordEmoji") - -local CacheType = require("@Enums/CacheType") - ---[=[ - @class Objects.GuildOnboardingPromptOption - - The `GuildOnboardingPromptOption` class provides a way for developers to interact with a guild onboarding prompt option. -]=] - ---[=[ - @prop id string - @within Objects.GuildOnboardingPromptOption -]=] - ---[=[ - @prop channelIds { string } - @within Objects.GuildOnboardingPromptOption -]=] - ---[=[ - @prop roleIds { string } - @within Objects.GuildOnboardingPromptOption -]=] - ---[=[ - @prop title string - @within Objects.GuildOnboardingPromptOption -]=] - ---[=[ - @prop description string? - @within Objects.GuildOnboardingPromptOption -]=] - -local GuildOnboardingPromptOption = {} - -GuildOnboardingPromptOption.Prototype = {} -GuildOnboardingPromptOption.Interface = {} - ---[=[ - @prop type string - @within Objects.GuildOnboardingPromptOption - @readonly - - The type of the GuildOnboardingPromptOption. Default is "GuildOnboardingPromptOption". -]=] -GuildOnboardingPromptOption.Prototype.type = "GuildOnboardingPromptOption" - ---[=[ - Creates a new GuildOnboardingPromptOption instance. - - @function new - @return GuildOnboardingPromptOption - @within Objects.GuildOnboardingPromptOption -]=] -function GuildOnboardingPromptOption.Interface.new( - discordClient: any, - onboardingPromptData: { - id: string, - - [string]: any, - } -) - local self = discordClient.discordCache:getDataOr( - CacheType.OnboardingPromptOption, - onboardingPromptData.id, - function() - return Construct({ - id = onboardingPromptData.id, - - discordClient = discordClient, - }, discordClient.Prototype) - end - ) - - if onboardingPromptData.emoji then - onboardingPromptData.emoji = DiscordEmoji.new(discordClient, onboardingPromptData.emoji) - end - - for index, value in onboardingPromptData do - self[index] = value - end - - return self -end - -export type GuildOnboardingPromptOption = typeof(GuildOnboardingPromptOption.Prototype) & { - id: string, - channelIds: { string }, - roleIds: { string }, - emoji: DiscordEmoji.DiscordEmoji, - title: string, - description: string?, -} - -return GuildOnboardingPromptOption.Interface diff --git a/Package/Classes/Objects/GuildPreview.luau b/Package/Classes/Objects/GuildPreview.luau deleted file mode 100644 index ebd8198..0000000 --- a/Package/Classes/Objects/GuildPreview.luau +++ /dev/null @@ -1,122 +0,0 @@ -local Construct = require("@Utils/Construct") - -local DiscordEmoji = require("@Objects/DiscordEmoji") -local DiscordSticker = require("@Objects/DiscordSticker") - -local CacheType = require("@Enums/CacheType") - ---[=[ - @class Objects.GuildPreview - - GuildPreview represents a user on Discord, encapsulating user data such as username, avatar, and various flags. -]=] - ---[=[ - @prop id string - @within Objects.GuildPreview -]=] - ---[=[ - @prop name string - @within Objects.GuildPreview -]=] - ---[=[ - @prop icon string? - @within Objects.GuildPreview -]=] - ---[=[ - @prop splash string? - @within Objects.GuildPreview -]=] - ---[=[ - @prop discoverySplash string? - @within Objects.GuildPreview -]=] - ---[=[ - @prop emojis { Objects.DiscordEmoji } - @within Objects.GuildPreview -]=] - ---[=[ - @prop features { string } - @within Objects.GuildPreview -]=] - ---[=[ - @prop approximateMemberCount number - @within Objects.GuildPreview -]=] - ---[=[ - @prop approximatePresenceCount number - @within Objects.GuildPreview -]=] - ---[=[ - @prop description string? - @within Objects.GuildPreview -]=] - ---[=[ - @prop stickers { Objects.DiscordSticker } - @within Objects.GuildPreview -]=] - -local GuildPreview = {} - -GuildPreview.Prototype = {} -GuildPreview.Interface = {} - -GuildPreview.Prototype.type = "GuildPreview" - ---[=[ - Creates a new instance of GuildPreview. - - @function new - @param discordClient any - @param guildPreview { ... } - @within Objects.GuildPreview - @return GuildPreview -]=] -function GuildPreview.Interface.new( - discordClient: any, - guildPreview: { - id: string, - - [string]: any, - } -) - local self = discordClient.discordCache:getDataOr(CacheType.GuildPreview, guildPreview.id, function() - return Construct({ - id = guildPreview.id, - - discordClient = discordClient, - }, GuildPreview.Prototype) - end) - - for index, value in guildPreview do - self[index] = value - end - - return self -end - -export type GuildPreview = typeof(GuildPreview.Prototype) & { - id: string, - name: string, - icon: string?, - splash: string?, - discoverySplash: string?, - emojis: { DiscordEmoji.DiscordEmoji }, - features: { string }, - approximateMemberCount: number, - approximatePresenceCount: number, - description: string?, - stickers: { DiscordSticker.DiscordSticker }, -} - -return GuildPreview.Interface diff --git a/Package/Classes/Objects/GuildPrivateThreadChannel.luau b/Package/Classes/Objects/GuildPrivateThreadChannel.luau deleted file mode 100644 index ca7e7b1..0000000 --- a/Package/Classes/Objects/GuildPrivateThreadChannel.luau +++ /dev/null @@ -1,44 +0,0 @@ -local Construct = require("@Utils/Construct") -local Extend = require("@Utils/Extend") - -local BaseGuildThread = require("@Objects/BaseGuildThread") - -local CacheType = require("@Enums/CacheType") - ---[=[ - @class Objects.GuildPrivateThreadChannel - - Represents the implementation of a Discord Private Thread channel. - - @tag inherit Objects.BaseGuildThread -]=] -local GuildPrivateThreadChannel = {} - -GuildPrivateThreadChannel.Prototype = {} -GuildPrivateThreadChannel.Interface = {} - -GuildPrivateThreadChannel.Prototype.type = "GuildPrivateThreadChannel" - -function GuildPrivateThreadChannel.Interface.new( - discordClient, - channelData: { - id: string, - guildId: string, - } -) - local self = discordClient.discordCache:getDataOr(CacheType.DiscordChannel, channelData.id, function() - local channelStruct = { - discordClient = discordClient, - } - - return Construct(channelStruct, GuildPrivateThreadChannel.Prototype) - end) - - return (Extend(self, BaseGuildThread.new(discordClient, channelData)) :: unknown) :: GuildPrivateThreadChannel -end - -export type GuildPrivateThreadChannel = BaseGuildThread.BaseGuildThread & typeof(GuildPrivateThreadChannel.Prototype) & { - discordClient: any, -} - -return GuildPrivateThreadChannel.Interface diff --git a/Package/Classes/Objects/GuildPublicThreadChannel.luau b/Package/Classes/Objects/GuildPublicThreadChannel.luau deleted file mode 100644 index f414750..0000000 --- a/Package/Classes/Objects/GuildPublicThreadChannel.luau +++ /dev/null @@ -1,44 +0,0 @@ -local Construct = require("@Utils/Construct") -local Extend = require("@Utils/Extend") - -local BaseGuildThread = require("@Objects/BaseGuildThread") - -local CacheType = require("@Enums/CacheType") - ---[=[ - @class Objects.GuildPublicThreadChannel - - Represents the implementation of a Discord Public Thread channel. - - @tag inherit Objects.BaseGuildThread -]=] -local GuildPublicThreadChannel = {} - -GuildPublicThreadChannel.Prototype = {} -GuildPublicThreadChannel.Interface = {} - -GuildPublicThreadChannel.Prototype.type = "GuildPublicThreadChannel" - -function GuildPublicThreadChannel.Interface.new( - discordClient, - channelData: { - id: string, - guildId: string, - } -) - local self = discordClient.discordCache:getDataOr(CacheType.DiscordChannel, channelData.id, function() - local channelStruct = { - discordClient = discordClient, - } - - return Construct(channelStruct, GuildPublicThreadChannel.Prototype) - end) - - return (Extend(self, BaseGuildThread.new(discordClient, channelData)) :: unknown) :: GuildPublicThreadChannel -end - -export type GuildPublicThreadChannel = BaseGuildThread.BaseGuildThread & typeof(GuildPublicThreadChannel.Prototype) & { - discordClient: any, -} - -return GuildPublicThreadChannel.Interface diff --git a/Package/Classes/Objects/GuildStageVoiceChannel.luau b/Package/Classes/Objects/GuildStageVoiceChannel.luau deleted file mode 100644 index 40fd240..0000000 --- a/Package/Classes/Objects/GuildStageVoiceChannel.luau +++ /dev/null @@ -1,47 +0,0 @@ -local Construct = require("@Utils/Construct") -local Extend = require("@Utils/Extend") - -local BaseGuildVoiceChannel = require("@Objects/BaseGuildVoiceChannel") - -local CacheType = require("@Enums/CacheType") - ---[=[ - @class Objects.GuildStageVoiceChannel - - Represents the implementation of a Discord Stage channel. - - @tag inherit Objects.BaseGuildVoiceChannel -]=] -local GuildStageVoiceChannel = {} - -GuildStageVoiceChannel.Prototype = {} -GuildStageVoiceChannel.Interface = {} - -GuildStageVoiceChannel.Prototype.type = "GuildStageVoiceChannel" - -function GuildStageVoiceChannel.Interface.new( - discordClient, - channelData: { - id: string, - guildId: string, - } -) - local self = discordClient.discordCache:getDataOr(CacheType.DiscordChannel, channelData.id, function() - local channelStruct = { - discordClient = discordClient, - } - - return Construct(channelStruct, GuildStageVoiceChannel.Prototype) - end) - - return (Extend(self, BaseGuildVoiceChannel.new(discordClient, channelData)) :: unknown) :: GuildStageVoiceChannel -end - -export type GuildStageVoiceChannel = - BaseGuildVoiceChannel.BaseGuildVoiceChannel - & typeof(GuildStageVoiceChannel.Prototype) - & { - discordClient: any, - } - -return GuildStageVoiceChannel.Interface diff --git a/Package/Classes/Objects/GuildTextChannel.luau b/Package/Classes/Objects/GuildTextChannel.luau deleted file mode 100644 index 034e56b..0000000 --- a/Package/Classes/Objects/GuildTextChannel.luau +++ /dev/null @@ -1,44 +0,0 @@ -local Construct = require("@Utils/Construct") -local Extend = require("@Utils/Extend") - -local BaseGuildTextChannel = require("@Objects/BaseGuildTextChannel") - -local CacheType = require("@Enums/CacheType") - ---[=[ - @class Objects.GuildTextChannel - - Represents the implementation of a Discord Text channel. - - @tag inherit Objects.BaseGuildTextChannel -]=] -local GuildTextChannel = {} - -GuildTextChannel.Prototype = {} -GuildTextChannel.Interface = {} - -GuildTextChannel.Prototype.type = "GuildTextChannel" - -function GuildTextChannel.Interface.new( - discordClient, - channelData: { - id: string, - guildId: string, - } -) - local self = discordClient.discordCache:getDataOr(CacheType.DiscordChannel, channelData.id, function() - local channelStruct = { - discordClient = discordClient, - } - - return Construct(channelStruct, GuildTextChannel.Prototype) - end) - - return (Extend(self, BaseGuildTextChannel.new(discordClient, channelData)) :: unknown) :: GuildTextChannel -end - -export type GuildTextChannel = BaseGuildTextChannel.BaseGuildTextChannel & typeof(GuildTextChannel.Prototype) & { - discordClient: any, -} - -return GuildTextChannel.Interface diff --git a/Package/Classes/Objects/GuildVoiceChannel.luau b/Package/Classes/Objects/GuildVoiceChannel.luau deleted file mode 100644 index 41a78d5..0000000 --- a/Package/Classes/Objects/GuildVoiceChannel.luau +++ /dev/null @@ -1,44 +0,0 @@ -local Construct = require("@Utils/Construct") -local Extend = require("@Utils/Extend") - -local BaseGuildVoiceChannel = require("@Objects/BaseGuildVoiceChannel") - -local CacheType = require("@Enums/CacheType") - ---[=[ - @class Objects.GuildVoiceChannel - - Represents the implementation of a Discord Voice channel. - - @tag inherit Objects.BaseGuildVoiceChannel -]=] -local GuildVoiceChannel = {} - -GuildVoiceChannel.Prototype = {} -GuildVoiceChannel.Interface = {} - -GuildVoiceChannel.Prototype.type = "GuildVoiceChannel" - -function GuildVoiceChannel.Interface.new( - discordClient, - channelData: { - id: string, - guildId: string, - } -) - local self = discordClient.discordCache:getDataOr(CacheType.DiscordChannel, channelData.id, function() - local channelStruct = { - discordClient = discordClient, - } - - return Construct(channelStruct, GuildVoiceChannel.Prototype) - end) - - return (Extend(self, BaseGuildVoiceChannel.new(discordClient, channelData)) :: unknown) :: GuildVoiceChannel -end - -export type GuildVoiceChannel = BaseGuildVoiceChannel.BaseGuildVoiceChannel & typeof(GuildVoiceChannel.Prototype) & { - discordClient: any, -} - -return GuildVoiceChannel.Interface diff --git a/Package/Classes/Objects/GuildWelcomeScreen.luau b/Package/Classes/Objects/GuildWelcomeScreen.luau deleted file mode 100644 index c299966..0000000 --- a/Package/Classes/Objects/GuildWelcomeScreen.luau +++ /dev/null @@ -1,78 +0,0 @@ -local Construct = require("@Utils/Construct") - -local DiscordChannel = require("@Objects/DiscordChannel") - -local CacheType = require("@Enums/CacheType") - ---[=[ - @class Objects.GuildWelcomeScreen - - The `GuildWelcomeScreen` class provides a way for developers to interact with a Welcome Screen. -]=] - ---[=[ - @prop description string? - @within Objects.GuildWelcomeScreen -]=] - ---[=[ - @prop welcomeChannels { Objects.DiscordChannel } - @within Objects.GuildWelcomeScreen -]=] - -local GuildWelcomeScreen = {} - -GuildWelcomeScreen.Prototype = {} -GuildWelcomeScreen.Interface = {} - ---[=[ - @prop type string - @within Objects.GuildWelcomeScreen - @readonly - - The type of the GuildWelcomeScreen. Default is "GuildWelcomeScreen". -]=] -GuildWelcomeScreen.Prototype.type = "GuildWelcomeScreen" - ---[=[ - Creates a new GuildWelcomeScreen instance. - - @function new - @return GuildWelcomeScreen - @within Objects.GuildWelcomeScreen -]=] -function GuildWelcomeScreen.Interface.new( - discordClient: any, - welcomeScreenData: { - id: string, - - [string]: any, - } -) - local self = discordClient.discordCache:getDataOr(CacheType.WelcomeScreen, welcomeScreenData.id, function() - return Construct({ - id = welcomeScreenData.id, - - discordClient = discordClient, - }, discordClient.Prototype) - end) - - if welcomeScreenData.welcomeChannels then - for index, value in welcomeScreenData.channels do - welcomeScreenData.welcomeChannels[index] = DiscordChannel.from(discordClient, value) - end - end - - for index, value in welcomeScreenData do - self[index] = value - end - - return self -end - -export type GuildWelcomeScreen = typeof(GuildWelcomeScreen.Prototype) & { - description: string?, - welcomeChannels: { DiscordChannel.DiscordChannel }, -} - -return GuildWelcomeScreen.Interface diff --git a/Package/Classes/Objects/GuildWidget.luau b/Package/Classes/Objects/GuildWidget.luau deleted file mode 100644 index a11ab50..0000000 --- a/Package/Classes/Objects/GuildWidget.luau +++ /dev/null @@ -1,111 +0,0 @@ -local Construct = require("@Utils/Construct") - -local DiscordChannel = require("@Objects/DiscordChannel") -local DiscordUser = require("@Objects/DiscordUser") - -local CacheType = require("@Enums/CacheType") - ---[=[ - @class Objects.GuildWidget - - The `GuildWidget` class provides a way for developers to interact with a Widget. -]=] - ---[=[ - @prop id string - @within Objects.GuildWidget -]=] - ---[=[ - @prop name string - @within Objects.GuildWidget -]=] - ---[=[ - @prop instantInvite string? - @within Objects.GuildWidget -]=] - ---[=[ - @prop channels { Objects.DiscordChannel } - @within Objects.GuildWidget -]=] - ---[=[ - @prop members { Objects.DiscordUser } - @within Objects.GuildWidget -]=] - ---[=[ - @prop presenceCount number - @within Objects.GuildWidget -]=] - -local GuildWidget = {} - -GuildWidget.Prototype = {} -GuildWidget.Interface = {} - ---[=[ - @prop type string - @within Objects.GuildWidget - @readonly - - The type of the GuildWidget. Default is "GuildWidget". -]=] -GuildWidget.Prototype.type = "GuildWidget" - ---[=[ - Creates a new GuildWidget instance. - - @function new - @return GuildWidget - @within Objects.GuildWidget -]=] -function GuildWidget.Interface.new( - discordClient: any, - widgetInformation: { - id: string, - - [string]: any, - } -) - local self = discordClient.discordCache:getDataOr(CacheType.GuildWidget, widgetInformation.id, function() - return Construct({ - id = widgetInformation.id, - - discordClient = discordClient, - }, discordClient.Prototype) - end) - - if widgetInformation.channels then - for index, value in widgetInformation.channels do - widgetInformation.channels[index] = DiscordChannel.from(discordClient, value) - end - end - - if widgetInformation.members then - for index, value in widgetInformation.members do - widgetInformation.channels[index] = DiscordUser.new(discordClient, value) - end - end - - for index, value in widgetInformation do - self[index] = value - end - - return self -end - -export type GuildWidget = typeof(GuildWidget.Prototype) & { - discordClient: any, - - id: string, - name: string, - instantInvite: string?, - channels: { DiscordChannel.DiscordChannel }, - members: { DiscordUser.DiscordUser }, - presenceCount: number, -} - -return GuildWidget.Interface diff --git a/Package/Classes/Objects/UserDMChannel.luau b/Package/Classes/Objects/UserDMChannel.luau deleted file mode 100644 index 43c2964..0000000 --- a/Package/Classes/Objects/UserDMChannel.luau +++ /dev/null @@ -1,43 +0,0 @@ -local Construct = require("@Utils/Construct") -local Extend = require("@Utils/Extend") - -local BaseUserChannel = require("@Objects/BaseUserChannel") - -local CacheType = require("@Enums/CacheType") - ---[=[ - @class Objects.UserDMChannel - - Represents the implementation of a Discord Direct Message Channel - - @tag inherit Objects.BaseUserChannel -]=] -local UserDMChannel = {} - -UserDMChannel.Prototype = {} -UserDMChannel.Interface = {} - -UserDMChannel.Prototype.type = "UserDMChannel" - -function UserDMChannel.Interface.new( - discordClient, - channelData: { - id: string, - } -) - local self = discordClient.discordCache:getDataOr(CacheType.DiscordChannel, channelData.id, function() - local channelStruct = { - discordClient = discordClient, - } - - return Construct(channelStruct, UserDMChannel.Prototype) - end) - - return (Extend(self, BaseUserChannel.new(discordClient, channelData)) :: unknown) :: UserDMChannel -end - -export type UserDMChannel = BaseUserChannel.BaseUserChannel & typeof(UserDMChannel.Prototype) & { - discordClient: any, -} - -return UserDMChannel.Interface diff --git a/Package/Classes/Objects/UserGroupChannel.luau b/Package/Classes/Objects/UserGroupChannel.luau deleted file mode 100644 index 75bc81f..0000000 --- a/Package/Classes/Objects/UserGroupChannel.luau +++ /dev/null @@ -1,92 +0,0 @@ -local Future = require("@Vendor/Future") - -local Construct = require("@Utils/Construct") -local Extend = require("@Utils/Extend") - -local ChannelBuilder = require("@Builders/ChannelBuilder") - -local BaseUserChannel = require("@Objects/BaseUserChannel") - -local DiscordEndpoints = require("@Data/DiscordEndpoints") - -local CacheType = require("@Enums/CacheType") - ---[=[ - @class Objects.UserGroupChannel - - Represents the implementation of a Discord Group Channel - - @tag inherit Objects.BaseUserChannel -]=] - ---[=[ - @prop icon string - @within Objects.UserGroupChannel -]=] - ---[=[ - @prop ownerId string - @within Objects.UserGroupChannel -]=] - ---[=[ - @prop applicationId string - @within Objects.UserGroupChannel -]=] - ---[=[ - @prop managed boolean - @within Objects.UserGroupChannel -]=] - -local UserGroupChannel = {} - -UserGroupChannel.Prototype = {} -UserGroupChannel.Interface = {} - -UserGroupChannel.Prototype.type = "UserGroupChannel" - ---[=[ - Modifies the thread channel settings asynchronously. - - @method modifyAsync - @param channelBuilder ChannelBuilder.ChannelBuilder - @within Objects.UserGroupChannel - @return Vendor.Future -]=] -function UserGroupChannel.Prototype.modifyAsync(self: UserGroupChannel, channelBuilder: ChannelBuilder.ChannelBuilder) - return Future.try(function() - return self.discordClient.discordGateway - :patchAsync(string.format(DiscordEndpoints.BotModifyChannel, self.id), channelBuilder:toPayloadObject()) - :await() - end) -end - -function UserGroupChannel.Interface.new( - discordClient, - channelData: { - id: string, - } -) - local self = discordClient.discordCache:getDataOr(CacheType.DiscordChannel, channelData.id, function() - local channelStruct = { - discordClient = discordClient, - } - - return Construct(channelStruct, UserGroupChannel.Prototype) - end) - - return (Extend(self, BaseUserChannel.new(discordClient, channelData)) :: unknown) :: UserGroupChannel -end - -export type UserGroupChannel = BaseUserChannel.BaseUserChannel & typeof(UserGroupChannel.Prototype) & { - discordClient: any, - - icon: string, - ownerId: string, - applicationId: string, - - managed: boolean, -} - -return UserGroupChannel.Interface diff --git a/Package/Data/DiscordEndpoints.luau b/Package/Data/DiscordEndpoints.luau deleted file mode 100644 index e3a103b..0000000 --- a/Package/Data/DiscordEndpoints.luau +++ /dev/null @@ -1,122 +0,0 @@ ---[[ - Maps a key to a API path, allowing us to map out the Discord REST API - - These API Paths adhere sto the luau string formatting, where '%s' is replaced with a different value. -]] - -return { - BotGateway = "gateway/bot", - - BotGetGuild = "guilds/%s", - BotCreateGuild = "guilds", - BotGetGuildPreview = "guilds/%s/preview", - BotCreateGuildChannel = "guilds/%s/channels", - BotGetAllActiveGuildThreads = "guilds/%s/threads/active", - BotAddGuildMember = "guilds/%s/members/%s", - BotModifyGuildRolePositions = "guilds/%s/roles", - BotDeleteGuildRole = "guilds/%s/roles/%s", - BotModifyGuildRole = "guilds/%s/roles/%s", - BotGetGuildBans = "guilds/%s/bans?before=%s&after=%s&limit=%s", - BotBulkGuildBan = "guilds/%s/bulk-ban", - BotCreateGuildRole = "guilds/%s/roles", - BotModifyGuildMFALevel = "guilds/%s/mfa", - BotBeginGuildPrune = "guilds/%s/prune", - BotGetGuildVoiceRegions = "guilds/%s/regions", - BotGetGuildInvites = "guilds/%s/invites", - BotGetGuildIntegrations = "guilds/%s/integrations", - BotGetGuildWidgetSettings = "guilds/%s/widget", - BotModifyGuildOnboarding = "guilds/%s/onboarding", - BotModifyGuildWelcomeScreen = "guilds/%s/welcome-screen", - BotGetWidgetImage = "guilds/%s/widget.png?style=%s", - BotGetGuildWelcomeScreen = "guilds/%s/welcome-screen", - BotGetGuildWidget = "guilds/%s/widget.json", - BotGetGuildVanityURL = "guilds/%s/vanity-url", - BotGetGuildOnboarding = "guilds/%s/onboarding", - BotModifyGuildWidgetSettings = "guilds/%s/widget", - BotGetGuildPruneCount = "guilds/%s/prune?days=%s&include_roles=%s", - BotGetGuildBan = "guilds/%s/bans/%s", - BotGetGuildRoles = "guilds/%s/roles", - - GetGuildAuditLogs = "guilds/%s/audit-logs", - BotModifyGuild = "guilds/%s", - BotDeleteGuild = "guilds/%s", - BotGetGuildChannels = "guilds/%s/channels", - BotGetGuildMember = "guilds/%s/members/%s", - BotModifyCurrentMemberGuild = "guilds/%s/members/@me", - BotGetGuildMembers = "guilds/%s/members?limit=%s&after=%s", - BotBanGuildMember = "guilds/%s/members/%s", - BotRemoveGuildMember = "guilds/%s/members/%s", - BotRemoveGuildMemberBan = "guilds/%s/bans/%s", - BotAddGuildMemberRole = "guilds/%s/members/%s/roles/%s", - BotRemoveGuildMemberRole = "guilds/%s/members/%s/roles/%s", - BotModifyGuildMember = "guilds/%s/members/%s", - BotSearchGuildMembers = "guilds/%s/members/search?query=%s&limit=%s", - BotDeleteGuildIntegration = "guilds/%s/integrations/%s", - - BotListAutomoderationRules = "guilds/%s/auto-moderation/rules", - BotGetAutomoderationRule = "guilds/%s/auto-moderation/rules/%s", - BotCreateAutomoderationRule = "guilds/%s/auto-moderation/rules", - BotModifyAutomoderationRule = "guilds/%s/auto-moderation/rules/%s", - BotDeleteAutomoderationRule = "guilds/%s/auto-moderation/rules/%s", - - BotGetChannel = "channels/%s", - BotModifyGuildChannelPosition = "channels/%s/channels", - BotCreateMessage = "channels/%s/messages", - BotGetChannelMessages = "channels/%s/messages", - BotGetChannelMessage = "channels/%s/messages/%s", - BotDeleteMessage = "channels/%s/messages/%s", - BotEditMessage = "channels/%s/messages/%s", - BotCrosspostMessage = "channels/%s/messages/%s/crosspost", - BotCreateThreadFromMessage = "channels/%s/messages/%s/threads", - BotCreateThread = "channels/%s/threads", - BotCreateForumOrMediaThread = "channels/%s/threads", - - BotGetInvites = "channels/%s/invites", - - BotCreateReaction = "channels/%s/messages/%s/reactions/%s/@me", - BotDeleteReaction = "channels/%s/messages/%s/reactions/%s/@me", - BotDeleteUserReaction = "channels/%s/messages/%s/reactions/%s/%s", - BotGetReactions = "channels/%s/messages/%s/reactions/%s", - BotDeleteAllReactions = "channels/%s/messages/%s/reactions", - BotDeleteAllReactionsForEmoji = "channels/%s/messages/%s/reactions/%s", - - BotDeleteChannel = "channels/%s", - BotModifyChannel = "channels/%s", - BotPinMessage = "channels/%s/pins/%s", - BotUnpinMessage = "channels/%s/pins/%s", - BotTriggerTypingChannel = "channels/%s/typing", - BotEditChannelPermissions = "channels/%s/permissions/%s", - BotDeleteChannelPermission = "channels/%s/permissions/%s", - BotGetPinnedMessages = "channels/%s/pins", - BotFollowAnnouncementChannel = "channels/%s/followers", - - BotGetThreadMembers = "channels/%s/thread-members", - BotGetThreadMember = "channels/%s/thread-members/%s", - BotJoinThreadChannel = "channels/%s/thread-members/@me", - BotAddMemberToThreadChannel = "channels/%s/thread-members/%s", - BotLeaveThreadChannel = "channels/%s/thread-members/@me", - BotRemoveMemberFromThreadChannel = "channels/%s/thread-members/%s", - BotListPublicArchivedThreads = "/channels/%s/threads/archived/public?before=%s&limit=%s", - BotListPrivateArchivedThreads = "/channels/%s/threads/archived/private?before=%s&limit=%s", - BotListJoinedPrivateArchivedThreads = "/channels/%s/users/@me/threads/archived/private?before=%s&limit=%s", - - BotBulkDeleteMessages = "channels/%s/messages/bulk-delete", - - CreateGlobalApplicationCommand = "applications/%s/commands", - EditGlobalApplicationCommand = "applications/%s/commands/%s", - OverwriteGlobalApplicationCommand = "applications/%s/commands", - GetGlobalApplicationCommands = "applications/%s/commands", - GetGlobalApplicationCommand = "applications/%s/commands/%s", - DeleteGlobalApplicationCommand = "applications/%s/commands/%s", - - CreateGuildApplicationCommand = "applications/%s/guilds/%s/commands", - EditGuildApplicationCommand = "applications/%s/guilds/%s/commands/%s", - GetGuildApplicationCommands = "applications/%s/guilds/%s/commands", - OverwriteGuildApplicationCommands = "applications/%s/guilds/%s/commands", - GetGuildApplicationCommand = "applications/%s/guilds/%s/commands/%s", - DeleteGuildApplicationCommand = "applications/%s/guilds/%s/commands/%s", - - BotDeleteInvite = "invites/%s", - CreateInteractionResponse = "interactions/%s/%s/callback", - EditOriginalInteractionResponse = "webhooks/%s/%s/messages/@original", -} diff --git a/Package/Enums/CacheType.luau b/Package/Enums/CacheType.luau deleted file mode 100644 index c78ad25..0000000 --- a/Package/Enums/CacheType.luau +++ /dev/null @@ -1,43 +0,0 @@ --- Internal cache keys, cache registers key in the following format `{CacheType}-{Id}`, though this is not strict, --- so some cache's may differ from this format. - -local CacheType = { - Application = "Application", - GuildWidget = "GuildWidget", - GuildPreview = "GuildPreview", - DiscordEmoji = "DiscordEmoji", - DiscordMember = "DiscordMember", - DiscordSticker = "DiscordSticker", - WelcomeScreen = "WelcomeScreen", - OnboardingPromptOption = "OnboardingPromptOption", - OnboardingPrompt = "OnboardingPrompt", - DiscordGuild = "DiscordGuild", - DiscordIntegration = "DiscordIntegration", - DiscordInvite = "DiscordInvite", - DiscordGuildRole = "DiscordGuildRole", - AutoModeration = "AutoModeration", - DiscordUser = "DiscordUser", - DiscordChannel = "DiscordChannel", - DiscordMessage = "DiscordMessage", -} - -export type CacheType = - "Application" - | "GuildPreview" - | "DiscordMember" - | "WelcomeScreen" - | "DiscordEmoji" - | "GuildWidget" - | "DiscordSticker" - | "OnboardingPromptOption" - | "OnboardingPrompt" - | "AutoModeration" - | "DiscordIntegration" - | "DiscordGuild" - | "DiscordInvite" - | "DiscordGuildRole" - | "DiscordUser" - | "DiscordChannel" - | "DiscordMessage" - -return CacheType diff --git a/Package/Enums/ChannelType.luau b/Package/Enums/ChannelType.luau deleted file mode 100644 index c0f23f8..0000000 --- a/Package/Enums/ChannelType.luau +++ /dev/null @@ -1,21 +0,0 @@ --- https://discord.com/developers/docs/resources/channel#channel-object-channel-types - -local ChannelType = { - GuildTextChannel = 0, - UserDMChannel = 1, - GuildVoiceChannel = 2, - UserGroupChannel = 3, - GuildCategory = 4, - GuildAnnouncement = 5, - GuildAnnouncementThread = 10, - GuildPublicThread = 11, - GuildPrivateThread = 12, - GuildStageVoiceChannel = 13, - GuildDirectory = 14, - GuildForumChannel = 15, - GuildMediaChannel = 16, -} - -export type ChannelType = number - -return ChannelType diff --git a/Package/Enums/DiscordEvents.luau b/Package/Enums/DiscordEvents.luau deleted file mode 100644 index eed3f7e..0000000 --- a/Package/Enums/DiscordEvents.luau +++ /dev/null @@ -1,54 +0,0 @@ --- Events that the library will fire from the DiscordClient instance. - -local DiscordEvents = { - Ready = "Ready", - - Message = "Message", - MessageChanged = "MessageChanged", - MessageDeleted = "MessageDeleted", - MessageBulkDeleted = "MessageDeleteBulk", - - ChannelCreate = "ChannelCreate", - ChannelUpdate = "ChannelUpdate", - ChannelDelete = "ChannelDelete", - - UserUpdated = "UserUpdate", - - ChannelPinsUpdate = "ChannelPinsUpdate", - - GuildCreate = "GuildCreate", - GuildUpdate = "GuildUpdate", - GuildDelete = "GuildDelete", - - GuildMemberBanned = "GuildBanAdded", - GuildMemberUnbanned = "GuildBanRemoved", - - GuildMemberJoined = "GuildMemberJoined", - GuildMemberLeft = "GuildMemberRemove", - GuildMemberUpdated = "GuildMemberUpdate", - - Interaction = "Interaction", -} - -export type DiscordEvents = - "Ready" - | "Message" - | "MessageChanged" - | "MessageDeleted" - | "MessageDeleteBulk" - | "ChannelCreate" - | "ChannelUpdate" - | "ChannelDelete" - | "UserUpdate" - | "ChannelPinsUpdate" - | "GuildCreate" - | "GuildUpdate" - | "GuildDelete" - | "GuildBanAdded" - | "GuildBanRemoved" - | "GuildMemberJoined" - | "GuildMemberRemove" - | "GuildMemberUpdate" - | "Interaction" - -return DiscordEvents diff --git a/Package/Enums/Intents.luau b/Package/Enums/Intents.luau deleted file mode 100644 index bd0df96..0000000 --- a/Package/Enums/Intents.luau +++ /dev/null @@ -1,46 +0,0 @@ --- https://discord.com/developers/docs/topics/opcodes-and-status-codes#gateway-gateway-opcodes - -local Intents = { - GuildMembers = "GuildMembers", - GuildModeration = "GuildModeration", - GuildEmojisAndStickers = "GuildEmojisAndStickers", - GuildIntegrations = "GuildIntegrations", - GuildWebhooks = "GuildWebhooks", - GuildInvites = "GuildInvites", - GuildVoiceState = "GuildVoiceState", - GuildPresences = "GuildPresences", - GuildMessage = "GuildMessage", - GuildMessageReactions = "GuildMessageReactions", - GuildMessageTyping = "GuildMessageTyping", - - DirectMessage = "DirectMessage", - DirectMessageReactions = "DirectMessageReactions", - DirectMessageTyping = "DirectMessageTyping", - - GuildMessageContent = "GuildMessageContent", - GuildScheduledEvents = "GuildScheduledEvents", - GuildModerationConfiguration = "GuildModerationConfiguration", - GuildModerationExecution = "GuildModerationExecution", -} - -export type Intents = - "GuildMembers" - | "GuildModeration" - | "GuildEmojisAndStickers" - | "GuildIntegrations" - | "GuildWebhooks" - | "GuildInvites" - | "GuildVoiceState" - | "GuildPresences" - | "GuildMessage" - | "GuildMessageReactions" - | "GuildMessageTyping" - | "DirectMessage" - | "DirectMessageReactions" - | "DirectMessageTyping" - | "GuildMessageContent" - | "GuildScheduledEvents" - | "GuildModerationConfiguration" - | "GuildModerationExecution" - -return Intents diff --git a/Package/Enums/ResolvableType.luau b/Package/Enums/ResolvableType.luau deleted file mode 100644 index f46120a..0000000 --- a/Package/Enums/ResolvableType.luau +++ /dev/null @@ -1,10 +0,0 @@ --- Types of Resolveables, only used internally to convey the data - -local ResolvableType = { - JSON = "JSON", - FORMDATA = "FORMDATA", -} - -export type ResolvableType = "JSON" | "FORMDATA" - -return ResolvableType diff --git a/Package/Enums/VoiceWebsocketCloseCodes.luau b/Package/Enums/VoiceWebsocketCloseCodes.luau deleted file mode 100644 index 327d1d9..0000000 --- a/Package/Enums/VoiceWebsocketCloseCodes.luau +++ /dev/null @@ -1,21 +0,0 @@ --- https://discord.com/developers/docs/topics/opcodes-and-status-codes#voice-voice-close-event-codes - -local VoiceWebsocketCloseCodes = { - UnknownError = 4000, - UnknownOperation = 4001, - DecodeError = 4002, - NotAuthenticated = 4003, - AuthenticationFailed = 4004, - AlreadyAuthenticated = 4005, - SessionInvalid = 4006, - SessionTimedOut = 4009, - ServerNotFound = 4011, - UnknownProtocol = 4012, - Disconnected = 4014, - VoiceServerCrashed = 4015, - UnknownEncryptionMode = 4016, -} - -export type VoiceWebsocketCloseCodes = number - -return VoiceWebsocketCloseCodes diff --git a/Package/Enums/VoiceWebsocketOperationCodes.luau b/Package/Enums/VoiceWebsocketOperationCodes.luau deleted file mode 100644 index 300c48d..0000000 --- a/Package/Enums/VoiceWebsocketOperationCodes.luau +++ /dev/null @@ -1,19 +0,0 @@ --- https://discord.com/developers/docs/topics/opcodes-and-status-codes#voice-voice-opcodes - -local VoiceWebsocketOperationCodes = { - Identify = 0, - SelectProtocol = 1, - Ready = 2, - Heartbeat = 3, - SessionDescription = 4, - Speaking = 5, - HeartbeatACK = 6, - Resume = 7, - Hello = 8, - Resumed = 9, - ClientDisconnect = 13, -} - -export type VoiceWebsocketOperationCodes = number - -return VoiceWebsocketOperationCodes diff --git a/Package/Enums/WebsocketCloseCodes.luau b/Package/Enums/WebsocketCloseCodes.luau deleted file mode 100644 index 5d5b629..0000000 --- a/Package/Enums/WebsocketCloseCodes.luau +++ /dev/null @@ -1,24 +0,0 @@ --- https://discord.com/developers/docs/topics/opcodes-and-status-codes#gateway-gateway-close-event-codes - -local WebsocketCloseCodes = { - UnknownError = 4000, - UnknownOperation = 4001, - DecodeError = 4002, - NotAuthenticated = 4003, - AuthenticationFailed = 4004, - AlreadyAuthenticated = 4005, - InvalidSequence = 4007, - RateLimited = 4008, - SessionTimedOut = 4009, - InvalidShard = 4010, - ShardingRequired = 4011, - InvalidAPIVersion = 4012, - InvalidIntents = 4013, - DisallowedIntents = 4014, - - VoiceServerCrashed = 4015, -} - -export type WebsocketCloseCodes = number - -return WebsocketCloseCodes diff --git a/Package/Enums/WebsocketEvents.luau b/Package/Enums/WebsocketEvents.luau deleted file mode 100644 index 54ea466..0000000 --- a/Package/Enums/WebsocketEvents.luau +++ /dev/null @@ -1,59 +0,0 @@ --- https://discord.com/developers/docs/topics/gateway-events#receive-events - -local WebsocketEvents = { - Ready = "READY", - - MessageCreate = "MESSAGE_CREATE", - MessageUpdate = "MESSAGE_UPDATE", - MessageDeleted = "MESSAGE_DELETE", - MessageBulkDeleted = "MESSAGE_DELETE_BULK", - - ChannelCreate = "CHANNEL_CREATE", - ChannelUpdate = "CHANNEL_UPDATE", - ChannelDeleted = "CHANNEL_DELETE", - - UserUpdated = "USER_UPDATE", - - ChannelPinsUpdate = "CHANNEL_PINS_UPDATE", - - GuildCreate = "GUILD_CREATE", - GuildUpdate = "GUILD_UPDATE", - GuildDeleted = "GUILD_DELETE", - - GuildMemberBanned = "GUILD_BAN_ADDED", - GuildMemberUnbanned = "GUILD_BAN_REMOVE", - - GuildMemberJoined = "GUILD_MEMBER_JOINED", - GuildMemberLeft = "GUILD_MEMBER_LEFT", - GuildMemberUpdated = "GUILD_MEMBER_UPDATE", - - InteractionCreate = "INTERACTION_CREATE", - - VoiceServerUpdate = "VOICE_SERVER_UPDATE", - VoiceStateUpdate = "VOICE_STATE_UPDATE", -} - -export type WebsocketEvents = - "READY" - | "MESSAGE_CREATE" - | "MESSAGE_UPDATE" - | "MESSAGE_DELETE" - | "MESSAGE_DELETE_BULK" - | "CHANNEL_CREATE" - | "CHANNEL_UPDATE" - | "CHANNEL_DELETE" - | "USER_UPDATE" - | "CHANNEL_PINS_UPDATE" - | "GUILD_CREATE" - | "GUILD_UPDATE" - | "GUILD_DELETE" - | "GUILD_BAN_ADDED" - | "GUILD_BAN_REMOVE" - | "GUILD_MEMBER_JOINED" - | "GUILD_MEMBER_LEFT" - | "GUILD_MEMBER_UPDATE" - | "INTERACTION_CREATE" - | "VOICE_SERVER_UPDATE" - | "VOICE_STATE_UPDATE" - -return WebsocketEvents diff --git a/Package/Enums/WebsocketOperationCodes.luau b/Package/Enums/WebsocketOperationCodes.luau deleted file mode 100644 index a7747e2..0000000 --- a/Package/Enums/WebsocketOperationCodes.luau +++ /dev/null @@ -1,19 +0,0 @@ --- https://discord.com/developers/docs/topics/opcodes-and-status-codes#gateway-gateway-opcodes - -local WebsocketOperationCodes = { - Dispatch = 0, - Heartbeat = 1, - Identify = 2, - PresenceUpdate = 3, - VoiceStateUpdate = 4, - Resume = 6, - Reconnect = 7, - RequestGuildMembers = 8, - InvalidSession = 9, - Hello = 10, - HeartbeatACK = 11, -} - -export type WebsocketOperationCodes = number - -return WebsocketOperationCodes diff --git a/Package/Enums/WebsocketOperationKeys.luau b/Package/Enums/WebsocketOperationKeys.luau deleted file mode 100644 index 52d9cca..0000000 --- a/Package/Enums/WebsocketOperationKeys.luau +++ /dev/null @@ -1,12 +0,0 @@ --- https://discord.com/developers/docs/topics/gateway#gateway-events - -local WebsocketOperationKeys = { - OperationCode = "op", - Data = "d", - Sequence = "s", - EventName = "t", -} - -export type WebsocketOperationKeys = "op" | "d" | "s" | "t" - -return WebsocketOperationKeys diff --git a/Package/Std/Datetime.luau b/Package/Std/Datetime.luau deleted file mode 100644 index 062f9f4..0000000 --- a/Package/Std/Datetime.luau +++ /dev/null @@ -1,8 +0,0 @@ ---[[ - Imports a Standard runtime library, in this case we're using Lune - but if we wanted to hop into another Runtime.. - theoretically, we could - we'd just need to emulate the lune runtime. -]] - -return setmetatable({}, { - __index = require("@lune/datetime"), -}) diff --git a/Package/Std/Net.luau b/Package/Std/Net.luau deleted file mode 100644 index f2d335e..0000000 --- a/Package/Std/Net.luau +++ /dev/null @@ -1,41 +0,0 @@ ---[[ - Imports a Standard runtime library, in this case we're using Lune - but if we wanted to hop into another Runtime.. - theoretically, we could - we'd just need to emulate the lune runtime. -]] - -local net = require("@lune/net") - ---[[ - because 'udpSocket' is not actually apart of @lune/net, instead it was apart of a fork of @lune - we don't want normal lune installs to error out - so I've implemented a generic/simple sandbox - which simulates a 'udpSocket' but won't ever connect, send or recieve data. -]] - -local udpSocket = (net :: any).udpSocket -local udpSocketClose = (net :: any).udpSocketClose - -if not (net :: any).udpSocket then - udpSocket = function() - local self = {} - - self.send = function() end - self.next = function() - coroutine.yield() - end - self.connect = function() end - - return self - end - - udpSocketClose = function() end -end - -export type WebSocket = net.WebSocket -export type FetchResponse = net.FetchResponse - -return setmetatable({ - udpSocket = udpSocket, - udpSocketClose = udpSocketClose, -}, { - __index = net, -}) diff --git a/Package/Std/Process.luau b/Package/Std/Process.luau deleted file mode 100644 index 8b5e37a..0000000 --- a/Package/Std/Process.luau +++ /dev/null @@ -1,8 +0,0 @@ ---[[ - Imports a Standard runtime library, in this case we're using Lune - but if we wanted to hop into another Runtime.. - theoretically, we could - we'd just need to emulate the lune runtime. -]] - -return setmetatable({}, { - __index = require("@lune/process"), -}) diff --git a/Package/Std/Serde.luau b/Package/Std/Serde.luau deleted file mode 100644 index a5bd6e9..0000000 --- a/Package/Std/Serde.luau +++ /dev/null @@ -1,8 +0,0 @@ ---[[ - Imports a Standard runtime library, in this case we're using Lune - but if we wanted to hop into another Runtime.. - theoretically, we could - we'd just need to emulate the lune runtime. -]] - -return setmetatable({}, { - __index = require("@lune/serde"), -}) diff --git a/Package/Std/Task.luau b/Package/Std/Task.luau deleted file mode 100644 index a06fb3f..0000000 --- a/Package/Std/Task.luau +++ /dev/null @@ -1,8 +0,0 @@ ---[[ - Imports a Standard runtime library, in this case we're using Lune - but if we wanted to hop into another Runtime.. - theoretically, we could - we'd just need to emulate the lune runtime. -]] - -return setmetatable({}, { - __index = require("@lune/task"), -}) diff --git a/Package/Utils/Construct.luau b/Package/Utils/Construct.luau deleted file mode 100644 index c97f936..0000000 --- a/Package/Utils/Construct.luau +++ /dev/null @@ -1,76 +0,0 @@ ---!nocheck - ---[[ - Constructs a Discord-Luau class object, every object in Discord-Luau calls out to this method. -]] - -local function failCall(object: any, callType: string) - error(`Invoked unsupported metamethod '{callType}' on object '{tostring(object)}'`, 3) -end - -return function(source: S, prototype: M): S & M - local object - - object = setmetatable(source, { - __index = prototype, - -- __newindex = source, - - __metatable = "This metatable is locked", - - __call = function(self) - return failCall(self, "Call") - end, - __concat = function(self) - return failCall(self, "Concat") - end, - __iter = function(self) - return failCall(self, "Iter") - end, - - __unm = function(self) - return failCall(self, "-.") - end, - __add = function(self) - return failCall(self, "+") - end, - __sub = function(self) - return failCall(self, "-") - end, - __mul = function(self) - return failCall(self, "*") - end, - __div = function(self) - return failCall(self, "/") - end, - __idiv = function(self) - return failCall(self, "//") - end, - __mod = function(self) - return failCall(self, "%") - end, - __pow = function(self) - return failCall(self, "^") - end, - __lt = function(self) - return failCall(self, "<") - end, - __le = function(self) - return failCall(self, "<=") - end, - __len = function(self) - return failCall(self, "#") - end, - - __mode = "", - __type = prototype.type or "GenericClass", - - -- __eq = ... - -- __gc = ... - - __tostring = function() - return `DiscordLuau<'{prototype.type or "GenericClass"}'>` - end, - }) - - return object -end diff --git a/Package/Utils/Dictionary/FetchKeys.luau b/Package/Utils/Dictionary/FetchKeys.luau deleted file mode 100644 index fb31227..0000000 --- a/Package/Utils/Dictionary/FetchKeys.luau +++ /dev/null @@ -1,9 +0,0 @@ -return function(dictionary: { [K]: V }): { K } - local result = {} - - for key in dictionary do - table.insert(result, key) - end - - return result -end diff --git a/Package/Utils/Dictionary/IsUnique.luau b/Package/Utils/Dictionary/IsUnique.luau deleted file mode 100644 index 74fadac..0000000 --- a/Package/Utils/Dictionary/IsUnique.luau +++ /dev/null @@ -1,23 +0,0 @@ -local IsUnique - -IsUnique = function(dictionary0: { [any]: any }, dictionary1: { [any]: any }) - for key, value in dictionary0 do - if type(value) == "table" then - if not dictionary1[key] then - return false - end - - if IsUnique(value, dictionary1[key]) then - return true - end - else - if value ~= dictionary1[key] then - return true - end - end - end - - return false -end - -return IsUnique diff --git a/Package/Utils/Enumerate.luau b/Package/Utils/Enumerate.luau deleted file mode 100644 index 52dfafb..0000000 --- a/Package/Utils/Enumerate.luau +++ /dev/null @@ -1,43 +0,0 @@ ---[[ - Create a list of enumerated values. -]] - -local Enumeration = {} - -Enumeration.Type = "Enumeration" - -Enumeration.Interface = {} -Enumeration.Prototype = {} - -function Enumeration.Prototype:Is(item) - for _, value in self do - if item == value then - return true - end - end - - return false -end - -function Enumeration.Prototype:Assert(item) - assert(self:Is(item), `EnumItem is invalid!`) -end - -function Enumeration.Prototype:ToString() - return `{Enumeration.Type}<"{table.concat(self, ", ")}">` -end - -function Enumeration.Interface.new(items: S): S & typeof(Enumeration.Prototype) - return setmetatable(items :: any, { - __index = Enumeration.Prototype, - __type = Enumeration.Type, - __iter = function(...) - return next, ... - end, - __tostring = function(self) - return self:ToString() - end, - }) -end - -return Enumeration.Interface diff --git a/Package/Utils/Extend.luau b/Package/Utils/Extend.luau deleted file mode 100644 index 551bc1f..0000000 --- a/Package/Utils/Extend.luau +++ /dev/null @@ -1,98 +0,0 @@ ---!nocheck - ---[[ - Extend class0 with class1 -]] - -local function failCall(object: any, callType: string) - error(`Invoked unsupported metamethod '{callType}' on object '{tostring(object)}'`, 3) -end - -return function(class0: S, class1: M): S & M & { super: M } - local object - - class0.super = class1 - - object = setmetatable({}, { - __index = function(_, index) - if class0[index] then - return class0[index] - end - - local indexOf = class1[index] - - if type(indexOf) == "function" then - return function(...) - local args = { ... } - - if select(1, ...) == object then - args[1] = class1 - end - - return indexOf(table.unpack(args)) - end - else - return indexOf - end - end, - -- __newindex = source, - - __metatable = "This metatable is locked", - - __call = function(self) - return failCall(self, "Call") - end, - __concat = function(self) - return failCall(self, "Concat") - end, - __iter = function(self) - return failCall(self, "Iter") - end, - - __unm = function(self) - return failCall(self, "-.") - end, - __add = function(self) - return failCall(self, "+") - end, - __sub = function(self) - return failCall(self, "-") - end, - __mul = function(self) - return failCall(self, "*") - end, - __div = function(self) - return failCall(self, "/") - end, - __idiv = function(self) - return failCall(self, "//") - end, - __mod = function(self) - return failCall(self, "%") - end, - __pow = function(self) - return failCall(self, "^") - end, - __lt = function(self) - return failCall(self, "<") - end, - __le = function(self) - return failCall(self, "<=") - end, - __len = function(self) - return failCall(self, "#") - end, - - __mode = "", - __type = `{class0.type} ({class1.type})`, - - -- __eq = ... - -- __gc = ... - - __tostring = function() - return `DiscordLuau<'{`{class0.type} ({class1.type})` or "GenericClass"}'>` - end, - }) - - return object -end diff --git a/Package/Utils/Inherit.luau b/Package/Utils/Inherit.luau deleted file mode 100644 index 608f305..0000000 --- a/Package/Utils/Inherit.luau +++ /dev/null @@ -1,78 +0,0 @@ ---!nocheck - ---[[ - Inherit the prototype of another class, into an already constructed discord-luau class. -]] - -local function failCall(object: any, callType: string) - error(`Invoked unsupported metamethod '{callType}' on object '{tostring(object)}'`, 3) -end - -return function(source: S, class: M): S & M - local object - - object = setmetatable({}, { - __index = function(_, index) - return source[index] or class[index] - end, - -- __newindex = source, - - __metatable = "This metatable is locked", - - __call = function(self) - return failCall(self, "Call") - end, - __concat = function(self) - return failCall(self, "Concat") - end, - __iter = function(self) - return failCall(self, "Iter") - end, - - __unm = function(self) - return failCall(self, "-.") - end, - __add = function(self) - return failCall(self, "+") - end, - __sub = function(self) - return failCall(self, "-") - end, - __mul = function(self) - return failCall(self, "*") - end, - __div = function(self) - return failCall(self, "/") - end, - __idiv = function(self) - return failCall(self, "//") - end, - __mod = function(self) - return failCall(self, "%") - end, - __pow = function(self) - return failCall(self, "^") - end, - __lt = function(self) - return failCall(self, "<") - end, - __le = function(self) - return failCall(self, "<=") - end, - __len = function(self) - return failCall(self, "#") - end, - - __mode = "", - __type = class.type or "GenericClass", - - -- __eq = ... - -- __gc = ... - - __tostring = function() - return `DiscordLuau<'{class.type or "GenericClass"}'>` - end, - }) - - return object -end diff --git a/Package/Vendor/Console.luau b/Package/Vendor/Console.luau deleted file mode 100644 index f1043b0..0000000 --- a/Package/Vendor/Console.luau +++ /dev/null @@ -1,13 +0,0 @@ ---[=[ - @class Vendor.Console - - The `Vendor.Console` module is a third-party library used within Discord-Luau for logging and debugging purposes. This module may contain modifications to better integrate with the Discord-Luau framework. - - Original module can be found at: https://docs.asyncmatrix.dev/Packages/Console - -]=] -local Console = require("Embedded/4x8Matrix/Console") - -export type Console = Console.Console - -return Console diff --git a/Package/Vendor/Embedded/4x8Matrix/Console.luau b/Package/Vendor/Embedded/4x8Matrix/Console.luau deleted file mode 100644 index 130aefc..0000000 --- a/Package/Vendor/Embedded/4x8Matrix/Console.luau +++ /dev/null @@ -1,707 +0,0 @@ ---# selene: allow(undefined_variable) - -local DEFAULT_LOGGING_SCHEMA = "[%s][%s] :: %s" -local MAXIMUM_CACHED_LOGS = 500 -local ARBITRARY_LARGE_NUMBER = 9999999 -local PRETTY_TABLE_TAB = string.rep("\t", 1) - -local task = require("../../../Std/Task") - ---[=[ - @class Console - - A package that helps to organise the Roblox output, primarily offering developers quality of life features over the default Roblox output behaviour. -]=] -local Console = {} - ---[=[ - @prop id string - @within Console -]=] - ---[=[ - @prop level number - @within Console -]=] - ---[=[ - @prop schema string - @within Console -]=] - ---[=[ - @prop enabled boolean - @within Console -]=] - ---[=[ - @prop logs { } - @within Console -]=] - -Console.Type = "Console" - -Console.LogLevel = 1 -Console.Schema = DEFAULT_LOGGING_SCHEMA - -Console.Cache = setmetatable({}, { __mode = "kv" }) - -Console.Functions = {} -Console.Interface = {} -Console.Instances = {} -Console.Prototype = {} - -Console.Interface.LogLevel = { - ["debug"] = 1, - ["log"] = 2, - ["warn"] = 3, - ["error"] = 4, - ["critical"] = 5, -} - --- // QoL functions -function Console.Functions:AddScopeToString(string) - local stringSplit = string.split(string, "\n") - - for index, value in stringSplit do - if index == 1 then - continue - end - - stringSplit[index] = string.format("%s%s", PRETTY_TABLE_TAB, value) - end - - return table.concat(stringSplit, "\n") -end - -function Console.Functions:ToPrettyString(...) - local stringifiedObjects = {} - - for _, object in { ... } do - local objectType = typeof(object) - - if objectType == "table" then - if Console.Cache[object] then - table.insert(stringifiedObjects, `RecursiveTable<{tostring(object)}>`) - - continue - else - Console.Cache[object] = true - end - - local tableSchema = "{\n" - local tableEntries = 0 - - for key, value in object do - tableEntries += 1 - - key = self:ToPrettyString(key) - - if typeof(value) == "table" then - value = self:AddScopeToString(self:ToPrettyString(value)) - else - value = self:ToPrettyString(value) - end - - tableSchema ..= string.format("%s[%s] = %s,\n", PRETTY_TABLE_TAB, key, value) - end - - table.insert(stringifiedObjects, tableEntries == 0 and "{ }" or tableSchema .. "}") - elseif objectType == "string" then - table.insert(stringifiedObjects, string.format('"%s"', object)) - else - table.insert(stringifiedObjects, tostring(object)) - end - end - - return table.concat(stringifiedObjects, " ") -end - -function Console.Functions:FormatVaradicArguments(...) - local args = { ... } - - local message = string.rep("%s ", #args) - local messageType = typeof(args[1]) - - if messageType == "string" then - local sourceString = table.remove(args, 1) - local filteredString = string.gsub(sourceString, "%%", "%%%%") - - message = filteredString - end - - for index, value in args do - args[index] = self:ToPrettyString(value) - end - - table.clear(Console.Cache) - - return string.format(message, table.unpack(args)) -end - -function Console.Functions:FormatMessageSchema(traceback: boolean, schema: string, source: string, ...) - source = source or debug.info(2, "s") - - local additionalSource = "" - - if traceback then - local tracebackData = debug.traceback("", 3) - - additionalSource = "\n" - .. string.format(schema, source, `traceback`, `begin`) - .. tracebackData - .. string.format(schema, source, `traceback`, `end`) - end - - return string.format(schema, source, ...) .. additionalSource -end - ---[=[ - @method assert - @within Console - - @param condition boolean? - @param message ... - - Assertions, however written through our Console, if the condition isn't met, the Console will call :error on itself with the given message. - - ```lua - local Console = Console.new("Console") - - Console:assert(1 == 1, "Hello, World!") -- > will output: nothing - Console:assert(1 == 2, "Hello, World!") -- > will output: [Console][error]: "Hello, World!" - ``` -]=] -function Console.Prototype:assert(condition, ...): () - if not condition then - self:error(...) - end -end - ---[=[ - @method critical - @within Console - - @param message ... - - Create a new log for 'critical', critical being deployed in a situation where something has gone terribly wrong. - - ```lua - local Console = Console.new("Console") - - Console:critical("Hello, World!") -- > will output: [Console][critical]: "Hello, World!" - ``` -]=] -function Console.Prototype:critical(...): () - local outputMessage = Console.Functions:FormatMessageSchema( - self.traceback, - self.schema, - self.id, - "critical", - Console.Functions:FormatVaradicArguments(...) - ) - - table.insert(self.logs, 1, { "critical", outputMessage, self.id }) - if #self.logs > MAXIMUM_CACHED_LOGS then - table.remove(self.logs, MAXIMUM_CACHED_LOGS) - end - - local logLevel = if self.level > Console.LogLevel or self.orphaned then self.level else Console.LogLevel - - if logLevel > Console.Interface.LogLevel.critical then - local thread = coroutine.running() - - task.defer(function() - task.cancel(thread) - end) - - return coroutine.yield() - end - - error(outputMessage, ARBITRARY_LARGE_NUMBER) -end - ---[=[ - @method error - @within Console - - @param message ... - - Create a new log for 'error', this is for errors raised through a developers code on purpose. - - ```lua - local Console = Console.new("Console") - - Console:error("Hello, World!") -- > will output: [Console][error]: "Hello, World!" - ``` -]=] -function Console.Prototype:error(...): () - local outputMessage = Console.Functions:FormatMessageSchema( - self.traceback, - self.schema, - self.id, - "error", - Console.Functions:FormatVaradicArguments(...) - ) - - table.insert(self.logs, 1, { "error", outputMessage, self.id }) - if #self.logs > MAXIMUM_CACHED_LOGS then - table.remove(self.logs, MAXIMUM_CACHED_LOGS) - end - - local logLevel = if self.level > Console.LogLevel or self.orphaned then self.level else Console.LogLevel - - if logLevel > Console.Interface.LogLevel.error then - local thread = coroutine.running() - - task.defer(function() - task.cancel(thread) - end) - - return coroutine.yield() - end - - error(outputMessage, ARBITRARY_LARGE_NUMBER) -end - ---[=[ - @method warn - @within Console - - @param message ... - - Create a new log for 'warn', this is for informing developers about something which takes precedence over a log - - ```lua - local Console = Console.new("Console") - - Console:warn("Hello, World!") -- > will output: [Console][warn]: "Hello, World!" - ``` -]=] -function Console.Prototype:warn(...): () - local outputMessage = Console.Functions:FormatMessageSchema( - self.traceback, - self.schema, - self.id, - "warn", - Console.Functions:FormatVaradicArguments(...) - ) - - table.insert(self.logs, 1, { "warn", outputMessage, self.id }) - if #self.logs > MAXIMUM_CACHED_LOGS then - table.remove(self.logs, MAXIMUM_CACHED_LOGS) - end - - local logLevel = if self.level > Console.LogLevel or self.orphaned then self.level else Console.LogLevel - - if logLevel > Console.Interface.LogLevel.warn then - return - end - - warn(outputMessage) -end - ---[=[ - @method log - @within Console - - @param message ... - - Create a new log for 'log', this is for general logging - ideally what we would use in-place of print. - - ```lua - local Console = Console.new("Console") - - Console:log("Hello, World!") -- > will output: [Console][log]: "Hello, World!" - ``` -]=] -function Console.Prototype:log(...): () - local outputMessage = Console.Functions:FormatMessageSchema( - self.traceback, - self.schema, - self.id, - "log", - Console.Functions:FormatVaradicArguments(...) - ) - - table.insert(self.logs, 1, { "log", outputMessage, self.id }) - if #self.logs > MAXIMUM_CACHED_LOGS then - table.remove(self.logs, MAXIMUM_CACHED_LOGS) - end - - local logLevel = if self.level > Console.LogLevel or self.orphaned then self.level else Console.LogLevel - - if logLevel > Console.Interface.LogLevel.log then - return - end - - print(outputMessage) -end - ---[=[ - @method debug - @within Console - - @param message ... - - Create a new log for 'debug', typically we should only use 'debug' when debugging code or leaving hints for developers. - - ```lua - local Console = Console.new("Console") - - Console:debug("Hello, World!") -- > will output: [Console][debug]: "Hello, World!" - ``` -]=] -function Console.Prototype:debug(...): () - local outputMessage = Console.Functions:FormatMessageSchema( - self.traceback, - self.schema, - self.id, - "debug", - Console.Functions:FormatVaradicArguments(...) - ) - - table.insert(self.logs, 1, { "debug", outputMessage, self.id }) - if #self.logs > MAXIMUM_CACHED_LOGS then - table.remove(self.logs, MAXIMUM_CACHED_LOGS) - end - - local logLevel = if self.level > Console.LogLevel or self.orphaned then self.level else Console.LogLevel - - if logLevel > Console.Interface.LogLevel.debug then - return - end - - print(outputMessage) -end - ---[=[ - @method setLogLevel - @within Console - - @param logLevel number - - Set an log level for this Console, log levels assigned per Console override the global log level. - - LogLevels that are by default set in `Console`: - - - 1 = debug - - 2 = log - - 3 = warn - - 4 = error - - 5 = critical - - - As an alternative, Console provides a `LogLevel` enum, you can access this enum like: `Console.LogLevel.debug` - - - ```lua - local Console = ConsoleModule.new("Console") - - ConsoleModule.setGlobalLogLevel(Console.LogLevel.warn) - - Console:log("Hello, World!") -- this will NOT output anything - Console:warn("Hello, World!") -- this will output something - - Console:setLogLevel(Console.LogLevel.log) - - Console:log("Hello, World!") -- this will output something - Console:warn("Hello, World!") -- this will output something - ``` -]=] -function Console.Prototype:setLogLevel(logLevel: number): () - self.level = logLevel -end - ---[=[ - @method setTracebackEnabled - @within Console - - @param traceback boolean - - @since 2.1.0 - - Enable/Disable traceback logging for the 'Console' object - - ```lua - local reporter = Console.new("🔥 CoolReporter") - - reporter:setTracebackEnabled(true) - reporter:log("Hello, World") --[[ - ["🔥 CoolReporter"]["log"]: "Hello, World" - ["🔥 CoolReporter"]["traceback"]: begin - Script 'ServerScriptService.Place.Services.GameLoopService', Line 30 - function OnGameStateBlocking - Script 'ServerScriptService.Place.Services.GameLoopService', Line 48 - Script 'ReplicatedStorage.Packages._Index.sleitnick_signal@1.5.0.signal', Line 56 - function acquireRunnerThreadAndCallEventHandler - Script 'ReplicatedStorage.Packages._Index.sleitnick_signal@1.5.0.signal', Line 67 - function runEventHandlerInFreeThread - ["🔥 CoolReporter"]["traceback"]: end - ]] - ``` -]=] -function Console.Prototype:setTracebackEnabled(traceback: boolean): () - self.traceback = traceback or false -end - ---[=[ - @method setState - @within Console - - @param state: boolean - - Sets the state of the Console, state depicts if the Console can log messages into the output. - - ```lua - local Console = Console.new("Console") - - Console:log("Hello, World!") -- > will output: [Console][log]: "Hello, World!" - Console:setState(false) - Console:log("Hello, World!") -- > will output: nothing - ``` -]=] -function Console.Prototype:setState(state: boolean): () - self.enabled = state -end - ---[=[ - @method fetchLogs - @within Console - - @param count: number? - - @return { [number]: { logType: string, message: string, logId: string } } - - Fetch an array of logs generated through this Console - - ```lua - local Console = Console.new("Console") - - Console:log("Hello, World!") -- > [Console][log]: "Hello, World!" - Console:fetchLogs() -- > [[ - { - "log", - "[Console][log]: \"Hello, World!\"", - "Console" - } - ]]-- - ``` -]=] -function Console.Prototype:fetchLogs(count: number): { [number]: { logType: string, message: string, logId: string } } - local fetchedLogs = {} - - if not count then - return self.logs - end - - for index = 1, count do - if not self.logs[index] then - return fetchedLogs - end - - table.insert(fetchedLogs, self.logs[index]) - end - - return fetchedLogs -end - ---[=[ - @method toString - @within Console - - @return string - - Returns a prettified string version of the Console table. - - ```lua - local Value = State.new(0) - - print(tostring(Value)) -- Value<0> - ``` -]=] -function Console.Prototype:toString(): string - return `{Console.Type}<"{tostring(self.id)}">` -end - ---[=[ - @function setGlobalLogLevel - @within Console - - @param logLevel number - - Set the global log level for all Consoles, a log level is the priority of a log, priorities are represented by a number. - - LogLevels that are by default set in `Console`: - - - 1 = debug - - 2 = log - - 3 = warn - - 4 = error - - 5 = critical - - - As an alternative, Console provides a `LogLevel` enum, you can access this enum like: `Console.LogLevel.debug` - - - ```lua - Console.setGlobalLogLevel(Console.LogLevel.warn) - - Console:log("Hello, World!") -- this will NOT output anything - Console:warn("Hello, World!") -- this will output something - ``` -]=] -function Console.Interface.setGlobalLogLevel(logLevel: number): () - Console.LogLevel = logLevel -end - ---[=[ - @function setGlobalSchema - @within Console - - @param schema string - - Set the global schema for all Consoles, a schema is how the log is displayed in the console. - - ```lua - Console.setGlobalSchema("[%s][%s]: %s") - - Console:log("Hello, World!") -- > [][log]: Hello, World! - ``` -]=] -function Console.Interface.setGlobalSchema(schema: string): () - Console.Schema = schema -end - ---[=[ - @function get - @within Console - - @param logId string - - @return Console? - - Fetch a `Console` object through it's given `logId` - - ```lua - Console.get("Console"):log("Hello, World!") -- > [Console][log]: "Hello, World!" - ``` -]=] -function Console.Interface.get(logId: string): Console? - return Console.Instances[logId] -end - ---[=[ - @function new - @within Console - - @param logId string? - @param schema string? - - @return Console - - Constructor to generate a `Console` prototype - - ```lua - Console.new("Example"):log("Hello, World!") -- > [Example][log]: "Hello, World!" - ``` -]=] -function Console.Interface.new(logId: string?, schema: string?): Console - local self = setmetatable({ - id = logId, - level = Console.Interface.LogLevel.debug, - schema = schema or Console.Schema, - traceback = false, - enabled = true, - orphaned = false, - logs = {}, - }, { - __index = Console.Prototype, - __type = Console.Type, - __tostring = function(obj) - return obj:toString() - end, - }) - - if logId then - Console.Instances[self.id] = self - end - - return self -end - ---[=[ - @function newOrphaned - @within Console - - @since since 2.0.4 - - @param logId string? - @param schema string? - - @return Console - - Constructor to generate an orphaned `Console` prototype, orphaned in this case meaning a console object that the Console library will - not track or monitor, thus any global console updates will not be applied to this console object. - - This should be used when using `Console` in a library so that any game `Consoles` are isolated from the libraries `Consoles` - - ```lua - Console.newOrphaned("Example"):log("Hello, World!") -- > [Example][log]: "Hello, World!" - ``` -]=] -function Console.Interface.newOrphaned(logId: string?, schema: string?): Console - local self = setmetatable({ - id = logId, - level = Console.Interface.LogLevel.debug, - schema = schema or DEFAULT_LOGGING_SCHEMA, - enabled = true, - orphaned = true, - logs = {}, - }, { - __index = Console.Prototype, - __type = Console.Type, - __tostring = function(obj) - return obj:toString() - end, - }) - - return self -end - ---[=[ - @function is - @within Console - - @param object Console? - - @return boolean - - Validate if an object is a 'Console' object - - ```lua - local object = Console.new("Test") - - if Console.is(object) then - ... - end - ``` -]=] -function Console.Interface.is(object: Console?): boolean - if not object or type(object) ~= "table" then - return false - end - - local metatable = getmetatable(object) - - return metatable and metatable.__type == Console.Type -end - --- export type Console = typeof(Console.Prototype) & { --- id: string, --- level: number, --- schema: string, --- enabled: boolean, --- logs: {}, --- } - -export type Console = typeof(Console.Interface.new()) - -return Console.Interface diff --git a/Package/Vendor/Embedded/Devforum/Signal.luau b/Package/Vendor/Embedded/Devforum/Signal.luau deleted file mode 100644 index 3e3ed50..0000000 --- a/Package/Vendor/Embedded/Devforum/Signal.luau +++ /dev/null @@ -1,77 +0,0 @@ ---!strict ---# selene: allow(shadowing) - -local task = require("../../../Std/Task") - -local Signal = {} -Signal.__index = Signal - -local Connection = {} -Connection.__index = Connection - -function Connection.new(Signal, Callback) - return setmetatable({ - Signal = Signal, - Callback = Callback, - }, Connection) -end - -function Connection.disconnect(self) - self.Signal[self] = nil -end - -function Signal.new() - return setmetatable({} :: any, Signal) -end - -function Signal.connect(self, Callback) - local CN = Connection.new(self, Callback) - self[CN] = true - return CN -end - -function Signal.once(self, Callback) - local CN - CN = Connection.new(self, function(...) - CN:disconnect() - Callback(...) - end) - self[CN] = true - return CN -end - -function Signal.wait(self) - local waitingCoroutine = coroutine.running() - local cn - cn = self:connect(function(...) - cn:disconnect() - task.spawn(waitingCoroutine, ...) - end) - return coroutine.yield() -end - -function Signal.disconnectAll(self) - table.clear(self) -end - -function Signal.fire(self, ...) - if next(self) then - for CN in pairs(self) do - CN.Callback(...) - end - end -end - -export type Connection = { - disconnect: (self: Connection) -> (), -} - -export type Signal = { - fire: (self: Signal, T...) -> (), - connect: (self: Signal, fn: (T...) -> ()) -> Connection, - once: (self: Signal, fn: (T...) -> ()) -> Connection, - wait: (self: Signal) -> T..., - disconnectAll: (self: Signal) -> (), -} - -return Signal :: { new: () -> Signal<...any> } diff --git a/Package/Vendor/Embedded/Redblox/Future.luau b/Package/Vendor/Embedded/Redblox/Future.luau deleted file mode 100644 index 4688b7e..0000000 --- a/Package/Vendor/Embedded/Redblox/Future.luau +++ /dev/null @@ -1,128 +0,0 @@ ---!nocheck ---# selene: allow(shadowing) - -local Spawn = require("Spawn") - -local Task = require("../../../Std/Task") - -export type Future = { - valueList: { any }?, - afterList: { (T...) -> () }, - yieldList: { thread }, - - isComplete: (self: Future) -> boolean, - isPending: (self: Future) -> boolean, - - expect: (self: Future, Message: string) -> T..., - unwrap: (self: Future) -> T..., - unwrapOr: (self: Future, T...) -> T..., - unwrapOrElse: (self: Future, Else: () -> T...) -> T..., - - after: (self: Future, Callback: (T...) -> ()) -> (), - await: (self: Future) -> T..., -} - -local function isComplete(self: Future): boolean - return self.valueList ~= nil -end - -local function isPending(self: Future): boolean - return self.valueList == nil -end - -local function expect(self: Future, Message: string): T... - assert(self.valueList, Message) - - return table.unpack(self.valueList) -end - -local function unwrap(self: Future): T... - return self:expect("Attempt to unwrap pending future!") -end - -local function unwrapOr(self: Future, ...): T... - if self.valueList then - return table.unpack(self.valueList) - else - return ... - end -end - -local function unwrapOrElse(self: Future, Else: () -> T...): T... - if self.valueList then - return table.unpack(self.valueList) - else - return Else() - end -end - -local function after(self: Future, Callback: (T...) -> ()): T... - if self.valueList then - Spawn(Callback, table.unpack(self.valueList)) - else - table.insert(self.afterList, Callback) - end -end - -local function await(self: Future): T... - if self.valueList then - return table.unpack(self.valueList) - else - table.insert(self.yieldList, coroutine.running()) - - return coroutine.yield() - end -end - -local function Future(Callback: (A...) -> T..., ...: A...): Future - local self: Future = { - valueList = nil, - afterList = {}, - yieldList = {}, - - isComplete = isComplete, - isPending = isPending, - - expect = expect, - unwrap = unwrap, - unwrapOr = unwrapOr, - unwrapOrElse = unwrapOrElse, - - after = after, - await = await, - } :: any - - Spawn(function(self: Future, Callback: (A...) -> T..., ...: A...) - local valueList = { Callback(...) } - self.valueList = valueList - - for _, Thread in self.yieldList do - Task.spawn(Thread, table.unpack(valueList)) - end - - for _, Callback in self.afterList do - Spawn(Callback, table.unpack(valueList)) - end - end, self, Callback, ...) - - return self -end - -local function Try(Callback: (A...) -> T..., ...: A...): Future - return Future(function(...) - local data = { pcall(Callback, ...) } - - local success = table.remove(data, 1) - - if not success then - error(data[1]) - end - - return table.unpack(data) - end, ...) -end - -return { - new = Future, - try = Try, -} diff --git a/Package/Vendor/Embedded/Redblox/Spawn.luau b/Package/Vendor/Embedded/Redblox/Spawn.luau deleted file mode 100644 index d35e706..0000000 --- a/Package/Vendor/Embedded/Redblox/Spawn.luau +++ /dev/null @@ -1,27 +0,0 @@ -local Task = require("../../../Std/Task") - -local FreeThreads: { thread } = {} - -local function RunCallback(Callback, Thread, ...) - Callback(...) - table.insert(FreeThreads, Thread) -end - -local function Yielder() - while true do - RunCallback(coroutine.yield()) - end -end - -return function(Callback: (T...) -> (), ...: T...) - local Thread - if #FreeThreads > 0 then - Thread = FreeThreads[#FreeThreads] - FreeThreads[#FreeThreads] = nil - else - Thread = coroutine.create(Yielder) - coroutine.resume(Thread) - end - - Task.spawn(Thread, Callback, Thread, ...) -end diff --git a/Package/Vendor/Future.luau b/Package/Vendor/Future.luau deleted file mode 100644 index bb03bc0..0000000 --- a/Package/Vendor/Future.luau +++ /dev/null @@ -1,13 +0,0 @@ ---[=[ - @class Vendor.Future - - The `Vendor.Future` module is a third-party library used within Discord-Luau for handling asynchronous operations. This module may contain modifications to better integrate with the Discord-Luau framework. - - Original module can be found at: https://util.redblox.dev/future.html - -]=] -local Future = require("Embedded/Redblox/Future") - -export type Future = Future.Future - -return Future diff --git a/Package/Vendor/Signal.luau b/Package/Vendor/Signal.luau deleted file mode 100644 index 226b6cd..0000000 --- a/Package/Vendor/Signal.luau +++ /dev/null @@ -1,14 +0,0 @@ ---[=[ - @class Vendor.Signal - - The `Vendor.Signal` module is a third-party library used within Discord-Luau for creating and managing events and connections. This module may contain modifications to better integrate with the Discord-Luau framework. - - Original module can be found at: https://create.roblox.com/docs/reference/engine/datatypes/RBXScriptSignal - -]=] -local Signal = require("Embedded/Devforum/Signal") - -export type Signal = Signal.Signal -export type Connection = Signal.Connection - -return Signal diff --git a/Package/init.luau b/Package/init.luau deleted file mode 100644 index 4d3ef78..0000000 --- a/Package/init.luau +++ /dev/null @@ -1,265 +0,0 @@ ---[=[ - @class DiscordLuau - - A Discord API wrapper written in Luau, expected to run under the Lune runtime. -]=] - --- Builders -local CommandBuilder = require("@Builders/CommandBuilder") -local CommandOptionBuilder = require("@Builders/CommandOptionBuilder") -local ActivityBuilder = require("@Builders/ActivityBuilder") -local AutomoderationRuleBuilder = require("@Builders/AutomoderationRuleBuilder") -local ChannelBuilder = require("@Builders/ChannelBuilder") -local EmbedBuilder = require("@Builders/EmbedBuilder") -local IntentsBuilder = require("@Builders/IntentsBuilder") -local ModalBuilder = require("@Builders/ModalBuilder") -local PermissionsBuilder = require("@Builders/PermissionsBuilder") -local PresenceBuilder = require("@Builders/PresenceBuilder") -local SettingsBuilder = require("@Builders/SettingsBuilder") -local MessageBuilder = require("@Builders/MessageBuilder") -local AttachmentBuilder = require("@Builders/AttachmentBuilder") - -local OnboardingBuilder = require("@Builders/OnboardingBuilder") -local OnboardingPromptBuilder = require("@Builders/OnboardingPromptBuilder") -local OnboardingPromptOptionBuilder = require("@Builders/OnboardingPromptOptionBuilder") -local WelcomeScreenBuilder = require("@Builders/WelcomeScreenBuilder") - --- Objects -local DiscordApplication = require("@Objects/DiscordApplication") -local DiscordAutomoderationRule = require("@Objects/DiscordAutomoderationRule") -local DiscordCache = require("@Objects/DiscordCache") -local DiscordChannel = require("@Objects/DiscordChannel") -local DiscordEmoji = require("@Objects/DiscordEmoji") -local DiscordGuild = require("@Objects/DiscordGuild") -local DiscordGuildMember = require("@Objects/DiscordGuildMember") -local DiscordGuildRole = require("@Objects/DiscordGuildRole") -local DiscordInteraction = require("@Objects/DiscordInteraction") -local DiscordInvite = require("@Objects/DiscordInvite") -local DiscordMessage = require("@Objects/DiscordMessage") -local GuildAnnouncementChannel = require("@Objects/GuildAnnouncementChannel") -local GuildAnnouncementThreadChannel = require("@Objects/GuildAnnouncementThreadChannel") -local GuildCategoryChannel = require("@Objects/GuildCategoryChannel") -local GuildDirectoryChannel = require("@Objects/GuildDirectoryChannel") -local GuildForumChannel = require("@Objects/GuildForumChannel") -local GuildMediaChannel = require("@Objects/GuildMediaChannel") -local GuildPrivateThreadChannel = require("@Objects/GuildPrivateThreadChannel") -local GuildPublicThreadChannel = require("@Objects/GuildPublicThreadChannel") -local GuildStageVoiceChannel = require("@Objects/GuildStageVoiceChannel") -local UserDMChannel = require("@Objects/UserDMChannel") -local UserGroupChannel = require("@Objects/UserGroupChannel") -local DiscordUser = require("@Objects/DiscordUser") -local GuildTextChannel = require("@Objects/GuildTextChannel") -local GuildVoiceChannel = require("@Objects/GuildVoiceChannel") - --- Components -local ActionRowBuilder = require("@Builders/Interface/ActionRowBuilder") -local ButtonBuilder = require("@Builders/Interface/ButtonBuilder") -local SelectionBuilder = require("@Builders/Interface/SelectionBuilder") -local TextInputBuilder = require("@Builders/Interface/TextInputBuilder") - --- Client -local DiscordClient = require("Classes/DiscordClient") - -local DiscordLuau = {} - ---#region ---#MARK: Discord-Luau Components - -DiscordLuau.Components = {} - -export type ActionRowBuilder = ActionRowBuilder.ActionRowBuilder -DiscordLuau.ActionRowBuilder = ActionRowBuilder ---[=[ - @prop ActionRowBuilder Builders.Interface.ActionRowBuilder - @within DiscordLuau -]=] - -export type ButtonBuilder = ButtonBuilder.ButtonBuilder -DiscordLuau.ButtonBuilder = ButtonBuilder ---[=[ - @prop ButtonBuilder Builders.Interface.ButtonBuilder - @within DiscordLuau -]=] - -export type SelectionBuilder = SelectionBuilder.SelectionBuilder -DiscordLuau.SelectionBuilder = SelectionBuilder ---[=[ - @prop SelectionBuilder Builders.Interface.SelectionBuilder - @within DiscordLuau -]=] - -export type TextInputBuilder = TextInputBuilder.TextInputBuilder -DiscordLuau.TextInputBuilder = TextInputBuilder ---[=[ - @prop TextInputBuilder Builders.Interface.TextInputBuilder - @within DiscordLuau -]=] - ---#endregion - ---#region ---#MARK: Discord-Luau Builders - -export type OnboardingBuilder = OnboardingBuilder.OnboardingBuilder -DiscordLuau.OnboardingBuilder = OnboardingBuilder ---[=[ - @prop OnboardingBuilder Builders.OnboardingBuilder - @within DiscordLuau -]=] - -export type OnboardingPromptBuilder = OnboardingPromptBuilder.OnboardingPromptBuilder -DiscordLuau.OnboardingPromptBuilder = OnboardingPromptBuilder ---[=[ - @prop OnboardingPromptBuilder Builders.OnboardingPromptBuilder - @within DiscordLuau -]=] - -export type OnboardingPromptOptionBuilder = OnboardingPromptOptionBuilder.OnboardingPromptOptionBuilder -DiscordLuau.OnboardingPromptOptionBuilder = OnboardingPromptOptionBuilder ---[=[ - @prop OnboardingPromptOptionBuilder Builders.OnboardingPromptOptionBuilder - @within DiscordLuau -]=] - -export type WelcomeScreenBuilder = WelcomeScreenBuilder.WelcomeScreenBuilder -DiscordLuau.WelcomeScreenBuilder = WelcomeScreenBuilder ---[=[ - @prop WelcomeScreenBuilder Builders.WelcomeScreenBuilder - @within DiscordLuau -]=] - -export type MessageBuilder = MessageBuilder.MessageBuilder -DiscordLuau.MessageBuilder = MessageBuilder ---[=[ - @prop MessageBuilder Builders.MessageBuilder - @within DiscordLuau -]=] - -export type ChannelBuilder = ChannelBuilder.ChannelBuilder -DiscordLuau.ChannelBuilder = ChannelBuilder ---[=[ - @prop ChannelBuilder Builders.ChannelBuilder - @within DiscordLuau -]=] - -export type AttachmentBuilder = AttachmentBuilder.AttachmentBuilder -DiscordLuau.AttachmentBuilder = AttachmentBuilder ---[=[ - @prop AttachmentBuilder Builders.AttachmentBuilder - @within DiscordLuau -]=] - -export type CommandBuilder = CommandBuilder.CommandBuilder -DiscordLuau.CommandBuilder = CommandBuilder ---[=[ - @prop CommandBuilder Builders.CommandBuilder - @within DiscordLuau -]=] - -export type CommandOptionBuilder = CommandOptionBuilder.CommandOptionBuilder -DiscordLuau.CommandOptionBuilder = CommandOptionBuilder ---[=[ - @prop CommandOptionBuilder Builders.CommandOptionBuilder - @within DiscordLuau -]=] - -export type ActivityBuilder = ActivityBuilder.ActivityBuilder -DiscordLuau.ActivityBuilder = ActivityBuilder ---[=[ - @prop ActivityBuilder Builders.ActivityBuilder - @within DiscordLuau -]=] - -export type AutomoderationRuleBuilder = AutomoderationRuleBuilder.AutomoderationRuleBuilder -DiscordLuau.AutomoderationRuleBuilder = AutomoderationRuleBuilder ---[=[ - @prop AutomoderationRuleBuilder Builders.AutomoderationRuleBuilder - @within DiscordLuau -]=] - -export type EmbedBuilder = EmbedBuilder.EmbedBuilder -DiscordLuau.EmbedBuilder = EmbedBuilder ---[=[ - @prop EmbedBuilder Builders.EmbedBuilder - @within DiscordLuau -]=] - -export type IntentsBuilder = IntentsBuilder.IntentsBuilder -DiscordLuau.IntentsBuilder = IntentsBuilder ---[=[ - @prop IntentsBuilder Builders.IntentsBuilder - @within DiscordLuau -]=] - -export type ModalBuilder = ModalBuilder.ModalBuilder -DiscordLuau.ModalBuilder = ModalBuilder ---[=[ - @prop ModalBuilder Builders.ModalBuilder - @within DiscordLuau -]=] - -export type PermissionsBuilder = PermissionsBuilder.PermissionsBuilder -DiscordLuau.PermissionsBuilder = PermissionsBuilder ---[=[ - @prop PermissionsBuilder Builders.PermissionsBuilder - @within DiscordLuau -]=] - -export type PresenceBuilder = PresenceBuilder.PresenceBuilder -DiscordLuau.PresenceBuilder = PresenceBuilder ---[=[ - @prop PresenceBuilder Builders.PresenceBuilder - @within DiscordLuau -]=] - -export type SettingsBuilder = SettingsBuilder.SettingsBuilder -DiscordLuau.SettingsBuilder = SettingsBuilder ---[=[ - @prop SettingsBuilder Builders.SettingsBuilder - @within DiscordLuau -]=] - ---#endregion - ---#region ---#MARK: Discord-Luau Client - -export type DiscordClient = DiscordClient.DiscordClient -DiscordLuau.DiscordClient = DiscordClient ---[=[ - @prop DiscordClient DiscordClient - @within DiscordLuau -]=] - ---#endregion - ---#region ---#MARK: Discord-Luau Type Exports -export type DiscordApplication = DiscordApplication.DiscordApplication -export type DiscordAutomoderationRule = DiscordAutomoderationRule.DiscordAutomoderationRule -export type DiscordCache = DiscordCache.DiscordCache -export type DiscordChannel = DiscordChannel.DiscordChannel -export type DiscordEmoji = DiscordEmoji.DiscordEmoji -export type DiscordGuild = DiscordGuild.DiscordGuild -export type DiscordGuildMember = DiscordGuildMember.DiscordGuildMember -export type DiscordGuildRole = DiscordGuildRole.DiscordGuildRole -export type DiscordInteraction = DiscordInteraction.DiscordInteraction -export type DiscordInvite = DiscordInvite.DiscordInvite -export type DiscordMessage = DiscordMessage.DiscordMessage -export type DiscordUser = DiscordUser.DiscordUser -export type GuildAnnouncementChannel = GuildAnnouncementChannel.GuildAnnouncementChannel -export type GuildAnnouncementThreadChannel = GuildAnnouncementThreadChannel.GuildAnnouncementThreadChannel -export type GuildCategoryChannel = GuildCategoryChannel.GuildCategoryChannel -export type GuildDirectoryChannel = GuildDirectoryChannel.GuildDirectoryChannel -export type GuildForumChannel = GuildForumChannel.GuildForumChannel -export type GuildMediaChannel = GuildMediaChannel.GuildMediaChannel -export type GuildPrivateThreadChannel = GuildPrivateThreadChannel.GuildPrivateThreadChannel -export type GuildPublicThreadChannel = GuildPublicThreadChannel.GuildPublicThreadChannel -export type GuildStageVoiceChannel = GuildStageVoiceChannel.GuildStageVoiceChannel -export type UserDMChannel = UserDMChannel.UserDMChannel -export type UserGroupChannel = UserGroupChannel.UserGroupChannel -export type GuildTextChannel = GuildTextChannel.GuildTextChannel -export type GuildVoiceChannel = GuildVoiceChannel.GuildVoiceChannel ---#endregion - -return DiscordLuau diff --git a/README.md b/README.md index c83efe1..7bcaec6 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,26 @@ -
-

- discord-luau -

+
+

+ discord-luau +

+
+
    + +

    DiscordLuau

    +
    +
+
-## About - -This is the **[Discord API](https://discord.com/developers/docs/intro) [Wrapper](https://rapidapi.com/blog/api-glossary/api-wrapper/)**, the resource that you'll be interacting with if you want to create a Discord Bot/Application! 🎉 - -### Project Structure - -- `init.luau`: Requires Package/init.luau -- `/Package`: The source code for the Discord-Luau package -- - `/Package/Classes`: All Discord-Luau generated obejcts/classes that the user can interact with. -- - `/Package/Data`: Internal library 'data' modules, consisting of generic lua datatypes, with string values. -- - `/Package/Enums`: Internal library 'enum' files, consisting of string keys and values. -- - `/Package/Std`: Standard libraries that this library uses, if Discord-Luau were to switch to another Runtime, we'd just need to modify the `/Package/Std` folder to support the standard libraries for another Runtime. -- - `/Package/Types`: Some awkward types that we need support for in Discord Luau -- - `/Package/Utils`: Utility functions Discord Luau uses -- - `/Package/Vendor`: Vendor, external resources or packages that Discord Luau uses -- - `/Package/init.luau`: Entrypoint for Discord Luau - -### Project Status - -At the moment, I *([AsynchronousMatrix](https://github.com/4x8Matrix))* will write to the Master branch every now and again, if I introduce breaking changes, I may put that into it's own branch, but at the moment, since there's no `0.1.0` version in sight, the Master branch is where I lurk, adding potentially breaking changes every now and again..! - -Pull Requests are welcome! But there's no guarntee that what has been written in that Pull Request will be merged..! - -### Project Goals - -- Enabling developers to create a discord bot that connect to the Discord Websocket. -- Send and recieve messages from a Discord websoket. -- Take full advantage of the Discord REST Http Library. -- Provide detailed and clear documentation on the Discord API Wrapper - -## Documentation +Discord +GitHub Actions Workflow Status +Lune -Please head on over to the [Wiki](https://github.com/DiscordLuau/Discord-Luau/wiki) for further details, however, i'll soon enough set up a dedicated website for Discord Luau! -## Examples +### Special Thanks +A sub-section just to credit people who have committed to the Discord Luau project and cannot be represented through the GitHub contributors section. -Please take a look at the [/Examples](https://github.com/DiscordLuau/Discord-Luau/tree/Master/Examples) folder under the Master branch! +#### Icon & Design +- [Dekkonot](https://github.com/Dekkonot) - Tilt concept +- [BlizzarBlitz](https://github.com/BizzarBlitz) - Figma troubleshooting & ideas +- [kalrnlo](https://github.com/kalrnlo) - Red ping dot +- [CompeyDev](https://github.com/CompeyDev) - Design prototype diff --git a/aftman.toml b/aftman.toml index d8b0487..49e10fd 100644 --- a/aftman.toml +++ b/aftman.toml @@ -1,9 +1,6 @@ -# This file lists tools managed by Aftman, a cross-platform toolchain manager. -# For more information, see https://github.com/LPGhatguy/aftman - -# To add a new tool, add an entry to this table. [tools] -lune = "filiptibell/lune@0.8.5" -stylua = "JohnnyMorganz/StyLua@0.20.0" +luau-lsp = "JohnnyMorganz/luau-lsp@1.31.0" +lune = "lune-org/lune@0.8.8" +# moonwave-extractor = "evaera/moonwave@1.1.3" selene = "Kampfkarren/selene@0.27.1" -moonwave = "evaera/moonwave@1.1.2" \ No newline at end of file +stylua = "JohnnyMorganz/StyLua@0.20.0" diff --git a/development.luau b/development.luau new file mode 100644 index 0000000..8eca22b --- /dev/null +++ b/development.luau @@ -0,0 +1,58 @@ +local botObject = require("@core/bot") + +local messageBuilder = require("@builders/message/message") +local intentsBuilder = require("@builders/intents") +local interactionBuilder = require("@builders/interaction/interaction") + +local env = require(".env") + +local discordIntents = intentsBuilder.new() + +discordIntents:addIntent("Guilds") + +local discordBot = botObject.new({ + token = env.DISCORD_BOT_TOKEN, + intents = discordIntents:build(), +}) + +discordBot.onAllShardsReady:listen(function() + assert(discordBot.application, ``) + + local interaction = interactionBuilder + .new() + :setName(`interaction-name`) + :setDescription("An example interaction, created with the rewrite of Discord-Luau!") + :addContext("Guild") + :addContext("BotDm") + :addContext("PrivateChannel") + :addIntegrationType("GuildInstall") + :addIntegrationType("UserInstall") + + print(`Creating slash command...`) + discordBot.application:createSlashCommandAsync(interaction:build()):after(function(command) + print(`Created slash command:`, command) + end) + + discordBot.onPingInteraction:listen(function(interaction) + interaction:pongAsync() + end) + + discordBot.onCommandInteraction:listen(function(interaction) + local message = interaction + :messageAsync( + messageBuilder + .new({ + content = "abc", + }) + :build(), + true + ) + :expect(`Something bad happened..`) + + print(message) + end) +end) + +discordBot:connectAsync():after(function() + print("Connected to Discord!") +end) diff --git a/examples/.gitkeep b/examples/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/extern/frktest b/extern/frktest new file mode 160000 index 0000000..5b1ba05 --- /dev/null +++ b/extern/frktest @@ -0,0 +1 @@ +Subproject commit 5b1ba052d229ce1c6859c12496110fe6c34137b2 diff --git a/init.luau b/init.luau index 4da564c..2a60be9 100644 --- a/init.luau +++ b/init.luau @@ -1,48 +1 @@ -local DiscordLuau = require("Package") - -export type ActionRowBuilder = DiscordLuau.ActionRowBuilder -export type ButtonBuilder = DiscordLuau.ButtonBuilder -export type SelectionBuilder = DiscordLuau.SelectionBuilder -export type TextInputBuilder = DiscordLuau.TextInputBuilder - -export type MessageBuilder = DiscordLuau.MessageBuilder -export type CommandBuilder = DiscordLuau.CommandBuilder -export type CommandOptionBuilder = DiscordLuau.CommandOptionBuilder -export type ActivityBuilder = DiscordLuau.ActivityBuilder -export type AutomoderationRuleBuilder = DiscordLuau.AutomoderationRuleBuilder -export type EmbedBuilder = DiscordLuau.EmbedBuilder -export type IntentsBuilder = DiscordLuau.IntentsBuilder -export type ModalBuilder = DiscordLuau.ModalBuilder -export type PermissionsBuilder = DiscordLuau.PermissionsBuilder -export type PresenceBuilder = DiscordLuau.PresenceBuilder -export type SettingsBuilder = DiscordLuau.SettingsBuilder - -export type DiscordClient = DiscordLuau.DiscordClient - -export type DiscordApplication = DiscordLuau.DiscordApplication -export type DiscordAutomoderationRule = DiscordLuau.DiscordAutomoderationRule -export type DiscordCache = DiscordLuau.DiscordCache -export type DiscordChannel = DiscordLuau.DiscordChannel -export type DiscordEmoji = DiscordLuau.DiscordEmoji -export type DiscordGuild = DiscordLuau.DiscordGuild -export type DiscordGuildMember = DiscordLuau.DiscordGuildMember -export type DiscordGuildRole = DiscordLuau.DiscordGuildRole -export type DiscordInteraction = DiscordLuau.DiscordInteraction -export type DiscordInvite = DiscordLuau.DiscordInvite -export type DiscordMessage = DiscordLuau.DiscordMessage -export type GuildAnnouncementChannel = DiscordLuau.GuildAnnouncementChannel -export type GuildAnnouncementThreadChannel = DiscordLuau.GuildAnnouncementThreadChannel -export type GuildCategoryChannel = DiscordLuau.GuildCategoryChannel -export type GuildDirectoryChannel = DiscordLuau.GuildDirectoryChannel -export type GuildForumChannel = DiscordLuau.GuildForumChannel -export type GuildMediaChannel = DiscordLuau.GuildMediaChannel -export type GuildPrivateThreadChannel = DiscordLuau.GuildPrivateThreadChannel -export type GuildPublicThreadChannel = DiscordLuau.GuildPublicThreadChannel -export type GuildStageVoiceChannel = DiscordLuau.GuildStageVoiceChannel -export type UserDMChannel = DiscordLuau.UserDMChannel -export type UserGroupChannel = DiscordLuau.UserGroupChannel -export type DiscordUser = DiscordLuau.DiscordUser -export type GuildTextChannel = DiscordLuau.GuildTextChannel -export type GuildVoiceChannel = DiscordLuau.GuildVoiceChannel - -return DiscordLuau +return require("@core") diff --git a/lune.yml b/lune.yml new file mode 100644 index 0000000..0a2142a --- /dev/null +++ b/lune.yml @@ -0,0 +1,6 @@ +--- +base: luau +globals: + warn: + args: + - type: ... diff --git a/packages/api-types/README.md b/packages/api-types/README.md new file mode 100644 index 0000000..a2e6fbf --- /dev/null +++ b/packages/api-types/README.md @@ -0,0 +1,9 @@ +
+

+ discord-luau +

+
+ +## DiscordLuau - Types + +This package contains luau types for every/all discord v10 APIs, allowing developers to type the HTTP, Websocket requests that they get from the discord API. \ No newline at end of file diff --git a/packages/api-types/src/apiTypes.luau b/packages/api-types/src/apiTypes.luau new file mode 100644 index 0000000..1271397 --- /dev/null +++ b/packages/api-types/src/apiTypes.luau @@ -0,0 +1,1511 @@ +export type Partial = any + +export type Snowflake = string + +-- https://discord.com/developers/docs/resources/user#user-object-premium-types +export type PremiumTypes = number + +-- https://discord.com/developers/docs/reference#locales +export type LanguageLocales = + "id" + | "da" + | "de" + | "en-GB" + | "en-US" + | "es-ES" + | "fr" + | "hr" + | "it" + | "lt" + | "nl" + | "no" + | "pl" + | "pt-BR" + | "ro" + | "fi" + | "sv-SE" + | "vi" + | "tr" + | "cs" + | "el" + | "bg" + | "ru" + | "uk" + | "hi" + | "th" + | "zn-CH" + | "ja" + | "ko" + +-- https://discord.com/developers/docs/topics/teams#data-models-membership-state-enum +export type MembershipState = number + +-- https://discord.com/developers/docs/topics/teams#data-models-membership-state-enum +export type TeamMemberRole = "Owner" | "Admin" | "Developer" | "Read-only" + +-- https://discord.com/developers/docs/resources/guild#guild-object-verification-level +export type VerificationLevel = number + +-- https://discord.com/developers/docs/resources/guild#guild-object-default-message-notification-level +export type DefaultMessageNotification = number + +-- https://discord.com/developers/docs/resources/guild#guild-object-explicit-content-filter-level +export type ExplicitContentFilterLevel = number + +-- https://discord.com/developers/docs/resources/guild#guild-object-explicit-content-filter-level +export type MFALevel = number + +-- https://discord.com/developers/docs/resources/guild#guild-object-guild-nsfw-level +export type GuildNSFWLevel = number + +-- https://discord.com/developers/docs/resources/guild#guild-object-premium-tier +export type PremiumTier = number + +-- https://discord.com/developers/docs/resources/guild#guild-object-system-channel-flags +export type SystemChannelFlags = number + +-- https://discord.com/developers/docs/resources/guild#guild-object-guild-features +export type GuildFeature = + "ANIMATED_BANNER" + | "ANIMATED_ICON" + | "APPLICATION_COMMAND_PERMISSIONS_V2" + | "AUTO_MODERATION" + | "BANNER" + | "COMMUNITY" + | "CREATOR_MONETIZABLE_PROVISIONAL" + | "CREATOR_STORE_PAGE" + | "DEVELOPER_SUPPORT_SERVER" + | "DISCOVERABLE" + | "FEATURABLE" + | "INVITES_DISABLED" + | "INVITE_SPLASH" + | "MEMBER_VERIFICATION_GATE_ENABLED" + | "MORE_STICKERS" + | "NEWS" + | "PARTNERED" + | "PREVIEW_ENABLED" + | "RAID_ALERTS_DISABLED" + | "ROLE_ICONS" + | "ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE" + | "ROLE_SUBSCRIPTIONS_ENABLED" + | "TICKETED_EVENTS_ENABLED" + | "VANITY_URL" + | "VERIFIED" + | "VIP_REGIONS" + | "WELCOME_SCREEN_ENABLED" + +-- https://discord.com/developers/docs/resources/guild#guild-object-mutable-guild-features +export type MutableGuildFeatures = "COMMUNITY" | "DISCOVERABLE" | "INVITES_DISABLED" | "RAID_ALERTS_DISABLED" + +-- https://discord.com/developers/docs/resources/sticker#sticker-object-sticker-types +export type StickerType = number + +-- https://discord.com/developers/docs/resources/sticker#sticker-object-sticker-format-types +export type StickerFormatType = number + +-- https://discord.com/developers/docs/topics/oauth2#shared-resources-oauth2-scopes +export type OAuth2Scopes = + "activities.read" + | "activities.write" + | "applications.builds.read" + | "applications.builds.upload" + | "applications.commands" + | "applications.commands.update" + | "applications.commands.permissions.update" + | "applications.entitlements" + | "applications.store.update" + | "bot" + | "connections" + | "dm_channels.read" + | "email" + | "gdm.join" + | "guilds" + | "guilds.join" + | "guilds.members.read" + | "identify" + | "messages.read" + | "relationships.read" + | "role_connections.write" + | "rpc" + | "rpc.activities.write" + | "rpc.notifications.read" + | "rpc.voice.read" + | "rpc.voice.write" + | "voice" + | "webhook.incoming" + +-- https://discord.com/developers/docs/resources/guild#integration-object +export type IntegrationType = number -- "twitch" | "youtube" | "discord" | "guild_subscriptions" + +-- https://discord.com/developers/docs/resources/application#application-object-application-integration-types +export type ApplicationIntegrationType = number + +-- https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-application-command-permission-type +export type ApplicationCommandPermissionType = number + +-- https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-event-types +export type AutomoderationRuleEventType = number + +-- https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-types +export type AutomoderationRuleTriggerType = number + +-- https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-keyword-preset-types +export type AutomoderationRuleKeywordPresetType = number + +-- https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-action-object-action-types +export type AutomoderationActionType = number + +-- https://discord.com/developers/docs/resources/channel#channel-object-channel-types +export type ChannelType = number + +-- https://discord.com/developers/docs/resources/channel#channel-object-video-quality-modes +export type VideoQualityMode = number + +-- https://discord.com/developers/docs/resources/channel#channel-object-channel-flags +export type ChannelFlags = number + +-- https://discord.com/developers/docs/resources/channel#channel-object-sort-order-types +export type SortOrderType = number + +-- https://discord.com/developers/docs/resources/channel#channel-object-forum-layout-types +export type ForumLayoutType = number + +-- https://discord.com/developers/docs/resources/channel#overwrite-object +export type OverwriteObjectType = number + +-- https://discord.com/developers/docs/monetization/entitlements#entitlement-object-entitlement-types +export type EntitlementType = number + +-- https://discord.com/developers/docs/topics/gateway-events#activity-object-activity-types +export type ActivityType = number + +-- https://discord.com/developers/docs/resources/stage-instance#stage-instance-object-privacy-level +export type PrivacyLevel = number + +-- https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-status +export type GuildScheduledEventStatus = number + +-- https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-entity-types +export type GuildScheduledEventEntityType = number + +-- https://discord.com/developers/docs/resources/guild#integration-object-integration-expire-behaviors +export type IntegrationExpireBehaviours = number + +-- https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-type +export type InteractionType = number + +-- https://discord.com/developers/docs/resources/message#embed-object-embed-types +export type EmbedType = "Rich" | "Image" | "Video" | "GIFV" | "Article" | "Link" | "PollResult" + +-- https://discord.com/developers/docs/resources/channel#message-object-message-types +export type MessageType = number + +-- https://discord.com/developers/docs/resources/channel#message-object-message-activity-types +export type MessageActivityType = number + +-- https://discord.com/developers/docs/interactions/message-components#button-object-button-styles +export type ButtonStyle = number + +-- https://discord.com/developers/docs/interactions/message-components#text-input-object-text-input-styles +export type TextInputStyles = number + +-- https://discord.com/developers/docs/resources/poll#layout-type +export type PollLayoutType = number + +-- https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-type +export type ApplicationCommandOptionType = number + +-- https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-context-types +export type InteractionContextType = number + +-- https://discord.com/developers/docs/resources/invite#invite-object-invite-types +export type InviteTypes = number + +-- https://discord.com/developers/docs/resources/invite#invite-object-invite-target-types +export type InviteTargetTypes = number + +-- https://discord.com/developers/docs/resources/channel#get-reactions-reaction-types +export type ReactionType = number + +-- https://discord.com/developers/docs/resources/application-role-connection-metadata#application-role-connection-metadata-object-application-role-connection-metadata-type +export type ApplicationRoleConnectionMetadataType = number + +-- https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-types +export type ApplicationCommandType = number + +-- https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object-audit-log-events +export type AuditLogEventType = number + +-- https://discord.com/developers/docs/resources/webhook#webhook-object-webhook-types +export type WebhookType = number + +-- https://discord.com/developers/docs/resources/guild#guild-onboarding-object-onboarding-mode +export type OnboardingMode = number + +-- https://discord.com/developers/docs/resources/guild#guild-onboarding-object-prompt-types +export type PromptTypes = number + +-- https://discord.com/developers/docs/resources/user#connection-object-services +export type ConnectionObjectServices = + "battlenet" + | "bungie" + | "domain" + | "ebay" + | "epicgames" + | "facebook" + | "github" + | "instagram" + | "leagueoflegends" + | "paypal" + | "playstation" + | "reddit" + | "riotgames" + | "spotify" + | "skype" + | "stream" + | "tiktok" + | "twitch" + | "twitter" + | "xbox" + | "youtube" + +-- https://discord.com/developers/docs/resources/channel#allowed-mentions-object-allowed-mention-types +export type AllowedMentionTypes = "roles" | "users" | "everyone" + +-- https://discord.com/developers/docs/resources/user#connection-object-visibility-types +export type ConnectionVisibilityTypes = number + +-- https://discord.com/developers/docs/resources/channel#message-reference-types +export type MessageReferenceType = number + +-- https://discord.com/developers/docs/resources/guild#guild-member-object-guild-member-flags +export type GuildMemberFlags = number + +-- https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-response-object-interaction-callback-type +export type InteractionCallbackType = number + +-- https://discord.com/developers/docs/topics/gateway-events#activity-object-activity-timestamps +export type ActivityTimestampObject = { + start: number, -- Unix time (in milliseconds) of when the activity started + ["end"]: number, -- Unix time (in milliseconds) of when the activity ends +} + +-- https://discord.com/developers/docs/topics/gateway-events#activity-object-activity-emoji +export type ActivityEmojiObject = { + name: string, -- Name of the emoji + id: Snowflake, -- ID of the emoji + animated: boolean?, -- Whether the emoji is animated +} + +-- https://discord.com/developers/docs/topics/gateway-events#activity-object-activity-party +export type ActivityPartyObject = { + id: string, -- ID of the party + size: { number }, -- Used to show the party's current and maximum size, array of two integers (current_size, max_size) +} + +-- https://discord.com/developers/docs/topics/gateway-events#activity-object-activity-assets +export type ActivityAssetsObject = { + large_image: string, -- https://discord.com/developers/docs/topics/gateway-events#activity-object-activity-asset-image + large_text: string, -- Text displayed when hovering over the large image of the activity + small_image: string, -- https://discord.com/developers/docs/topics/gateway-events#activity-object-activity-asset-image + small_text: string, -- Text displayed when hovering over the small image of the activity +} + +-- https://discord.com/developers/docs/topics/gateway-events#activity-object-activity-secrets +export type ActivitySecretsObject = { + join: string?, -- Secret for joining a party + spectate: string?, -- Secret for spectating a game + match: string?, -- Secret for a specific instanced match +} + +-- https://discord.com/developers/docs/topics/gateway-events#activity-object-activity-buttons +export type ActivityButtonsObject = { + label: string, -- Text shown on the button (1-32 characters) + url: string, -- URL opened when clicking the button (1-512 characters) +} + +-- https://discord.com/developers/docs/topics/gateway-events#activity-object +export type ActivityObject = { + name: string, -- Activity's name + type: number, -- Activity's type + url: string?, -- Stream URL, is validated when type is 1 + created_at: number, -- Unix timestamp (in milliseconds) of when the activity was added to the user's session + timestamps: ActivityTimestampObject, -- Unix timestamps for start and/or end of the game + application_id: Snowflake, -- Application ID for the game + details: string?, -- What the player is currently doing + state: string?, -- User's current party status, or text used for a custom status + emoji: ActivityEmojiObject?, -- Emoji used for a custom status + party: ActivityPartyObject?, -- Information for the current party of the player + assets: ActivityAssetsObject?, -- Images for the presence and their hover texts + secrets: ActivitySecretsObject?, -- Secrets for Rich Presence joining and spectating + instance: boolean?, -- Whether or not the activity is an instanced game session + flags: number?, -- Activity flags ORd together, describes what the payload includes + buttons: { ActivityButtonsObject }?, -- Custom buttons shown in the Rich Presence (max 2) +} + +-- https://discord.com/developers/docs/topics/gateway-events#update-presence +export type PresenceObject = { + since: number, -- Unix time (in milliseconds) of when the client went idle, or null if the client is not idle + activities: { ActivityObject }, -- User's activities + status: string, -- User's new status + afk: boolean, -- Whether or not the client is afk +} + +-- https://discord.com/developers/docs/topics/gateway-events#identify-identify-connection-properties +export type IdentifyPropertiesObject = { + os: string, + browser: string, + device: string, +} + +-- https://discord.com/developers/docs/resources/user#avatar-decoration-data-object +export type AvatarDecorationDataObject = { + asset: string, -- the avatar decoration hash + sku_id: Snowflake, -- id of the avatar decoration's SKU +} + +-- https://discord.com/developers/docs/resources/user#user-object +export type UserObject = { + id: Snowflake, -- the user's id + username: string, -- the user's username, not unique across the platform + discriminator: string, -- the user's Discord-tag + global_name: string, -- the user's display name, if it is set. For bots, this is the application name + avatar: string, -- the user's avatar hash + bot: boolean?, -- whether the user belongs to an OAuth2 application + system: boolean?, -- whether the user is an Official Discord System user (part of the urgent message system) + mfa_enabled: boolean?, -- whether the user has two factor enabled on their account + banner: string?, -- the user's banner hash + accent_color: number?, -- the user's banner color encoded as an integer representation of hexadecimal color code + locale: LanguageLocales?, -- the user's chosen language option + verified: boolean?, -- whether the email on this account has been verified + email: string?, -- the user's email + flags: number?, -- the flags on a user's account + premium_type: PremiumTypes?, -- the type of Nitro subscription on a user's account + public_flags: number?, -- the public flags on a user's account + avatar_decoration_data: AvatarDecorationDataObject?, -- data for the user's avatar decoration +} + +-- https://discord.com/developers/docs/resources/guild#unavailable-guild-object +export type UnavailableGuildObject = { + id: Snowflake, -- guild id + unavailable: boolean, -- true, the guild is unavailable +} + +-- https://discord.com/developers/docs/topics/teams#data-models-team-member-object +export type TeamMemberObject = { + membership_state: MembershipState, -- User's membership state on the team + team_id: Snowflake, -- ID of the parent team of which they are a member + user: UserObject, -- Avatar, discriminator, ID, and username of the user + role: TeamMemberRole, -- Role of the team member +} + +-- https://discord.com/developers/docs/topics/teams#data-models-team-object +export type TeamObject = { + icon: string, -- Hash of the image of the team's icon + id: Snowflake, -- Unique ID of the team + members: { TeamMemberObject }, -- Members of the team + name: string, -- Name of the team + owner_user_id: Snowflake, -- User ID of the current team owner +} + +-- https://discord.com/developers/docs/topics/permissions#role-object-role-tags-structure +export type GuildRoleTagObject = { + bot_id: Snowflake?, -- the id of the bot this role belongs to + integration_id: Snowflake?, -- the id of the integration this role belongs to + premium_subscriber: nil, -- whether this is the guild's Booster role + subscription_listing_id: Snowflake, -- the id of this role's subscription sku and listing + available_for_purchase: nil, -- whether this role is available for purchase + guild_connections: nil, -- whether this role is a guild's linked role +} + +-- https://discord.com/developers/docs/topics/permissions#role-object +export type GuildRoleObject = { + id: Snowflake?, -- role id + name: string, -- role name + color: number, -- integer representation of hexadecimal color code + hoist: boolean, -- if this role is pinned in the user listing + icon: string?, -- role icon hash + unicode_emoji: string?, -- role unicode emoji + position: number, -- position of this role (roles with the same position are sorted by id) + permissions: string, -- permission bit set + managed: boolean, -- whether this role is managed by an integration + mentionable: boolean, -- whether this role is mentionable + tags: GuildRoleTagObject?, -- the tags this role has + flags: number, -- role flags combined as a bitfield +} + +-- https://discord.com/developers/docs/resources/emoji#emoji-object +export type EmojiObject = { + id: Snowflake?, -- emoji id + name: string, -- emoji name + roles: { Snowflake }?, -- roles allowed to use this emoji + user: UserObject?, -- user that created this emoji + require_colons: boolean?, -- whether this emoji must be wrapped in colons + managed: boolean?, -- whether this emoji is managed + animated: boolean?, -- whether this emoji is animated + available: boolean?, -- whether this emoji can be used, may be false due to loss of Server Boosts +} + +-- https://discord.com/developers/docs/resources/guild#welcome-screen-object-welcome-screen-channel-structure +export type WelcomeScreenChannelObject = { + channel_id: Snowflake, -- the channel's id + description: string, -- the description shown for the channel + emoji_id: Snowflake?, -- the emoji id, if the emoji is custom + emoji_name: string?, -- the emoji name if custom, the unicode character if standard, or null if no emoji is set +} + +-- https://discord.com/developers/docs/resources/guild#welcome-screen-object +export type WelcomeScreenObject = { + description: string, -- the server description shown in the welcome screen + welcome_channels: { WelcomeScreenChannelObject }, -- the channels shown in the welcome screen, up to 5 +} + +-- https://discord.com/developers/docs/resources/sticker#sticker-object +export type StickerObject = { + id: Snowflake, -- id of the sticker + pack_id: Snowflake, -- for standard stickers, id of the pack the sticker is from + name: string, -- name of the sticker + description: string?, -- description of the sticker + tags: string, -- autocomplete/suggestion tags for the sticker (max 200 characters) + asset: string?, -- Deprecated previously the sticker asset hash, now an empty string + type: StickerType, -- type of sticker + format_type: StickerFormatType, -- type of sticker format + available: boolean?, -- whether this guild sticker can be used, may be false due to loss of Server Boosts + guild_id: Snowflake?, -- id of the guild that owns this sticker + user: UserObject?, -- the user that uploaded the guild sticker + sort_value: number?, -- the standard sticker's sort order within its pack +} + +-- https://discord.com/developers/docs/resources/guild#guild-object +export type GuildObject = { + id: Snowflake?, -- guild id + name: string?, -- guild name (2-100 characters, excluding trailing and leading whitespace) + icon: string?, -- icon hash + icon_hash: string?, -- icon hash, returned when in the template object + splash: string?, -- splash hash + discovery_splash: string?, -- discovery splash hash; only present for guilds with the "DISCOVERABLE" feature + owner: boolean?, -- true if the user is the owner of the guild + owner_id: Snowflake?, -- id of owner + permissions: string?, -- total permissions for the user in the guild (excludes overwrites and implicit permissions) + region: string?, -- voice region id for the guild (deprecated) + afk_channel_id: Snowflake?, -- id of afk channel + afk_timeout: number?, -- afk timeout in seconds + widget_enabled: boolean?, -- true if the server widget is enabled + widget_channel_id: Snowflake?, -- the channel id that the widget will generate an invite to, or null if set to no invite + verification_level: VerificationLevel?, -- verification level required for the guild + default_message_notifications: DefaultMessageNotification?, -- default message notifications level + explicit_content_filter: ExplicitContentFilterLevel?, -- explicit content filter level + roles: { GuildRoleObject }?, -- roles in the guild + emojis: { EmojiObject }?, -- custom guild emojis + features: { GuildFeature }?, -- enabled guild features + mfa_level: MFALevel?, -- required MFA level for the guild + application_id: Snowflake?, -- application id of the guild creator if it is bot-created + system_channel_id: Snowflake?, -- the id of the channel where guild notices such as welcome messages and boost events are posted + system_channel_flags: SystemChannelFlags?, -- system channel flags + rules_channel_id: Snowflake?, -- the id of the channel where Community guilds can display rules and/or guidelines + max_presences: number?, -- the maximum number of presences for the guild (null is always returned, apart from the largest of guilds) + max_members: number?, -- the maximum number of members for the guild + vanity_url_code: string?, -- the vanity url code for the guild + description: string?, -- the description of a guild + banner: string?, -- banner hash + premium_tier: PremiumTier?, -- premium tier (Server Boost level) + premium_subscription_count: number?, -- the number of boosts this guild currently has + preferred_locale: LanguageLocales?, -- the preferred locale of a Community guild; used in server discovery and notices from Discord, and sent in interactions; defaults to "en-US" + public_updates_channel_id: Snowflake?, -- the id of the channel where admins and moderators of Community guilds receive notices from Discord + max_video_channel_users: number?, -- the maximum amount of users in a video channel + max_stage_video_channel_users: number?, -- the maximum amount of users in a stage video channel + approximate_member_count: number?, -- approximate number of members in this guild, returned from the GET /guilds/ and /users/@me/guilds endpoints when with_counts is true + approximate_presence_count: number?, -- approximate number of non-offline members in this guild, returned from the GET /guilds/ and /users/@me/guilds endpoints when with_counts is true + welcome_screen: WelcomeScreenObject?, -- the welcome screen of a Community guild, shown to new members, returned in an Invite's guild object + nsfw_level: GuildNSFWLevel?, -- guild NSFW level + stickers: { StickerObject }?, -- custom guild stickers + premium_progress_bar_enabled: boolean?, -- whether the guild has the boost progress bar enabled + safety_alerts_channel_id: Snowflake?, -- the id of the channel where admins and moderators of Community guilds receive safety alerts from Discord +} + +-- https://discord.com/developers/docs/resources/application#install-params-object +export type InstallParamsObject = { + scopes: { OAuth2Scopes }, -- Scopes to add the application to the server with + permissions: string, -- Permissions to request for the bot role +} + +-- https://discord.com/developers/docs/resources/application#application-object +export type ApplicationObject = { + id: Snowflake, -- ID of the app + name: string, -- Name of the app + icon: string?, -- Icon hash of the app + description: string, -- Description of the app + rpc_origins: { string }?, -- List of RPC origin URLs, if RPC is enabled + bot_public: boolean, -- When false, only the app owner can add the app to guilds + bot_require_code_grant: boolean, -- When true, the app's bot will only join upon completion of the full OAuth2 code grant flow + bot: UserObject?, -- Partial user object for the bot user associated with the app + terms_of_service_url: string?, -- URL of the app's Terms of Service + privacy_policy_url: string?, -- URL of the app's Privacy Policy + owner: UserObject?, -- Partial user object for the owner of the app + summary: string, -- deprecated and will be removed in v11. An empty string. + verify_key: string, -- Hex encoded key for verification in interactions and the GameSDK's GetTicket + team: TeamObject?, -- If the app belongs to a team, this will be a list of the members of that team + guild_id: Snowflake?, -- Guild associated with the app. For example, a developer support server. + guild: GuildObject?, -- Partial object of the associated guild + primary_sku_id: Snowflake?, -- If this app is a game sold on Discord, this field will be the id of the "Game SKU" that is created, if exists + slug: string?, -- If this app is a game sold on Discord, this field will be the URL slug that links to the store page + cover_image: string?, -- App's default rich presence invite cover image hash + flags: number?, -- App's public flags + approximate_guild_count: number?, -- Approximate count of guilds the app has been added to + redirect_uris: { string }?, -- Array of redirect URIs for the app + interactions_endpoint_url: string?, -- Interactions endpoint URL for the app + role_connections_verification_url: string?, -- Role connection verification URL for the app + tags: { string }?, -- List of tags describing the content and functionality of the app. Max of 5 tags. + install_params: InstallParamsObject?, -- Settings for the app's default in-app authorization link, if enabled + integration_types_config: { [ApplicationIntegrationType]: boolean }?, -- In preview. Default scopes and permissions for each supported installation context. Value for each key is an integration type configuration object + custom_install_url: string?, -- Default custom authorization URL for the app, if enabled +} + +-- https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-application-command-permissions-structure +export type GuildApplicationCommandPermissionObject = { + id: Snowflake, -- ID of the role, user, or channel. It can also be a permission constant + type: ApplicationCommandPermissionType, -- role (1), user (2), or channel (3) + permission: boolean, -- true to allow, false, to disallow +} + +-- https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-guild-application-command-permissions-structure +export type GuildApplicationCommandPermissionsObject = { + id: Snowflake, -- ID of the command or the application ID + application_id: Snowflake, -- ID of the application the command belongs to + guild_id: Snowflake, -- ID of the guild + permissions: { GuildApplicationCommandPermissionObject }, -- Permissions for the command in the guild, max of 100 +} + +-- https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-metadata +export type AutomoderationRuleTriggerMetadataObject = { + keyword_filter: { string }, -- substrings which will be searched for in content (Maximum of 1000) + regex_patters: { string }, -- regular expression patterns which will be matched against content (Maximum of 10) + presets: { AutomoderationRuleKeywordPresetType }, -- the internally pre-defined wordsets which will be searched for in content + allow_list: { string }, -- substrings which should not trigger the rule (Maximum of 100 or 1000) + mention_total_limit: number, -- total number of unique role and user mentions allowed per message (Maximum of 50) + mention_radi_protection_enabled: boolean, -- whether to automatically detect mention raids +} + +-- https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-action-object-action-metadata +export type AutmoderationActionMetadataObject = { + channel_id: Snowflake, -- channel to which user content should be logged + duration_seconds: number, -- timeout duration in seconds, maximum of 2419200 seconds (4 weeks) + custom_message: string?, -- additional explanation that will be shown to members whenever their message is blocked, maximum of 150 characters +} + +-- https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-action-object +export type AutomoderationActionObject = { + type: AutomoderationActionType, -- the type of action + metadata: AutmoderationActionMetadataObject?, -- additional metadata needed during execution for this specific action type +} + +-- https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object +export type AutomoderationRuleObject = { + id: Snowflake, -- the id of this rule + guild_id: Snowflake, -- the id of the guild which this rule belongs to + name: string, -- the rule name + creator_id: Snowflake, -- the user which first created this rule + event_type: AutomoderationRuleEventType, -- the rule event type + trigger_type: AutomoderationRuleTriggerType, -- the rule trigger type + trigger_metadata: AutomoderationRuleTriggerMetadataObject, -- the rule trigger metadata + actions: { AutomoderationActionObject }, -- the actions which will execute when the rule is triggered + enabled: boolean, -- whether the rule is enabled + exempt_roles: { Snowflake }, -- the role ids that should not be affected by the rule (Maximum of 20) + exempt_channels: { Snowflake }, -- the channel ids that should not be affected by the rule (Maximum of 50) +} + +-- https://discord.com/developers/docs/resources/channel#overwrite-object +export type OverwriteObject = { + id: Snowflake, -- role or user id + type: OverwriteObjectType, -- either 0 (role) or 1 (member) + allow: string, -- permission bit set + deny: string, --permission bit set +} + +-- https://discord.com/developers/docs/resources/channel#thread-metadata-object +export type ThreadMetadataObject = { + archived: boolean, -- whether the thread is archived + auto_archive_duration: number, -- the thread will stop showing in the channel list after auto_archive_duration minutes of inactivity, can be set to: 60, 1440, 4320, 10080 + archive_timestamp: string, -- timestamp when the thread's archive status was last changed, used for calculating recent activity + locked: boolean, -- whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it + invitable: boolean?, -- whether non-moderators can add other non-moderators to a thread; only available on private threads + create_timestamp: string, -- timestamp when the thread was created; only populated for threads created after 2022-01-09 +} + +-- https://discord.com/developers/docs/resources/guild#guild-member-object +export type GuildMemberObject = { + user: UserObject?, -- the user this guild member represents + nick: string?, -- this user's guild nickname + avatar: string?, -- the member's guild avatar hash + roles: { Snowflake }, -- array of role object ids + joined_at: string, -- when the user joined the guild + premium_since: string?, -- when the user started boosting the guild + deaf: boolean, -- whether the user is deafened in voice channels + mute: boolean, -- whether the user is muted in voice channels + flags: number, -- guild member flags represented as a bit set, defaults to 0 + pending: boolean?, -- whether the user has not yet passed the guild's Membership Screening requirements + permissions: string?, -- total permissions of the member in the channel, including overwrites, returned when in the interaction object + communication_disabled_until: string?, -- when the user's timeout will expire and the user will be able to communicate in the guild again, null or a time in the past if the user is not timed out + avatar_decoration_data: AvatarDecorationDataObject?, -- data for the member's guild avatar decoration +} + +-- https://discord.com/developers/docs/resources/channel#thread-member-object +export type ThreadMemberObject = { + id: Snowflake?, -- ID of the thread + user_id: Snowflake?, -- ID of the user + join_timestamp: string, -- Time the user last joined the thread + flags: number, -- Any user-thread settings, currently only used for notifications + member: GuildMemberObject?, -- Additional information about the user +} + +-- https://discord.com/developers/docs/resources/channel#forum-tag-object +export type ForumTagObject = { + id: Snowflake, -- the id of the tag + name: string, -- the name of the tag (0-20 characters) + moderated: boolean, -- whether this tag can only be added to or removed from threads by a member with the MANAGE_THREADS permission + emoji_id: string?, -- the id of a guild's custom emoji + emoji_name: string?, -- the unicode character of the emoji +} + +-- https://discord.com/developers/docs/resources/channel#default-reaction-object +export type DefaultReactionObject = { + emoji_id: string?, -- the id of a guild's custom emoji + emoji_name: string?, -- the unicode character of the emoji +} + +-- https://discord.com/developers/docs/resources/channel#channel-object +export type ChannelObject = { + id: Snowflake?, -- the id of this channel + type: ChannelType, -- the type of channel + guild_id: Snowflake?, -- the id of the guild (may be missing for some channel objects received over gateway guild dispatches) + position: number, -- sorting position of the channel (channels with the same position are sorted by id) + permission_overwrites: { OverwriteObject }, -- explicit permission overwrites for members and roles + name: string, -- the name of the channel (1-100 characters) + topic: string?, -- the channel topic (0-4096 characters for GUILD_FORUM and GUILD_MEDIA channels, 0-1024 characters for all others) + nsfw: boolean?, -- whether the channel is nsfw + last_message_id: Snowflake?, -- the id of the last message sent in this channel (or thread for GUILD_FORUM or GUILD_MEDIA channels) (may not point to an existing or valid message or thread) + bitrate: number?, -- the bitrate (in bits) of the voice channel + user_limit: number?, -- the user limit of the voice channel + rate_limit_per_user: number?, -- amount of seconds a user has to wait before sending another message (0-21600); bots, as well as users with the permission manage_messages or manage_channel, are unaffected + recipients: { UserObject }?, -- the recipients of the DM + icon: string?, -- icon hash of the group DM + owner_id: Snowflake?, -- id of the creator of the group DM or thread + application_id: Snowflake?, -- application id of the group DM creator if it is bot-created + managed: boolean?, -- for group DM channels: whether the channel is managed by an application via the gdm.join OAuth2 scope + parent_id: Snowflake?, -- for guild channels: id of the parent category for a channel (each parent category can contain up to 50 channels), for threads: id of the text channel this thread was created + last_pin_timestamp: string?, -- when the last pinned message was pinned. This may be null in events such as GUILD_CREATE when a message is not pinned. + rtc_region: string?, -- voice region id for the voice channel, automatic when set to null + video_quality_mode: VideoQualityMode?, -- the camera video quality mode of the voice channel, 1 when not present + message_count: number?, -- number of messages (not including the initial message or deleted messages) in a thread. + member_count: number?, -- an approximate count of users in a thread, stops counting at 50 + thread_metadata: ThreadMetadataObject?, -- thread-specific fields not needed by other channels + member: ThreadMemberObject?, -- thread member object for the current user, if they have joined the thread, only included on certain API endpoints + default_auto_archive_duration: number?, -- default duration, copied onto newly created threads, in minutes, threads will stop showing in the channel list after the specified period of inactivity, can be set to: 60, 1440, 4320, 10080 + permissions: string?, -- computed permissions for the invoking user in the channel, including overwrites, only included when part of the resolved data received on a slash command interaction. This does not include implicit permissions, which may need to be checked separately + flags: ChannelFlags?, -- channel flags combined as a bitfield + total_message_sent: number?, -- number of messages ever sent in a thread, it's similar to message_count on message creation, but will not decrement the number when a message is deleted + available_tags: { ForumTagObject }?, -- the set of tags that can be used in a GUILD_FORUM or a GUILD_MEDIA channel + applied_tags: { Snowflake }?, -- the IDs of the set of tags that have been applied to a thread in a GUILD_FORUM or a GUILD_MEDIA channel + default_reaction_emoji: DefaultReactionObject?, -- the emoji to show in the add reaction button on a thread in a GUILD_FORUM or a GUILD_MEDIA channel + default_thread_rate_limit_per_user: number?, -- the initial rate_limit_per_user to set on newly created threads in a channel. this field is copied to the thread at creation time and does not live update. + default_sort_order: SortOrderType?, -- the default sort order type used to order posts in GUILD_FORUM and GUILD_MEDIA channels. Defaults to null, which indicates a preferred sort order hasn't been set by a channel admin + default_forum_layout: ForumLayoutType?, -- the default forum layout view used to display posts in GUILD_FORUM channels. Defaults to 0, which indicates a layout view has not been set by a channel admin +} + +-- https://discord.com/developers/docs/monetization/entitlements#entitlement-object +export type EntitlementObject = { + id: Snowflake, -- ID of the entitlement + sku_id: Snowflake, -- ID of the SKU + application_id: Snowflake, -- ID of the parent application + user_id: Snowflake?, -- ID of the user that is granted access to the entitlement's sku + type: EntitlementType, -- Type of entitlement + deleted: boolean, -- Entitlement was deleted + starts_at: string?, -- Start date at which the entitlement is valid. Not present when using test entitlements. + ends_at: string?, -- Date at which the entitlement is no longer valid. Not present when using test entitlements. + guild_id: Snowflake?, -- ID of the guild that is granted access to the entitlement's sku + consumed: boolean?, -- For consumable items, whether or not the entitlement has been consumed +} + +-- https://discord.com/developers/docs/resources/voice#voice-state-object +export type VoiceStateObject = { + guild_id: Snowflake?, -- the guild id this voice state is for + channel_id: Snowflake?, -- the channel id this user is connected to + user_id: Snowflake, -- the user id this voice state is for + member: GuildMemberObject?, -- the guild member this voice state is for + session_id: string, -- the session id for this voice state + deaf: boolean, -- whether this user is deafened by the server + mute: boolean, -- whether this user is muted by the server + self_deaf: boolean, -- whether this user is locally deafened + self_mute: boolean, -- whether this user is locally muted + self_stream: boolean?, -- whether this user is streaming using "Go Live" + self_video: boolean, -- whether this user's camera is enabled + suppress: boolean, -- whether this user's permission to speak is denied + request_to_speak_timestamp: string?, -- the time at which the user requested to speak +} + +-- https://discord.com/developers/docs/topics/gateway-events#client-status-object +export type ClientStatusObject = { + desktop: string?, -- User's status set for an active desktop (Windows, Linux, Mac) application session + mobile: string?, -- User's status set for an active mobile (iOS, Android) application session + web: string?, -- User's status set for an active web (browser, bot user) application session +} + +-- https://discord.com/developers/docs/topics/gateway-events#presence-update +export type PresenceUpdateObject = { + user: UserObject, -- User whose presence is being updated + guild_id: Snowflake, -- ID of the guild + status: string, -- Either "idle", "dnd", "online", or "offline" + activities: { ActivityObject }, -- User's current activities + client_status: ClientStatusObject, -- User's platform-dependent status +} + +-- https://discord.com/developers/docs/resources/stage-instance#stage-instance-object +export type StageInstanceObject = { + id: Snowflake, -- The id of this Stage instance + guild_id: Snowflake, -- The guild id of the associated Stage channel + channel_id: Snowflake, -- The id of the associated Stage channel + topic: string, -- The topic of the Stage instance (1-120 characters) + privacy_level: PrivacyLevel, -- The privacy level of the Stage instance + discoverable_disabled: boolean, -- Whether or not Stage Discovery is disabled (deprecated) + guild_scheduled_event_id: Snowflake?, -- The id of the scheduled event for this Stage instance +} + +-- https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-entity-metadata +export type GuildScheduledEventEntityMetadata = { + location: string, -- location of the event (1-100 characters) +} + +-- https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object +export type GuildScheduledEventObject = { + id: Snowflake, -- the id of the scheduled event + guild_id: Snowflake, -- the guild id which the scheduled event belongs to + channel_id: Snowflake?, -- the channel id in which the scheduled event will be hosted, or null if scheduled entity type is EXTERNAL + creator_id: Snowflake?, -- the id of the user that created the scheduled event + name: string, -- the name of the scheduled event (1-100 characters) + description: string?, -- the description of the scheduled event (1-1000 characters) + scheduled_start_time: string, -- the time the scheduled event will start + scheduled_end_time: string?, -- the time the scheduled event will end, required if entity_type is EXTERNAL + privacy_level: PrivacyLevel, -- the privacy level of the scheduled event + status: GuildScheduledEventStatus, -- the status of the scheduled event + entity_type: GuildScheduledEventEntityType, -- the type of the scheduled event + entity_id: Snowflake?, -- the id of an entity associated with a guild scheduled event + entity_metadata: GuildScheduledEventEntityMetadata?, -- additional metadata for the guild scheduled event + creator: UserObject?, -- the user that created the scheduled event + user_count: number, -- the number of users subscribed to the scheduled event + image: string?, -- the cover image hash of the scheduled event +} + +-- https://discord.com/developers/docs/resources/guild#integration-account-object +export type IntegrationAccountObject = { + id: string, -- id of the account + name: string, -- name of the account +} + +-- https://discord.com/developers/docs/resources/guild#integration-object +export type IntegrationObject = { + id: Snowflake, -- integration id + name: string, -- integration name + type: IntegrationType, -- integration type (twitch, youtube, discord, or guild_subscription) + enabled: boolean, -- is this integration enabled + syncing: boolean?, -- is this integration syncing + role_id: Snowflake?, -- id that this integration uses for "subscribers" + enable_emoticons: boolean?, -- whether emoticons should be synced for this integration (twitch only currently) + expire_behaviour: IntegrationExpireBehaviours?, -- the behavior of expiring subscribers + expire_grace_period: number?, -- the grace period (in days) before expiring subscribers + user: UserObject?, -- user for this integration + account: IntegrationAccountObject, -- integration account information + synced_at: string?, -- when this integration was last synced + subscriber_count: number?, -- how many subscribers this integration has + revoked: boolean?, -- has this integration been revoked + application: ApplicationObject?, -- The bot/OAuth2 application for discord integrations + scopes: { OAuth2Scopes }?, -- the scopes the application has been authorized for +} + +-- https://discord.com/developers/docs/resources/channel#channel-mention-object +export type ChannelMentionObject = { + id: Snowflake, -- id of the channel + guild_id: Snowflake, -- id of the guild containing the channel + type: ChannelType, -- the type of channel + name: string, -- the name of the channel +} + +-- https://discord.com/developers/docs/resources/channel#attachment-object +export type AttachmentObject = { + id: Snowflake, -- attachment id + filename: string, -- name of file attached + title: string?, -- the title of the file + description: string?, -- description for the file (max 1024 characters) + content_type: string?, -- the attachment's media type + size: number, -- size of file in bytes + url: string, -- source url of file + proxy_url: string, -- a proxied url of file + height: number?, -- height of file (if image) + width: number?, -- width of file (if image) + ephemeral: boolean?, -- whether this attachment is ephemeral + duration_secs: number?, -- the duration of the audio file (currently for voice messages) + waveform: string?, -- base64 encoded bytearray representing a sampled waveform (currently for voice messages) + flags: number, -- attachment flags combined as a bitfield +} + +-- https://discord.com/developers/docs/resources/channel#embed-object-embed-footer-structure +export type EmbedFooterObject = { + text: string, -- footer text + icon_url: string?, -- url of footer icon (only supports http(s) and attachments) + proxy_icon_url: string?, -- a proxied url of footer icon +} + +-- https://discord.com/developers/docs/resources/channel#embed-object-embed-image-structure +export type EmbedImageObject = { + url: string, -- source url of image (only supports http(s) and attachments) + proxy_url: string?, -- a proxied url of the image + height: number?, -- height of image + width: number?, -- width of image +} + +-- https://discord.com/developers/docs/resources/channel#embed-object-embed-provider-structure +export type EmbedProviderObject = { + name: string?, -- name of provider + url: string?, -- url of provider +} + +-- https://discord.com/developers/docs/resources/channel#embed-object-embed-author-structure +export type EmbedAuthorObject = { + name: string, -- name of author + url: string?, -- url of author (only supports http(s)) + icon_url: string?, -- url of author icon (only supports http(s) and attachments) + proxy_icon_url: string?, -- a proxied url of author icon +} + +-- https://discord.com/developers/docs/resources/channel#embed-object-embed-field-structure +export type EmbedFieldObject = { + name: string, -- name of the field + value: string, -- value of the field + inline: boolean?, -- whether or not this field should display inline +} + +-- https://discord.com/developers/docs/resources/channel#embed-object-embed-thumbnail-structure +export type EmbedThumbnailObject = { + url: string, -- source url of thumbnail (only supports http(s) and attachments) + proxy_url: string?, -- a proxied url of the thumbnail + height: number?, -- height of thumbnail + width: number?, -- width of thumbnail +} + +-- https://discord.com/developers/docs/resources/channel#embed-object-embed-video-structure +export type EmbedVideoObject = { + url: string, -- source url of video + proxy_url: string?, -- a proxied url of the video + height: number?, -- height of video + width: number?, -- width of video +} + +-- https://discord.com/developers/docs/resources/channel#embed-object +export type EmbedObject = { + title: string?, -- title of embed + type: EmbedType, -- type of embed (always "rich" for webhook embeds) + description: string?, -- description of embed + url: string?, -- url of embed + timestamp: string, -- timestamp of embed content + color: number?, -- color code of the embed + footer: EmbedFooterObject?, -- footer information + image: EmbedImageObject?, -- image information + thumbnail: EmbedThumbnailObject?, -- thumbnail information + video: EmbedVideoObject?, -- video information + provider: EmbedProviderObject?, -- provider information + author: EmbedAuthorObject?, -- author information + fields: { EmbedFieldObject }?, -- fields information, max of 25 +} + +-- https://discord.com/developers/docs/resources/channel#reaction-count-details-object +export type ReactionCountDetailsObject = { + burst: number, -- Count of super reactions + normal: number, -- Count of normal reactions +} + +-- https://discord.com/developers/docs/resources/channel#reaction-object +export type ReactionObject = { + count: number, -- Total number of times this emoji has been used to react (including super reacts) + count_details: ReactionCountDetailsObject, -- Reaction count details object + me: boolean, -- Whether the current user reacted using this emoji + me_burst: boolean, -- Whether the current user super-reacted using this emoji + emoji: EmojiObject, -- emoji information + burst_colors: { string }, -- HEX colors used for super reaction +} + +-- https://discord.com/developers/docs/resources/channel#message-object-message-activity-structure +export type MessageActivityObject = { + type: MessageActivityType, -- type of message activity + party_id: string, -- party_id from a Rich Presence event +} + +-- https://discord.com/developers/docs/resources/channel#message-reference-object-message-reference-structure +export type MessageReferenceObject = { + type: MessageReferenceType?, -- type of reference. + message_id: Snowflake?, -- id of the originating message + channel_id: Snowflake?, -- id of the originating message's channel + guild_id: Snowflake?, -- id of the originating message's guild + fail_if_not_exists: boolean?, -- when sending, whether to error if the referenced message doesn't exist instead of sending as a normal (non-reply) message, default true +} + +-- https://discord.com/developers/docs/resources/channel#message-interaction-metadata-object-message-interaction-metadata-structure +export type MessageInteractionMetadatObject = { + id: Snowflake, -- ID of the interaction + type: InteractionType, -- Type of interaction + user: UserObject, -- User who triggered the interaction + authorizing_integration_owners: { [ApplicationIntegrationType]: Snowflake }, -- IDs for installation context(s) related to an interaction. Details in Authorizing Integration Owners Object + original_response_message_id: Snowflake?, -- ID of the original response message, present only on follow-up messages + interacted_message_id: Snowflake?, -- ID of the message that contained interactive component, present only on messages created from component interactions + triggering_interaction_metadata: MessageInteractionMetadatObject, -- Metadata for the interaction that was used to open the modal, present only on modal submit interactions +} + +-- https://discord.com/developers/docs/interactions/receiving-and-responding#message-interaction-object-message-interaction-structure +export type MessageInteractionObject = { + id: string, -- ID of the interaction + type: InteractionType, -- Type of interaction + name: string, -- Name of the application command, including subcommands and subcommand groups + user: UserObject, -- User who invoked the interaction + member: GuildMemberObject?, -- Member who invoked the interaction in the guild +} + +-- https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-option-structure +export type SelectOptionObject = { + label: string, -- User-facing name of the option; max 100 characters + value: string, -- Dev-defined value of the option; max 100 characters + description: string?, -- Additional description of the option; max 100 characters + emoji: EmojiObject?, -- id, name, and animated + default: boolean, -- Will show this option as selected by default +} + +-- https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-default-value-structure +export type SelectDefaultValueObject = { + id: Snowflake, -- ID of a user, role, or channel + type: string, -- Type of value that id represents. Either "user", "role", or "channel" +} + +-- https://discord.com/developers/docs/interactions/message-components#action-rows +export type ActionRowComponentObject = { + type: number, -- 1 for an action row. + components: { ComponentObjects }, -- range of component objects +} + +-- https://discord.com/developers/docs/interactions/message-components#button-object +export type ButtonComponentObject = { + type: number, -- 2 for a button + style: ButtonStyle, -- A button style + label: string?, -- Text that appears on the button; max 80 characters + emoji: EmojiObject?, -- name, id, and animated + custom_id: string?, -- Developer-defined identifier for the button; max 100 characters + sku_id: string?, -- Identifier for a purchasable SKU, only available when using premium-style buttons + url: string?, -- URL for link-style buttons + disabled: boolean?, -- Whether the button is disabled (defaults to false) +} + +-- https://discord.com/developers/docs/interactions/message-components#select-menu-object +export type SelectMenuComponentObject = { + type: number, -- Type of select menu component (text: 3, user: 5, role: 6, mentionable: 7, channels: 8) + custom_id: string, -- ID for the select menu; max 100 characters + options: { SelectOptionObject }?, -- Specified choices in a select menu (only required and available for string selects (type 3); max 25 + channel_types: { ChannelType }?, -- List of channel types to include in the channel select component (type 8) + placeholder: string?, -- Placeholder text if nothing is selected; max 150 characters + default_values: { SelectDefaultValueObject }?, -- List of default values for auto-populated select menu components; number of default values must be in the range defined by min_values and max_values + min_values: number?, -- Minimum number of items that must be chosen (defaults to 1); min 0, max 25 + max_values: number?, -- Maximum number of items that can be chosen (defaults to 1); max 25 + disabled: boolean?, -- Whether select menu is disabled (defaults to false) +} + +-- https://discord.com/developers/docs/interactions/message-components#select-menu-object +export type TextInputComponentObject = { + type: number, -- 4 for a text input + custom_id: string, -- Developer-defined identifier for the input; max 100 characters + style: TextInputStyles, -- The Text Input Style + label: string, -- Label for this component; max 45 characters + min_length: number?, -- Minimum input length for a text input; min 0, max 4000 + max_length: number?, -- Maximum input length for a text input; min 1, max 4000 + required: boolean?, -- Whether this component is required to be filled (defaults to true) + value: string?, -- Pre-filled value for this component; max 4000 characters + placeholder: string?, -- Custom placeholder text if the input is empty; max 100 characters +} + +-- https://discord.com/developers/docs/interactions/message-components#message-components +export type ComponentObjects = + ActionRowComponentObject + | ButtonComponentObject + | SelectMenuComponentObject + | TextInputComponentObject + +-- https://discord.com/developers/docs/resources/sticker#sticker-item-object +export type SitckerItemObject = { + id: Snowflake, -- id of the sticker + name: string, -- name of the sticker + format_type: StickerFormatType, -- type of sticker format +} + +-- https://discord.com/developers/docs/resources/channel#role-subscription-data-object +export type RoleSubscriptionDataObject = { + role_subscription_listing_id: Snowflake, -- the id of the sku and listing that the user is subscribed to + tier_name: string, -- the name of the tier that the user is subscribed to + total_months_subscribed: number, -- the cumulative number of months that the user has been subscribed for + is_renewal: boolean, -- whether this notification is for a renewal rather than a new purchase +} + +-- https://discord.com/developers/docs/resources/poll#poll-media-object-poll-media-object-structure +export type PollMediaObject = { + text: string?, -- The text of the field + emoji: EmojiObject?, -- The emoji of the field +} + +-- https://discord.com/developers/docs/resources/poll#poll-answer-object-poll-answer-object-structure +export type PollAnswerObject = { + answer_id: number, -- The ID of the answer + poll_media: PollMediaObject, -- The data of the answer +} + +-- https://discord.com/developers/docs/resources/poll#poll-results-object-poll-answer-count-object-structure +export type PollAnswerCountObject = { + id: number, -- The answer_id + count: number, -- The number of votes for this answer + me_voted: boolean, -- Whether the current user voted for this answer +} + +-- https://discord.com/developers/docs/resources/poll#poll-results-object-poll-results-object-structure +export type PollResultObject = { + is_finalized: boolean, -- Whether the votes have been precisely counted + answer_counts: { PollAnswerCountObject }, -- The counts for each answer +} + +-- https://discord.com/developers/docs/resources/poll#poll-object +export type PollObject = { + question: PollMediaObject, -- The question of the poll. Only text is supported. + answers: { PollAnswerObject }, -- Each of the answers available in the poll. + expiry: string, -- The time when the poll ends. + allow_multiselect: boolean, -- Whether a user can select multiple answers + layout_type: PollLayoutType, -- The layout type of the poll + results: PollResultObject, -- The results of the poll +} + +-- https://discord.com/developers/docs/resources/channel#message-call-object +export type MessageCallObject = { + participants: { UserObject }, -- array of user object ids that participated in the call + ended_timestamp: string, -- time when call ended +} + +-- https://discord.com/developers/docs/resources/channel#message-object +export type MessageObject = { + id: Snowflake?, -- id of the message + channel_id: Snowflake?, -- id of the channel the message was sent in + author: UserObject?, -- the author of this message (not guaranteed to be a valid user, see below) + content: string?, -- contents of the message + timestamp: string?, -- when this message was sent + edited_timestamp: string?, -- when this message was edited (or null if never) + tts: boolean?, -- whether this was a TTS message + mention_everyone: boolean?, -- whether this message mentions everyone + mentions: { UserObject }?, -- users specifically mentioned in the message + mention_roles: { GuildRoleObject }?, -- roles specifically mentioned in this message + mention_channels: { ChannelMentionObject }?, -- channels specifically mentioned in this message + attachments: { AttachmentObject }?, -- any attached files + embeds: { EmbedObject }?, -- any embedded content + reactions: { ReactionObject }?, -- reactions to the message + nonce: string?, -- used for validating a message was sent + pinned: boolean?, -- whether this message is pinned + webhook_id: Snowflake?, -- if the message is generated by a webhook, this is the webhook's id + type: MessageType?, -- type of message + activity: MessageActivityObject?, -- sent with Rich Presence-related chat embeds + application: ApplicationObject?, -- sent with Rich Presence-related chat embeds + application_id: Snowflake?, -- if the message is an Interaction or application-owned webhook, this is the id of the application + message_reference: MessageReferenceObject?, -- data showing the source of a crosspost, channel follow add, pin, or reply message + flags: number?, -- message flags combined as a bitfield + referenced_message: MessageObject?, -- the message associated with the message_reference + interaction_metadata: MessageInteractionMetadatObject?, -- In preview. Sent if the message is sent as a result of an interaction + interaction: MessageInteractionObject?, -- Deprecated in favor of interaction_metadata; sent if the message is a response to an interaction + thread: ChannelObject?, -- the thread that was started from this message, includes thread member object + components: { ComponentObjects }?, -- sent if the message contains components like buttons, action rows, or other interactive components + sticker_items: { SitckerItemObject }?, -- sent if the message contains stickers + stickers: { StickerObject }?, -- Deprecated the stickers sent with the message + position: number?, -- A generally increasing integer (there may be gaps or duplicates) that represents the approximate position of the message in a thread, it can be used to estimate the relative position of the message in a thread in company with total_message_sent on parent thread + role_subscription_data: RoleSubscriptionDataObject?, -- data of the role subscription purchase or renewal that prompted this ROLE_SUBSCRIPTION_PURCHASE message + resolved: ResolvedDataStructure?, -- data for users, members, channels, and roles in the message's auto-populated select menus + poll: PollObject?, -- the poll associated with the message + call: MessageCallObject?, -- the call associated with the message +} + +-- https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-resolved-data-structure +export type ResolvedDataStructure = { + users: { UserObject }?, -- the ids and User objects + members: { GuildMemberObject }?, -- the ids and partial Member objects + roles: { GuildRoleObject }?, -- the ids and Role objects + channels: { ChannelObject }?, -- the ids and partial Channel objects + messages: { MessageObject }?, -- the ids and partial Message objects + attachments: { AttachmentObject }?, -- the ids and attachment objects +} + +-- https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-application-command-interaction-data-option-structure] +export type ApplicationCommandInteractionDataOptionObject = { + name: string, -- Name of the parameter + type: ApplicationCommandOptionType, -- Value of application command option type + value: (string | number | boolean)?, -- Value of the option resulting from user input + options: { ApplicationCommandInteractionDataOptionObject }, -- Present if this option is a group or subcommand + focused: boolean, -- true if this option is the currently focused option for autocomplete +} + +-- https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-data +export type InteractionDataObject = { + id: Snowflake, -- the ID of the invoked command + name: string, -- the name of the invoked command + type: number, -- the type of the invoked command + resolved: ResolvedDataStructure?, -- converted users + roles + channels + attachments + options: { ApplicationCommandInteractionDataOptionObject }?, -- the params + values from the user + guild_id: Snowflake?, -- the id of the guild the command is registered to + target_id: Snowflake?, -- id of the user or message targeted by a user or message command +} + +-- https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-structure +export type InteractionObject = { + id: Snowflake, -- ID of the interaction + application_id: Snowflake, -- ID of the application this interaction is for + type: InteractionType, -- Type of interaction + data: InteractionDataObject?, -- Interaction data payload + guild: GuildObject?, -- Guild that the interaction was sent from + guild_id: Snowflake?, -- Guild that the interaction was sent from + channel: ChannelObject?, -- Channel that the interaction was sent from + channel_id: Snowflake?, -- Channel that the interaction was sent from + member: GuildMemberObject?, -- Guild member data for the invoking user, including permissions + user: UserObject?, -- User object for the invoking user, if invoked in a DM + token: string, -- Continuation token for responding to the interaction + version: number, -- Read-only property, always 1 + message: MessageObject?, -- For components, the message they were attached to + app_permissions: string, -- Bitwise set of permissions the app has in the source location of the interaction + locale: LanguageLocales?, -- Selected language of the invoking user + guild_locale: LanguageLocales?, -- Guild's preferred locale, if invoked in a guild + entitlements: { EntitlementObject }, -- For monetized apps, any entitlements for the invoking user, representing access to premium SKUs + authorizing_integration_owners: { IntegrationType }, -- Mapping of installation contexts that the interaction was authorized for to related user or guild IDs. See Authorizing Integration Owners Object for details + context: InteractionContextType, -- Context where the interaction was triggered from +} + +-- https://discord.com/developers/docs/resources/application-role-connection-metadata#application-role-connection-metadata-object +export type ApplicationRoleConnectionMetadataObject = { + type: ApplicationRoleConnectionMetadataType, -- type of metadata value + key: string, -- dictionary key for the metadata field (must be a-z, 0-9, or _ characters; 1-50 characters) + name: string, -- name of the metadata field (1-100 characters) + name_localizations: { [LanguageLocales]: string }?, -- translations of the name + description: string, -- description of the metadata field (1-200 characters) + description_localizations: { [LanguageLocales]: string }?, -- translations of the description +} + +-- https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-choice-structure +export type ApplicationCommandOptionChoiceObject = { + name: string, -- 1-100 character choice name + name_localizations: { [LanguageLocales]: string }?, -- Localization dictionary for the name field. Values follow the same restrictions as name + value: string | number, -- Value for the choice, up to 100 characters if string +} + +-- https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-structure +export type ApplicationCommandOptionObject = { + type: ApplicationCommandOptionType, -- Type of option + name: string, -- 1-32 character name + name_localizations: { [LanguageLocales]: string }?, -- Localization dictionary for the name field. Values follow the same restrictions as name + description: string?, -- 1-100 character description + description_localizations: { [LanguageLocales]: string }?, -- Localization dictionary for the description field. Values follow the same restrictions as description + required: boolean?, -- Whether the parameter is required or optional, default false + choices: { ApplicationCommandOptionChoiceObject }, -- Choices for the user to pick from, max 25 + options: { ApplicationCommandOptionObject }?, -- If the option is a subcommand or subcommand group type, these nested options will be the parameters or subcommands respectively; up to 25 + channel_types: { ChannelType }?, -- The channels shown will be restricted to these types + min_value: number?, -- The minimum value permitted + max_value: number?, -- The maximum value permitted + min_length: number?, -- The minimum allowed length (minimum of 0, maximum of 6000) + max_length: number?, -- The maximum allowed length (minimum of 1, maximum of 6000) + autocomplete: boolean?, -- If autocomplete interactions are enabled for this option +} + +-- https://discord.com/developers/docs/interactions/application-commands#application-command-object +export type ApplicationCommandObject = { + id: Snowflake?, -- Unique ID of command + type: ApplicationCommandType?, -- Type of command, defaults to 1 + application_id: Snowflake?, -- ID of the parent application + guild_id: Snowflake?, -- Guild ID of the command, if not global + name: string, -- Name of command, 1-32 characters + name_localizations: { [LanguageLocales]: string }?, -- Localization dictionary for name field. Values follow the same restrictions as name + description: string, -- Description for CHAT_INPUT commands, 1-100 characters. Empty string for USER and MESSAGE commands + description_localizations: { [LanguageLocales]: string }?, -- Localization dictionary for description field. Values follow the same restrictions as description + options: { ApplicationCommandOptionObject }?, -- Parameters for the command, max of 25 + default_member_permissions: string?, -- Set of permissions represented as a bit set + dm_permission: string?, -- Deprecated (use contexts instead); Indicates whether the command is available in DMs with the app, only for globally-scoped commands. By default, commands are visible. + default_permission: boolean?, -- Not recommended for use as field will soon be deprecated. Indicates whether the command is enabled by default when the app is added to a guild, defaults to true + nsfw: boolean?, -- Indicates whether the command is age-restricted, defaults to false + integration_types: { IntegrationType }?, -- Installation contexts where the command is available, only for globally-scoped commands. Defaults to your app's configured contexts + contexts: { InteractionContextType }?, -- Interaction context(s) where the command can be used, only for globally-scoped commands. By default, all interaction context types included for new commands. + version: Snowflake?, -- Autoincrementing version identifier updated during substantial record changes +} + +-- https://discord.com/developers/docs/resources/audit-log#audit-log-change-object +export type AuditLogChangeObject = { + new_value: any?, -- New value of the key + old_value: any?, -- Old value of the key + key: string, -- Name of the changed entity, with a few exceptions +} + +-- https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object-optional-audit-entry-info +export type OptionalAuditEntryInfoObject = { + application_id: Snowflake, -- ID of the app whose permissions were targeted + auto_moderation_rule_name: string, -- Name of the Auto Moderation rule that was triggered + auto_moderation_rule_trigger_type: AutomoderationRuleTriggerType, -- Trigger type of the Auto Moderation rule that was triggered + channel_id: Snowflake, -- Channel in which the entities were targeted + count: string, -- Number of entities that were targeted + delete_member_days: string, -- Number of days after which inactive members were kicked + id: Snowflake, -- ID of the overwritten entity + members_removed: string, -- Number of members removed by the prune + message_id: Snowflake, -- ID of the message that was targeted + role_name: string, -- Name of the role if type is "0" (not present if type is "1") + type: string, -- Type of overwritten entity - role ("0") or member ("1") + integration_type: IntegrationType, -- The type of integration which performed the action +} + +-- https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object +export type AuditLogEntryObject = { + target_id: string?, -- ID of the affected entity (webhook, user, role, etc.) + changes: { AuditLogChangeObject }?, -- Changes made to the target_id + user_id: Snowflake?, -- User or app that made the changes + id: Snowflake, -- ID of the entry + action_type: AuditLogEventType, -- Type of action that occurred + options: { OptionalAuditEntryInfoObject }?, -- Additional info for certain event types + reason: string?, -- Reason for the change (1-512 characters) +} + +-- https://discord.com/developers/docs/resources/webhook#webhook-object +export type WebhookObject = { + id: Snowflake, -- the id of the webhook + type: WebhookType, -- the type of the webhook + guild_id: Snowflake?, -- the guild id this webhook is for, if any + channel_id: Snowflake?, -- the channel id this webhook is for, if any + user: UserObject?, -- the user this webhook was created by (not returned when getting a webhook with its token) + name: string?, -- the default name of the webhook + avatar: string?, -- the default user avatar hash of the webhook + token: string?, -- the secure token of the webhook (returned for Incoming Webhooks) + application_id: Snowflake?, -- the bot/OAuth2 application that created this webhook + source_guild: GuildObject?, -- the guild of the channel that this webhook is following (returned for Channel Follower Webhooks) + source_channel: ChannelObject?, -- the channel that this webhook is following (returned for Channel Follower Webhooks) + url: string?, -- the url used for executing the webhook (returned by the webhooks OAuth2 flow) +} + +-- https://discord.com/developers/docs/resources/audit-log#audit-log-object +export type AuditLogObject = { + application_commands: { ApplicationCommandObject }, -- List of application commands referenced in the audit log + audit_log_entries: { AuditLogEntryObject }, -- List of audit log entries, sorted from most to least recent + auto_moderation_rules: { AutomoderationRuleObject }, -- List of auto moderation rules referenced in the audit log + guild_scheduled_events: { GuildScheduledEventObject }, -- List of guild scheduled events referenced in the audit log + integrations: { IntegrationObject }, -- List of partial integration objects + threads: { ChannelObject }, -- List of threads referenced in the audit log* + users: { UserObject }, -- List of users referenced in the audit log + webhooks: { WebhookObject }, -- List of webhooks referenced in the audit log +} + +-- https://discord.com/developers/docs/resources/invite#invite-object +export type InviteObject = { + type: InviteTypes, -- the type of invite + code: string, -- the invite code (unique ID) + guild: GuildObject?, -- the guild this invite is for + channel: ChannelObject?, -- the channel this invite is for + inviter: UserObject?, -- the user who created the invite + target_type: InviteTargetTypes, -- the type of target for this voice channel invite + target_user: UserObject?, -- the user whose stream to display for this voice channel stream invite + target_application: ApplicationObject?, -- the embedded application to open for this voice channel embedded application invite + approximate_presence_count: number?, -- approximate count of online members, returned from the GET /invites/ endpoint when with_counts is true + approximate_member_count: number?, -- approximate count of total members, returned from the GET /invites/ endpoint when with_counts is true + expires_at: string?, -- the expiration date of this invite, returned from the GET /invites/ endpoint when with_expiration is true + stage_instance: StageInstanceObject?, -- stage instance data if there is a public Stage instance in the Stage channel this invite is for (deprecated) + guild_scheduled_event: GuildScheduledEventObject?, -- guild scheduled event data, only included if guild_scheduled_event_id contains a valid guild scheduled event id +} + +-- https://discord.com/developers/docs/resources/invite#invite-metadata-object +export type InviteMetadataObject = { + uses: number, -- number of times this invite has been used + max_uses: number, -- max number of times this invite can be used + max_age: number, -- duration (in seconds) after which the invite expires + temporary: boolean, -- whether this invite only grants temporary membership + created_at: string, -- when this invite was created +} + +-- https://discord.com/developers/docs/resources/channel#followed-channel-object +export type FollowedChannelObject = { + channel_id: Snowflake, -- source channel id + webhook_id: Snowflake, -- created target webhook id +} + +-- https://discord.com/developers/docs/resources/guild#guild-preview-object +export type GuildPreviewObject = { + id: Snowflake, -- guild id + name: string, -- guild name (2-100 characters) + icon: string?, -- icon hash + splash: string?, -- splash hash + discovery_splash: string?, -- discovery splash hash + emojis: { EmojiObject }, -- custom guild emojis + features: { GuildFeature }, -- enabled guild features + approximate_member_count: number, -- approximate number of members in this guild + approximate_presence_count: number, -- approximate number of online members in this guild + description: string?, -- the description for the guild + stickers: { StickerObject }, -- custom guild stickers +} + +-- https://discord.com/developers/docs/resources/guild#ban-object +export type BanObject = { + reason: string?, -- the reason for the ban + user: UserObject, -- the banned user= +} + +-- https://discord.com/developers/docs/resources/voice#voice-region-object +export type VoiceRegionObject = { + id: string, -- unique ID for the region + name: string, -- name of the region + optimal: boolean, -- true for a single server that is closest to the current user's client + deprecated: boolean, -- whether this is a deprecated voice region (avoid switching to these) + custom: boolean, -- whether this is a custom voice region (used for events/etc) +} + +-- https://discord.com/developers/docs/resources/guild#guild-widget-settings-object +export type GuildWidgetSettingsObject = { + enabled: boolean, -- whether the widget is enabled + channel_id: Snowflake?, -- the widget channel id +} + +-- https://discord.com/developers/docs/resources/guild#guild-widget-object +export type GuildWidgetObject = { + id: Snowflake, -- guild id + name: string, -- guild name (2-100 characters) + instant_invite: string?, -- instant invite for the guilds specified widget invite channel + channels: { ChannelObject }, -- voice and stage channels which are accessible by @everyone + members: { UserObject }, -- special widget user objects that includes users presence (Limit 100) + presence_count: number, -- number of online members in this guild +} + +-- https://discord.com/developers/docs/resources/guild#guild-onboarding-object-prompt-option-structure +export type PromptOptionObject = { + id: Snowflake, -- ID of the prompt option + channel_ids: { Snowflake }, -- IDs for channels a member is added to when the option is selected + role_ids: { Snowflake }, -- IDs for roles assigned to a member when the option is selected + emoji: EmojiObject?, -- Emoji of the option (see below) + emoji_id: Snowflake, -- Emoji ID of the option (see below) + emoji_name: string?, -- Emoji name of the option (see below) + emoji_animated: boolean?, -- Whether the emoji is animated (see below) + title: string, -- Title of the option + description: string?, -- Description of the option +} + +-- https://discord.com/developers/docs/resources/guild#guild-onboarding-object-onboarding-prompt-structure +export type OnboardingPromptObject = { + id: Snowflake, -- ID of the prompt + type: PromptTypes, -- Type of prompt + options: { PromptOptionObject }, -- Options available within the prompt + title: string, -- Title of the prompt + single_select: boolean, -- Indicates whether users are limited to selecting one option for the prompt + required: boolean, -- Indicates whether the prompt is required before a user completes the onboarding flow + in_onboarding: boolean, -- Indicates whether the prompt is present in the onboarding flow. If false, the prompt will only appear in the Channels & Roles tab +} + +-- https://discord.com/developers/docs/resources/guild#guild-onboarding-object +export type GuildOnboardingObject = { + guild_id: Snowflake, -- ID of the guild this onboarding is part of + prompts: { OnboardingPromptObject }, -- Prompts shown during onboarding and in customize community + default_channel_ids: { Snowflake }, -- Channel IDs that members get opted into automatically + enabled: boolean, -- Whether onboarding is enabled in the guild + mode: OnboardingMode, -- Current mode of onboarding +} + +-- https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-user-object +export type GuildScheduledEventUserObject = { + guild_scheduled_event_id: Snowflake, -- the scheduled event id which the user subscribed to + user: UserObject, -- user which subscribed to an event + member: GuildMemberObject?, -- guild member data for this user for the guild which this event belongs to, if any +} + +-- https://discord.com/developers/docs/resources/guild-template#guild-template-object +export type GuildTemplateObject = { + code: string, -- the template code (unique ID) + name: string, -- template name + description: string?, -- the description for the template + usage_count: number, -- number of times this template has been used + creator_id: Snowflake, -- the ID of the user who created the template + creator: UserObject, -- the user who created the template + created_at: string, -- when this template was created + updated_at: string, -- when this template was last synced to the source guild + source_guild_id: Snowflake, -- the ID of the guild this template is based on + serialized_source_guild: GuildObject, -- the guild snapshot this template contains + is_dirty: boolean?, -- whether the template has unsynced changes +} + +-- https://discord.com/developers/docs/resources/sticker#sticker-pack-object +export type StickerPackObject = { + id: Snowflake, -- id of the sticker pack + stickers: { StickerObject }, -- the stickers in the pack + name: string, -- name of the sticker pack + sku_id: Snowflake, -- id of the pack's SKU + cover_sticker_id: Snowflake?, -- id of a sticker in the pack which is shown as the pack's icon + description: string, -- description of the sticker pack + banner_asset_id: Snowflake?, -- id of the sticker pack's banner image +} + +-- https://discord.com/developers/docs/resources/user#connection-object +export type ConnectionObject = { + id: string, -- id of the connection account + name: string, -- the username of the connection account + type: ConnectionObjectServices, -- the service of this connection + revoked: boolean?, -- whether the connection is revoked + integrations: { IntegrationObject }?, -- an array of partial server integrations + verified: boolean, -- whether the connection is verified + friend_sync: boolean, -- whether friend sync is enabled for this connection + show_Activity: boolean, -- whether activities related to this connection will be shown in presence updates + two_way_link: boolean, -- whether this connection has a corresponding third party OAuth2 token + visibility: ConnectionVisibilityTypes, -- visibility of this connection +} + +-- https://discord.com/developers/docs/resources/user#application-role-connection-object +export type ApplicationRoleConnectionObject = { + platform_name: string?, -- the vanity name of the platform a bot has connected (max 50 characters) + platform_username: string?, -- the username on the platform a bot has connected (max 100 characters) + metadata: ApplicationRoleConnectionMetadataObject, -- object mapping application role connection metadata keys to their string-ified value (max 100 characters) for the user on the platform a bot has connected +} + +-- https://discord.com/developers/docs/topics/gateway#session-start-limit-object +export type SessionStartLimitObject = { + total: number, -- Total number of session starts the current user is allowed + remaining: number, -- Remaining number of session starts the current user is allowed + reset_after: number, -- Number of milliseconds after which the limit resets + max_concurrency: number, -- Number of identify requests allowed per 5 seconds +} + +-- https://discord.com/developers/docs/resources/channel#allowed-mentions-object +export type AllowedMentionObject = { + parse: { AllowedMentionTypes }?, -- An array of allowed mention types to parse from the content. + users: { string }?, -- Array of role_ids to mention (Max size of 100) + roles: { string }?, -- Array of user_ids to mention (Max size of 100) + replied_user: boolean?, -- For replies, whether to mention the author of the message being replied to (default false) +} + +-- https://discord.com/developers/docs/resources/channel#start-thread-in-forum-or-media-channel-forum-and-media-thread-message-params-object +export type ForumAndMediaThreadMessageObject = { + content: string?, -- Message contents (up to 2000 characters) + embeds: { EmbedObject }?, -- Up to 10 rich embeds (up to 6000 characters) + allowed_mentions: AllowedMentionObject?, -- Allowed mentions for the message + components: { ComponentObjects }?, -- Components to include with the message + sticker_ids: { Snowflake }?, -- IDs of up to 3 stickers in the server to send in the message + attachments: { AttachmentObject }?, -- Attachment objects with filename and description. See Uploading Files + flags: number?, -- Message flags combined as a bitfield (only SUPPRESS_EMBEDS and SUPPRESS_NOTIFICATIONS can be set) +} + +-- https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-response-object-interaction-callback-data-structure +export type InteractionCallbackAutocompleteObject = { + choices: { ApplicationCommandOptionChoiceObject }?, -- autocomplete choices (max of 25 choices) +} + +-- https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-response-object-interaction-callback-data-structure +export type InteractionCallbackModalObject = { + custom_id: string?, -- Developer-defined identifier for the modal, max 100 characters + title: string?, -- Title of the popup modal, max 45 characters + -- components: {} -- Between 1 and 5 (inclusive) components that make up the modal +} + +-- https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-response-object-interaction-callback-type +export type InteractionResponseObject = { + type: InteractionCallbackType, -- the type of response + data: (Partial | InteractionCallbackAutocompleteObject | InteractionCallbackModalObject)?, -- an optional response message +} + +return "DISCORD_V10_PAYLOAD_TYPES" diff --git a/packages/api-types/src/application.luau b/packages/api-types/src/application.luau new file mode 100644 index 0000000..b1c7773 --- /dev/null +++ b/packages/api-types/src/application.luau @@ -0,0 +1,51 @@ +local reflect = require("@utils/table/reflect") + +local MembershipState = table.freeze(reflect({ + Invited = 1, + Accepted = 2, +})) + +local IntegrationTypesConfig = table.freeze(reflect({ + GuildInstall = 0, + UserInstall = 1, +})) + +export type MembershipState = "Invited" | "Accepted" +export type TeamRole = "Owner" | "Admin" | "Developer" | "ReadOnly" + +export type IntegrationTypesConfig = "GuildInstall" | "UserInstall" + +export type OAuth2Scopes = + "activities.read" + | "activities.write" + | "applications.builds.read" + | "applications.builds.upload" + | "applications.commands" + | "applications.commands.update" + | "applications.commands.permissions.update" + | "applications.entitlements" + | "applications.store.update" + | "bot" + | "connections" + | "dm_channels.read" + | "email" + | "gdm.join" + | "guilds" + | "guilds.join" + | "guilds.members.read" + | "identify" + | "messages.read" + | "relationships.read" + | "role_connections.write" + | "rpc" + | "rpc.activities.write" + | "rpc.notifications.read" + | "rpc.voice.read" + | "rpc.voice.write" + | "voice" + | "webhook.incoming" + +return { + MembershipState = MembershipState, + IntegrationTypesConfig = IntegrationTypesConfig, +} diff --git a/packages/api-types/src/channel.luau b/packages/api-types/src/channel.luau new file mode 100644 index 0000000..1615222 --- /dev/null +++ b/packages/api-types/src/channel.luau @@ -0,0 +1,67 @@ +local reflect = require("@utils/table/reflect") + +local ChannelTypes = table.freeze(reflect({ + GuildText = 0, + DM = 1, + GuildVoice = 2, + GroupDM = 3, + GuildCategory = 4, + GuildAnnouncement = 5, + AnnouncementThread = 10, + PublicThread = 11, + PrivateThread = 12, + GuildStageVoice = 13, + GuildDirectory = 14, + GuildForum = 15, + GuildMedia = 16, +})) + +local MediaSortOrder = table.freeze(reflect({ + LatestActivity = 0, + CreationDate = 1, +})) + +local ForumSortOrder = table.freeze(reflect({ + LatestActivity = 0, + CreationDate = 1, +})) + +local ForumLayout = table.freeze(reflect({ + NotSet = 0, + ListView = 1, + GalleryView = 2, +})) + +local VideoQualityMode = table.freeze(reflect({ + Auto = 1, + Full = 2, +})) + +export type ChannelType = + "GuildText" + | "DM" + | "GuildVoice" + | "GroupDM" + | "GuildCategory" + | "GuildAnnouncement" + | "AnnouncementThread" + | "PublicThread" + | "PrivateThread" + | "GuildStageVoice" + | "GuildDirectory" + | "GuildForum" + | "GuildMedia" + +export type MediaSortOrder = "LatestActivity" | "CreationDate" +export type ForumSortOrder = "LatestActivity" | "CreationDate" +export type ForumLayout = "NotSet" | "ListView" | "GalleryView" + +export type VideoQualityMode = "Auto" | "Full" + +return { + ChannelTypes = ChannelTypes, + MediaSortOrder = MediaSortOrder, + ForumSortOrder = ForumSortOrder, + ForumLayout = ForumLayout, + VideoQualityMode = VideoQualityMode, +} diff --git a/packages/api-types/src/embed.luau b/packages/api-types/src/embed.luau new file mode 100644 index 0000000..c1660f2 --- /dev/null +++ b/packages/api-types/src/embed.luau @@ -0,0 +1,17 @@ +local reflect = require("@utils/table/reflect") + +local EmbedType = table.freeze(reflect({ + Rich = "rich", + Image = "image", + Video = "video", + Gifv = "gifv", + Article = "article", + Link = "link", + PollResult = "poll_result", +})) + +export type EmbedType = "Rich" | "Image" | "Video" | "Gifv" | "Article" | "Link" | "PollResult" + +return { + EmbedType = EmbedType, +} diff --git a/packages/api-types/src/entitlement.luau b/packages/api-types/src/entitlement.luau new file mode 100644 index 0000000..9f0e0b0 --- /dev/null +++ b/packages/api-types/src/entitlement.luau @@ -0,0 +1,26 @@ +local reflect = require("@utils/table/reflect") + +local EntitlementType = table.freeze(reflect({ + Purchase = 1, + PremiumSubscription = 2, + DeveloperGift = 3, + TestModePurchase = 4, + FreePurchase = 5, + UserGift = 6, + PremiumPurchase = 7, + ApplicationSubscription = 8, +})) + +export type EntitlementType = + "Purchase" + | "PremiumSubscription" + | "DeveloperGift" + | "TestModePurchase" + | "FreePurchase" + | "UserGift" + | "PremiumPurchase" + | "ApplicationSubscription" + +return { + EntitlementType = EntitlementType, +} diff --git a/packages/api-types/src/gateway/closeCodes.luau b/packages/api-types/src/gateway/closeCodes.luau new file mode 100644 index 0000000..1b7ae32 --- /dev/null +++ b/packages/api-types/src/gateway/closeCodes.luau @@ -0,0 +1,38 @@ +--[[ + https://discord.com/developers/docs/topics/opcodes-and-status-codes#gateway-gateway-close-event-codes +]] + +local CloseCodes = table.freeze({ + UnknownError = 4000, + UnknownOpcode = 4001, + DecodeError = 4002, + NotAuthenticated = 4003, + AuthenticationFailed = 4004, + AlreadyAuthenticated = 4005, + InvalidSeq = 4007, + Ratelimited = 4008, + SessionTimedOut = 4009, + InvalidShard = 4010, + ShardingRequired = 4011, + InvalidAPIVersion = 4012, + InvalidIntents = 4013, + DisallowedIntents = 4014, +}) + +export type CloseCode = + "UnknownError" + | "UnknownOpcode" + | "DecodeError" + | "NotAuthenticated" + | "AuthenticationFailed" + | "AlreadyAuthenticated" + | "InvalidSeq" + | "Ratelimited" + | "SessionTimedOut" + | "InvalidShard" + | "ShardingRequired" + | "InvalidAPIVersion" + | "InvalidIntents" + | "DisallowedIntents" + +return CloseCodes diff --git a/packages/api-types/src/gateway/intents.luau b/packages/api-types/src/gateway/intents.luau new file mode 100644 index 0000000..46c0d01 --- /dev/null +++ b/packages/api-types/src/gateway/intents.luau @@ -0,0 +1,52 @@ +--[[ + https://discord.com/developers/docs/topics/gateway#list-of-intents +]] + +local Intents = table.freeze({ + Guilds = 0, + GuildMembers = 1, + GuildModeration = 2, + GuildEmojisAndStickers = 3, + GuildIntegrations = 4, + GuildWebhooks = 5, + GuildInvites = 6, + GuildVoiceStates = 7, + GuildPresences = 8, + GuildMessages = 9, + GuildMessageReactions = 10, + GuildMessageTyping = 11, + DirectMessages = 12, + DirectMessageReactions = 13, + DirectMessageTyping = 14, + MessageContent = 15, + GuildScheduleEvents = 16, + AutoModerationConfiguration = 20, + AutoModerationExecution = 21, + GuildMessagePolls = 24, + DirectMessagePolls = 25, +}) + +export type Intent = + "Guilds" + | "GuildMembers" + | "GuildModeration" + | "GuildEmojisAndStickers" + | "GuildIntegrations" + | "GuildWebhooks" + | "GuildInvites" + | "GuildVoiceStates" + | "GuildPresences" + | "GuildMessages" + | "GuildMessageReactions" + | "GuildMessageTyping" + | "DirectMessages" + | "DirectMessageReactions" + | "DirectMessageTyping" + | "MessageContent" + | "GuildScheduleEvents" + | "AutoModerationConfiguration" + | "AutoModerationExecution" + | "GuildMessagePolls" + | "DirectMessagePolls" + +return Intents diff --git a/packages/api-types/src/gateway/opcodes.luau b/packages/api-types/src/gateway/opcodes.luau new file mode 100644 index 0000000..a738b82 --- /dev/null +++ b/packages/api-types/src/gateway/opcodes.luau @@ -0,0 +1,32 @@ +--[[ + https://discord.com/developers/docs/topics/opcodes-and-status-codes#gateway-gateway-opcodes +]] + +local Opcodess = table.freeze({ + Dispatch = 0, + Heartbeat = 1, + Identify = 2, + PresenceUpdate = 3, + VoiceStateUpdate = 4, + Resume = 6, + Reconnect = 7, + RequestGuildMembers = 8, + InvalidSession = 9, + Hello = 10, + HeartbeatACK = 11, +}) + +export type Opcode = + "Dispatch" + | "Heartbeat" + | "Identify" + | "PresenceUpdate" + | "VoiceStateUpdate" + | "Resume" + | "Reconnect" + | "RequestGuildMembers" + | "InvalidSession" + | "Hello" + | "HeartbeatACK" + +return Opcodess diff --git a/packages/api-types/src/gateway/receiveEvents.luau b/packages/api-types/src/gateway/receiveEvents.luau new file mode 100644 index 0000000..11f8c0c --- /dev/null +++ b/packages/api-types/src/gateway/receiveEvents.luau @@ -0,0 +1,151 @@ +--[[ + https://discord.com/developers/docs/topics/gateway-events#receive-events + + these enums map to the `T` event name given in a Discord payload; + https://discord.com/developers/docs/topics/gateway-events#payload-structure +]] + +local ReceiveEvents = table.freeze({ + Hello = "HELLO", + Ready = "READY", + Resumed = "RESUMED", + Reconnect = "RECONNECT", + InvalidSession = "INVALID_SESSION", + ApplicationCommandPermissionsUpdate = "APPLICATION_COMMAND_PERMISSIONS_UPDATE", + AutoModerationRuleCreate = "AUTO_MODERATION_RULE_CREATE", + AutoModerationRuleUpdate = "AUTO_MODERATION_RULE_UPDATE", + AutoModerationRuleDelete = "AUTO_MODERATION_RULE_DELETE", + AutoModerationActionExecution = "AUTO_MODERATION_ACTION_EXECUTION", + ChannelCreate = "CHANNEL_CREATE", + ChannelUpdate = "CHANNEL_UPDATE", + ChannelDelete = "CHANNEL_DELETE", + ChannelPinsUpdate = "CHANNEL_PINS_UPDATE", + ThreadCreate = "THREAD_CREATE", + ThreadUpdate = "THREAD_UPDATE", + ThreadDelete = "THREAD_DELETE", + ThreadListSync = "THREAD_LIST_SYNC", + ThreadMemberUpdate = "THREAD_MEMBER_UPDATE", + ThreadMembersUpdate = "THREAD_MEMBERS_UPDATE", + EntitlementCreate = "ENTITLEMENT_CREATE", + EntitlementUpdate = "ENTITLEMENT_UPDATE", + EntitlementDelete = "ENTITLEMENT_DELETE", + GuildCreate = "GUILD_CREATE", + GuildUpdate = "GUILD_UPDATE", + GuildDelete = "GUILD_DELETE", + GuildAuditLogEntryCreate = "GUILD_AUDIT_LOG_ENTRY_CREATE", + GuildBanAdd = "GUILD_BAN_ADD", + GuildBanRemove = "GUILD_BAN_REMOVE", + GuildEmojisUpdate = "GUILD_EMOJIS_UPDATE", + GuildStickersUpdate = "GUILD_STICKERS_UPDATE", + GuildIntegrationsUpdate = "GUILD_INTEGRATIONS_UPDATE", + GuildMemberAdd = "GUILD_MEMBER_ADD", + GuildMemberRemove = "GUILD_MEMBER_REMOVE", + GuildMemberUpdate = "GUILD_MEMBER_UPDATE", + GuildMembersChunk = "GUILD_MEMBERS_CHUNK", + GuildRoleCreate = "GUILD_ROLE_CREATE", + GuildRoleDelete = "GUILD_ROLE_DELETE", + GuildRoleUpdate = "GUILD_ROLE_UPDATE", + GuildScheduledEventCreate = "GUILD_SCHEDULED_EVENT_CREATE", + GuildScheduledEventUpdate = "GUILD_SCHEDULED_EVENT_UPDATE", + GuildScheduledEventDelete = "GUILD_SCHEDULED_EVENT_DELETE", + GuildScheduledEventUserAdd = "GUILD_SCHEDULED_EVENT_USER_ADD", + GuildScheduledEventUserRemove = "GUILD_SCHEDULED_EVENT_USER_REMOVE", + IntegrationCreate = "INTEGRATION_CREATE", + IntegrationUpdate = "INTEGRATION_UPDATE", + IntegrationDelete = "INTEGRATION_DELETE", + InteractionCreate = "INTERACTION_CREATE", + InviteCreate = "INVITE_CREATE", + InviteDelete = "INVITE_DELETE", + MessageCreate = "MESSAGE_CREATE", + MessageUpdate = "MESSAGE_UPDATE", + MessageDelete = "MESSAGE_DELETE", + MessageDeleteBulk = "MESSAGE_DELETE_BULK", + MessageReactionAdd = "MESSAGE_REACTION_ADD", + MessageReactionRemove = "MESSAGE_REACTION_REMOVE", + MessageReactionRemoveAll = "MESSAGE_REACTION_REMOVE_ALL", + MessageReactionRemoveEmoji = "MESSAGE_REACTION_REMOVE_EMOJI", + PresenceUpdate = "PRESENCE_UPDATE", + StageInstanceCreate = "STAGE_INSTANCE_CREATE", + StageInstanceUpdate = "STAGE_INSTANCE_UPDATE", + StageInstanceDelete = "STAGE_INSTANCE_DELETE", + TypingStart = "TYPING_START", + UserUpdate = "USER_UPDATE", + VoiceStateUpdate = "VOICE_STATE_UPDATE", + VoiceServerUpdate = "VOICE_SERVER_UPDATE", + WebhooksUpdate = "WEBHOOKS_UPDATE", + MessagePollVoteAdd = "MESSAGE_POLL_VOTE_ADD", + MessagePollVoteRemove = "MESSAGE_POLL_VOTE_REMOVE", +}) + +export type ReceiveEvent = + "Hello" + | "Ready" + | "Resumed" + | "Reconnect" + | "InvalidSession" + | "ApplicationCommandPermissionsUpdate" + | "AutoModerationRuleCreate" + | "AutoModerationRuleUpdate" + | "AutoModerationRuleDelete" + | "AutoModerationActionExecution" + | "ChannelCreate" + | "ChannelUpdate" + | "ChannelDelete" + | "ChannelPinsUpdate" + | "ThreadCreate" + | "ThreadUpdate" + | "ThreadDelete" + | "ThreadListSync" + | "ThreadMemberUpdate" + | "ThreadMembersUpdate" + | "EntitlementCreate" + | "EntitlementUpdate" + | "EntitlementDelete" + | "GuildCreate" + | "GuildUpdate" + | "GuildDelete" + | "GuildAuditLogEntryCreate" + | "GuildBanAdd" + | "GuildBanRemove" + | "GuildEmojisUpdate" + | "GuildStickersUpdate" + | "GuildIntegrationsUpdate" + | "GuildMemberAdd" + | "GuildMemberRemove" + | "GuildMemberUpdate" + | "GuildMembersChunk" + | "GuildRoleCreate" + | "GuildRoleDelete" + | "GuildRoleUpdate" + | "GuildScheduledEventCreate" + | "GuildScheduledEventUpdate" + | "GuildScheduledEventDelete" + | "GuildScheduledEventUserAdd" + | "GuildScheduledEventUserRemove" + | "IntegrationCreate" + | "IntegrationUpdate" + | "IntegrationDelete" + | "InteractionCreate" + | "InviteCreate" + | "InviteDelete" + | "MessageCreate" + | "MessageUpdate" + | "MessageDelete" + | "MessageDeleteBulk" + | "MessageReactionAdd" + | "MessageReactionRemove" + | "MessageReactionRemoveAll" + | "MessageReactionRemoveEmoji" + | "PresenceUpdate" + | "StageInstanceCreate" + | "StageInstanceUpdate" + | "StageInstanceDelete" + | "TypingStart" + | "UserUpdate" + | "VoiceStateUpdate" + | "VoiceServerUpdate" + | "WebhooksUpdate" + | "MessagePollVoteAdd" + | "MessagePollVoteRemove" + +return ReceiveEvents diff --git a/packages/api-types/src/gateway/sendEvents.luau b/packages/api-types/src/gateway/sendEvents.luau new file mode 100644 index 0000000..ee96fb9 --- /dev/null +++ b/packages/api-types/src/gateway/sendEvents.luau @@ -0,0 +1,18 @@ +--[[ + https://discord.com/developers/docs/topics/gateway-events#send-events + + these enums map to the `opcodes` module under `@api-types/gateway/optcodes.luau` +]] + +local SendEvents = table.freeze({ + Identify = "Identify", + Resume = "Resume", + Heartbeat = "Heartbeat", + RequestGuildMembers = "RequestGuildMembers", + UpdateVoiceState = "UpdateVoiceState", + UpdatePresence = "UpdatePresence", +}) + +export type SendEvent = "Identify" | "Resume" | "Heartbeat" | "RequestGuildMembers" | "UpdateVoiceState" | "UpdatePresence" + +return SendEvents diff --git a/packages/api-types/src/gateway/types.luau b/packages/api-types/src/gateway/types.luau new file mode 100644 index 0000000..61fef64 --- /dev/null +++ b/packages/api-types/src/gateway/types.luau @@ -0,0 +1,488 @@ +--[[ + Mapping out all discord recieve/send payloads that can be consumed by the Discord v10 API. +]] + +local objects = require("@api-types/apiTypes") + +-- [[ Base ]] -- +export type Payload = { + op: number, + d: DATA, + s: number?, + t: string?, +} + +-- [[ Send Events - https://discord.com/developers/docs/topics/gateway-events#send-events ]] -- + +-- https://discord.com/developers/docs/topics/gateway-events#identify +export type IdentifyPayload = Payload<{ + token: string, -- Authentication token + properties: objects.IdentifyPropertiesObject, -- Connection properties + compress: boolean?, -- Whether this connection supports compression of packets + large_threshold: number?, -- Value between 50 and 250, total number of members where the gateway will stop sending offline members in the guild member list + shard: { number }?, -- Used for Guild Sharding + presence: objects.ActivityObject?, -- Presence structure for initial presence information + intents: number, -- Gateway Intents you wish to receive +}> + +-- https://discord.com/developers/docs/topics/gateway-events#resume +export type ResumePayload = Payload<{ + token: string, -- Session token + session_id: string, -- Session ID + seq: number, -- Last sequence number received +}> + +-- https://discord.com/developers/docs/topics/gateway-events#heartbeat +export type HeartbeatPayload = Payload + +-- https://discord.com/developers/docs/topics/gateway-events#request-guild-members +export type RequestGuildMembersPayload = Payload<{ + guild_id: objects.Snowflake, -- ID of the guild to get members for + query: string?, -- string that username starts with, or an empty string to return all members + limit: number, -- maximum number of members to send matching the query; a limit of 0 can be used with an empty string query to return all members + presences: boolean?, -- used to specify if we want the presences of the matched members + user_ids: { objects.Snowflake }?, -- used to specify which users you wish to fetch + nonce: string?, -- nonce to identify the Guild Members Chunk response +}> + +-- https://discord.com/developers/docs/topics/gateway-events#update-voice-state +export type UpdateVoiceStatePayload = Payload<{ + guild_id: objects.Snowflake, -- ID of the guild + channel_id: string?, -- ID of the voice channel client wants to join (null if disconnecting) + self_mute: boolean, -- Whether the client is muted + self_deaf: boolean, -- Whether the client deafened +}> + +-- https://discord.com/developers/docs/topics/gateway-events#update-presence +export type UpdatePresencePayload = Payload + +-- [[ Receive Events - https://discord.com/developers/docs/topics/gateway-events#receive-events ]] -- + +-- https://discord.com/developers/docs/topics/gateway-events#hello +export type HelloPayload = Payload<{ + heartbeat_interval: number, -- Interval (in milliseconds) an app should heartbeat with +}> + +-- https://discord.com/developers/docs/topics/gateway-events#ready +export type ReadyPayload = Payload<{ + v: number, + user: objects.UserObject, + guilds: { objects.UnavailableGuildObject }, + session_id: string, + resume_gateway_url: string, + shard: { number }?, + application: objects.ApplicationObject, +}> + +-- https://discord.com/developers/docs/topics/gateway-events#resumed +export type ResumedPayload = Payload + +-- https://discord.com/developers/docs/topics/gateway-events#reconnect +export type ReconnectPayload = Payload + +-- https://discord.com/developers/docs/topics/gateway-events#invalid-session +export type InvalidSessionPayload = Payload + +-- https://discord.com/developers/docs/topics/gateway-events#auto-moderation-rule-create +export type ApplicationCommandPermissionsCreatePayload = Payload + +-- https://discord.com/developers/docs/topics/gateway-events#application-command-permissions-update +export type ApplicationCommandPermissionsUpdatePayload = Payload + +-- https://discord.com/developers/docs/topics/gateway-events#auto-moderation-rule-delete +export type ApplicationCommandPermissionsDeletePayload = Payload + +-- https://discord.com/developers/docs/topics/gateway-events#auto-moderation-action-execution +export type ApplicationCommandPermissionsExecutionPayload = Payload<{ + guild_id: objects.Snowflake, -- ID of the guild in which action was executed + action: objects.AutomoderationActionObject, -- Action which was executed + rule_id: objects.Snowflake, -- ID of the rule which action belongs to + rule_trigger_type: objects.AutomoderationRuleTriggerType, -- Trigger type of rule which was triggered + user_id: objects.Snowflake, -- ID of the user which generated the content which triggered the rule + channel_id: objects.Snowflake?, -- ID of the channel in which user content was posted + message_id: objects.Snowflake?, -- ID of any user message which content belongs to * + alert_system_message_id: objects.Snowflake?, -- ID of any system auto moderation messages posted as a result of this action ** + content: string, -- User-generated text content + matched_keyword: string?, -- Word or phrase configured in the rule that triggered the rule + matched_content: string?, -- Substring in content that triggered the rule +}> + +-- https://discord.com/developers/docs/topics/gateway-events#channel-create +export type CreateChannelPayload = Payload + +-- https://discord.com/developers/docs/topics/gateway-events#channel-update +export type UpdateChannelPayload = Payload + +-- https://discord.com/developers/docs/topics/gateway-events#channel-delete +export type DeleteChannelPayload = Payload + +-- https://discord.com/developers/docs/topics/gateway-events#channel-pins-update +export type ChannelPinsUpdatePayload = Payload<{ + guild_id: objects.Snowflake?, + channel_id: objects.Snowflake, + last_pin_timestamp: string?, +}> + +-- https://discord.com/developers/docs/topics/gateway-events#thread-create +export type CreateThreadPayload = Payload + +-- https://discord.com/developers/docs/topics/gateway-events#thread-update +export type UpdateThreadPayload = Payload + +-- https://discord.com/developers/docs/topics/gateway-events#thread-delete +export type DeleteThreadPayload = Payload + +-- https://discord.com/developers/docs/topics/gateway-events#thread-list-sync +export type ThreadListSyncPayload = Payload<{ + guild_id: objects.Snowflake, -- ID of the guild + channel_ids: { objects.Snowflake }?, -- Parent channel IDs whose threads are being synced. If omitted, then threads were synced for the entire guild. This array may contain channel_ids that have no active threads as well, so you know to clear that data. + threads: { objects.ChannelObject }, -- All active threads in the given channels that the current user can access + members: { objects.ThreadMemberObject }, -- All thread member objects from the synced threads for the current user, indicating which threads the current user has been added to +}> + +-- https://discord.com/developers/docs/topics/gateway-events#thread-member-update +export type ThreadMemberUpdatePayload = Payload<{ + guild_id: objects.Snowflake, -- ID of the guild +}> + +-- https://discord.com/developers/docs/topics/gateway-events#thread-member-update +export type ThreadMembersUpdatePayload = Payload<{ + id: objects.Snowflake, -- ID of the thread + guild_id: objects.Snowflake, -- ID of the guild + member_count: number, -- Approximate number of members in the thread, capped at 50 + added_members: { objects.ThreadMemberObject }?, -- Users who were added to the thread + removed_member_ids: { objects.Snowflake }?, -- ID of the users who were removed from the thread +}> + +-- https://discord.com/developers/docs/topics/gateway-events#entitlement-create +export type EntitlementCreatePayload = Payload + +-- https://discord.com/developers/docs/topics/gateway-events#entitlement-update +export type EntitlementUpdatePayload = Payload + +-- https://discord.com/developers/docs/topics/gateway-events#entitlement-delete +export type EntitlementDeletePayload = Payload + +-- https://discord.com/developers/docs/topics/gateway-events#guild-create +export type GuildCreatePayload = Payload< + objects.UnavailableGuildObject | (objects.GuildObject & { + joined_at: string, -- When this guild was joined at + large: boolean, -- true if this is considered a large guild + unavailable: boolean?, -- true if this guild is unavailable due to an outage + member_count: number, -- Total number of members in this guild + voice_states: { objects.VoiceStateObject }, -- States of members currently in voice channels; lacks the guild_id key + members: { objects.GuildMemberObject }, -- Users in the guild + channels: { objects.ChannelObject }, -- Channels in the guild + threads: { objects.ChannelObject }, -- All active threads in the guild that current user has permission to view + presences: { objects.PresenceUpdateObject }, -- Presences of the members in the guild, will only include non-offline members if the size is greater than large threshold + stage_instances: { objects.StageInstanceObject }, -- Stage instances in the guild + guild_scheduled_events: { objects.GuildScheduledEventObject }, -- Scheduled events in the guild + }) +> + +-- https://discord.com/developers/docs/topics/gateway-events#guild-update +export type GuildUpdatePayload = Payload + +-- https://discord.com/developers/docs/topics/gateway-events#guild-delete +export type GuildDeletePayload = Payload + +-- https://discord.com/developers/docs/topics/gateway-events#guild-ban-add +export type GuildBanAddPayload = Payload<{ + guild_id: objects.Snowflake, -- ID of the guild + user: objects.UserObject, -- User who was banned +}> + +-- https://discord.com/developers/docs/topics/gateway-events#guild-ban-remove +export type GuildBanRemovePayload = Payload<{ + guild_id: objects.Snowflake, -- ID of the guild + user: objects.UserObject, -- User who was unbanned +}> + +-- https://discord.com/developers/docs/topics/gateway-events#guild-emojis-update +export type GuildEmojisUpdatePayload = Payload<{ + guild_id: objects.Snowflake, -- ID of the guild + emojis: { objects.EmojiObject }, -- Array of emojis +}> + +-- https://discord.com/developers/docs/topics/gateway-events#guild-stickers-update +export type GuildStickersUpdatePayload = Payload<{ + guild_id: objects.Snowflake, -- ID of the guild + stickers: { objects.StickerObject }, -- Array of stickers +}> + +-- https://discord.com/developers/docs/topics/gateway-events#guild-integrations-update +export type GuildIntegrationsUpdatePayload = Payload<{ + guild_id: objects.Snowflake, -- ID of the guild +}> + +-- https://discord.com/developers/docs/topics/gateway-events#guild-member-add +export type GuildMemberAddPayload = Payload< + objects.GuildMemberObject & { + guild_id: objects.Snowflake, -- ID of the guild + } +> + +-- https://discord.com/developers/docs/topics/gateway-events#guild-member-remove +export type GuildMemberRemovePayload = Payload<{ + guild_id: objects.Snowflake, --ID of the guild + user: objects.UserObject, -- User who was removed +}> + +-- https://discord.com/developers/docs/topics/gateway-events#guild-member-update +export type GuildMemberUpdatePayload = Payload<{ + guild_id: objects.Snowflake, -- ID of the guild + roles: { objects.Snowflake }, -- User role ids + user: objects.UserObject, -- The user effected + nick: string?, -- Nickname of the user in the guild + avatar: string?, -- Member's guild avatar hash + joined_at: string?, -- When the user joined the guild + premium_since: string?, -- When the user starting boosting the guild + deaf: boolean?, -- Whether the user is deafened in voice channels + mute: boolean?, -- Whether the user is muted in voice channels + pending: boolean?, -- Whether the user has not yet passed the guild's Membership Screening requirements + communication_disabled_until: string?, -- When the user's timeout will expire and the user will be able to communicate in the guild again, null or a time in the past if the user is not timed out + flags: number?, -- Guild member flags represented as a bit set, defaults to 0 + avatar_decoration_data: objects.AvatarDecorationDataObject?, -- Data for the member's guild avatar decoration +}> + +-- https://discord.com/developers/docs/topics/gateway-events#guild-members-chunk +export type GuildMembersChunkPayload = Payload<{ + guild_id: objects.Snowflake, -- ID of the guild + members: { objects.GuildMemberObject }, -- Set of guild members + chunk_index: number, -- Chunk index in the expected chunks for this response (0 <= chunk_index < chunk_count) + chunk_count: number, -- Total number of expected chunks for this response + not_found: { unknown }?, -- When passing an invalid ID to REQUEST_GUILD_MEMBERS, it will be returned here + presences: { objects.PresenceObject }?, -- When passing true to REQUEST_GUILD_MEMBERS, presences of the returned members will be here + nonce: string?, -- Nonce used in the Guild Members Request +}> + +-- https://discord.com/developers/docs/topics/gateway-events#guild-role-create +export type GuildRoleCreatePayload = Payload<{ + guild_id: objects.Snowflake, -- ID of the guild + role: objects.GuildRoleObject, -- Role that was created +}> + +-- https://discord.com/developers/docs/topics/gateway-events#guild-role-update +export type GuildRoleUpdatePayload = Payload<{ + guild_id: objects.Snowflake, -- ID of the guild + role: objects.GuildRoleObject, -- Role that was updated +}> + +-- https://discord.com/developers/docs/topics/gateway-events#guild-role-delete +export type GuildRoleDeletePayload = Payload<{ + guild_id: objects.Snowflake, -- ID of the guild + role_id: objects.Snowflake, -- ID of the role +}> + +-- https://discord.com/developers/docs/topics/gateway-events#guild-scheduled-event-create +export type GuildScheduledEventCreatePayload = Payload + +-- https://discord.com/developers/docs/topics/gateway-events#guild-scheduled-event-create +export type GuildScheduledEventUpdatePayload = Payload + +-- https://discord.com/developers/docs/topics/gateway-events#guild-scheduled-event-create +export type GuildScheduledEventDeletePayload = Payload + +-- https://discord.com/developers/docs/topics/gateway-events#guild-scheduled-event-create +export type GuildScheduledEventUserAddPayload = Payload<{ + guild_scheduled_event_id: objects.Snowflake, -- ID of the guild scheduled event + user_id: objects.Snowflake, -- ID of the user + guild_id: objects.Snowflake, -- ID of the guild +}> + +-- https://discord.com/developers/docs/topics/gateway-events#guild-scheduled-event-create +export type GuildScheduledEventUserRemovePayload = Payload<{ + guild_scheduled_event_id: objects.Snowflake, -- ID of the guild scheduled event + user_id: objects.Snowflake, -- ID of the user + guild_id: objects.Snowflake, -- ID of the guild +}> + +-- https://discord.com/developers/docs/topics/gateway-events#integration-create +export type IntegrationCreatePayload = Payload< + objects.IntegrationObject & { + guild_id: objects.Snowflake?, -- ID of the guild + } +> + +-- https://discord.com/developers/docs/topics/gateway-events#integration-update +export type IntegrationUpdatePayload = Payload< + objects.IntegrationObject & { + guild_id: objects.Snowflake?, -- ID of the guild + } +> + +-- https://discord.com/developers/docs/topics/gateway-events#integration-delete +export type IntegrationDeletePayload = Payload< + objects.IntegrationObject & { + id: objects.Snowflake?, -- Integration ID + guild_id: objects.Snowflake, -- ID of the guild + application_id: objects.Snowflake?, -- ID of the bot/OAuth2 application for this discord integration + } +> + +-- https://discord.com/developers/docs/topics/gateway-events#interaction-create +export type InteractionCreatePayload = Payload + +-- https://discord.com/developers/docs/topics/gateway-events#invite-create +export type InviteCreatePayload = Payload<{ + channel_id: objects.Snowflake, -- Channel the invite is for + code: string, -- Unique invite code + created_at: string, -- Time at which the invite was created + guild_id: objects.Snowflake?, -- Guild of the invite + inviter: objects.UserObject?, -- User that created the invite + max_age: number, -- How long the invite is valid for (in seconds) + max_uses: number, -- Maximum number of times the invite can be used + target_type: objects.InviteTargetTypes?, -- Type of target for this voice channel invite + target_user: objects.UserObject?, -- User whose stream to display for this voice channel stream invite + target_application: objects.ApplicationObject?, -- Embedded application to open for this voice channel embedded application invite + temporary: boolean, -- Whether or not the invite is temporary (invited users will be kicked on disconnect unless they're assigned a role) + uses: number, -- How many times the invite has been used (always will be 0) +}> + +-- https://discord.com/developers/docs/topics/gateway-events#invite-delete +export type InviteDeletePayload = Payload<{ + channel_id: objects.Snowflake, -- Channel of the invite + code: string, -- Unique invite code + guild_id: objects.Snowflake?, -- Guild of the invite +}> + +-- https://discord.com/developers/docs/topics/gateway-events#message-create +export type MessageCreatePayload = Payload< + objects.MessageObject & { + mentions: { objects.UserObject & { member: objects.GuildMemberObject } }, -- Users specifically mentioned in the message + member: objects.GuildMemberObject?, -- Member properties for this message's author. Missing for ephemeral messages and messages from webhooks + guild_id: objects.Snowflake?, -- ID of the guild the message was sent in - unless it is an ephemeral message + } +> + +-- https://discord.com/developers/docs/topics/gateway-events#message-update +export type MessageUpdatePayload = Payload< + objects.MessageObject & { + mentions: { objects.UserObject & { member: objects.GuildMemberObject } }, -- Users specifically mentioned in the message + member: objects.GuildMemberObject?, -- Member properties for this message's author. Missing for ephemeral messages and messages from webhooks + guild_id: objects.Snowflake?, -- ID of the guild the message was sent in - unless it is an ephemeral message + } +> + +-- https://discord.com/developers/docs/topics/gateway-events#message-delete +export type MessageDeletePayload = Payload<{ + id: objects.Snowflake, -- ID of the messages + channel_id: objects.Snowflake, -- ID of the channel + guild_id: objects.Snowflake?, -- ID of the guild +}> + +-- https://discord.com/developers/docs/topics/gateway-events#message-delete-bulk +export type MessageDeleteBulkPayload = Payload<{ + ids: { objects.Snowflake }, -- IDs of the messages + channel_id: objects.Snowflake, -- ID of the channel + guild_id: objects.Snowflake?, -- ID of the guild +}> + +-- https://discord.com/developers/docs/topics/gateway-events#message-reaction-add +export type MessageReactionAddPayload = Payload<{ + user_id: objects.Snowflake, -- ID of the user + channel_id: objects.Snowflake, -- ID of the channel + message_id: objects.Snowflake, -- ID of the message + guild_id: objects.Snowflake?, -- ID of the guild + member: objects.GuildMemberObject?, -- Member who reacted if this happened in a guild + emoji: objects.EmojiObject, -- Emoji used to react - example + message_author_id: objects.Snowflake?, -- ID of the user who authored the message which was reacted to + burst: boolean, -- true if this is a super-reaction + burst_colors: { string }, -- Colors used for super-reaction animation in "#rrggbb" format + type: objects.ReactionType, -- The type of reaction +}> + +-- https://discord.com/developers/docs/topics/gateway-events#message-reaction-remove +export type MessageReactionRemovePayload = Payload<{ + user_id: objects.Snowflake, -- ID of the user + channel_id: objects.Snowflake, -- ID of the channel + message_id: objects.Snowflake, -- ID of the message + guild_id: objects.Snowflake?, -- ID of the guild + emoji: objects.EmojiObject, -- Emoji used to react - example + burst: boolean, -- true if this was a super-reaction + type: objects.ReactionType, -- The type of reaction +}> + +-- https://discord.com/developers/docs/topics/gateway-events#message-reaction-remove-all +export type MessageReactionRemoveAllPayload = Payload<{ + channel_id: objects.Snowflake, -- ID of the channel + message_id: objects.Snowflake, -- ID of the guild + guild_id: objects.Snowflake?, -- ID of the message +}> + +-- https://discord.com/developers/docs/topics/gateway-events#message-reaction-remove-emoji +export type MessageReactionRemoveEmojiPayload = Payload<{ + channel_id: objects.Snowflake, -- ID of the channel + message_id: objects.Snowflake, -- ID of the guild + guild_id: objects.Snowflake?, -- ID of the message + emoji: objects.EmojiObject, -- Emoji that was removed +}> + +-- https://discord.com/developers/docs/topics/gateway-events#presence-update +export type PresenceUpdatePayload = Payload<{ + user: objects.UserObject, -- User whose presence is being updated + guild_id: objects.Snowflake, -- ID of the guild + status: string, -- Either "idle", "dnd", "online", or "offline" + activities: { objects.ActivityObject }, -- User's current activities + client_status: objects.ClientStatusObject, -- User's platform-dependent status +}> + +-- https://discord.com/developers/docs/topics/gateway-events#stage-instance-create +export type StageInstanceCreate = Payload + +-- https://discord.com/developers/docs/topics/gateway-events#stage-instance-update +export type StageInstanceUpdate = Payload + +-- https://discord.com/developers/docs/topics/gateway-events#stage-instance-delete +export type StageInstanceDelete = Payload + +-- https://discord.com/developers/docs/topics/gateway-events#typing-start +export type TypingStartPayload = Payload<{ + channel_id: objects.Snowflake, -- ID of the channel + guild_id: objects.Snowflake?, -- ID of the guild + user_id: objects.Snowflake, -- ID of the user + timestamp: number, -- Unix time (in seconds) of when the user started typing + member: objects.GuildMemberObject?, -- Member who started typing if this happened in a guild +}> + +-- https://discord.com/developers/docs/topics/gateway-events#user-update +export type UserUpdatePayload = Payload +-- https://discord.com/developers/docs/topics/gateway-events#voice-state-update +export type VoiceStateUpdatePayload = Payload<{ + token: string, -- Voice connection token + guild_id: objects.Snowflake, -- Guild this voice server update is for + endpoint: string?, -- Voice server host +}> + +-- https://discord.com/developers/docs/topics/gateway-events#voice-server-update +export type VoiceServerUpdatePayload = Payload<{ + token: string, -- Voice connection token + guild_id: objects.Snowflake, -- Guild this voice server update is for + endpoint: string?, -- Voice server host +}> + +-- https://discord.com/developers/docs/topics/gateway-events#webhooks-update +export type WebhooksUpdatePayload = Payload<{ + guild_id: objects.Snowflake, -- ID of the guild + channel_id: objects.Snowflake, -- ID of the channel= +}> + +-- https://discord.com/developers/docs/topics/gateway-events#message-poll-vote-add +export type MessagePollVoteAdd = Payload<{ + user_id: objects.Snowflake, -- ID of the user + channel_id: objects.Snowflake, -- ID of the channel + message_id: objects.Snowflake, -- ID of the message + guild_id: objects.Snowflake?, -- ID of the guild + answer_id: number, -- ID of the answer +}> + +-- https://discord.com/developers/docs/topics/gateway-events#message-poll-vote-remove +export type MessagePollVoteRemove = Payload<{ + user_id: objects.Snowflake, -- ID of the user + channel_id: objects.Snowflake, -- ID of the channel + message_id: objects.Snowflake, -- ID of the message + guild_id: objects.Snowflake?, -- ID of the guild + answer_id: number, -- ID of the answer +}> + +return "DISCORD_V10_PAYLOAD_TYPES" diff --git a/packages/api-types/src/guild.luau b/packages/api-types/src/guild.luau new file mode 100644 index 0000000..eada762 --- /dev/null +++ b/packages/api-types/src/guild.luau @@ -0,0 +1,55 @@ +local reflect = require("@utils/table/reflect") + +local VerificationLevel = table.freeze(reflect({ + None = 0, + Low = 1, + Medium = 2, + High = 3, + VeryHigh = 4, +})) + +local DefaultMessageNotification = table.freeze(reflect({ + AllMessages = 0, + OnlyMentions = 1, +})) + +local ExplicitContentFilterLevel = table.freeze(reflect({ + Disabled = 0, + MembersWithoutRoles = 1, + AllMembers = 2, +})) + +local NSFWLevel = table.freeze(reflect({ + Default = 0, + Explicit = 1, + Safe = 2, + AgeRestricted = 3, +})) + +local PremiumTier = table.freeze(reflect({ + None = 0, + Tier1 = 1, + Tier2 = 2, + Tier3 = 3, +})) + +local MFALevel = table.freeze(reflect({ + None = 0, + Elevated = 1, +})) + +export type VerificationLevel = "None" | "Low" | "Medium" | "High" | "VeryHigh" +export type DefaultMessageNotification = "AllMessages" | "OnlyMentions" +export type ExplicitContentFilterLevel = "Disabled" | "MembersWithoutRoles" | "AllMembers" +export type NSFWLevel = "Default" | "Explicit" | "Safe" | "AgeRestricted" +export type PremiumTier = "None" | "Tier1" | "Tier2" | "Tier3" +export type MFALevel = "None" | "Elevated" + +return { + VerificationLevel = VerificationLevel, + DefaultMessageNotification = DefaultMessageNotification, + ExplicitContentFilterLevel = ExplicitContentFilterLevel, + NSFWLevel = NSFWLevel, + PremiumTier = PremiumTier, + MFALevel = MFALevel, +} diff --git a/packages/api-types/src/interaction.luau b/packages/api-types/src/interaction.luau new file mode 100644 index 0000000..18b99c4 --- /dev/null +++ b/packages/api-types/src/interaction.luau @@ -0,0 +1,67 @@ +local reflect = require("@utils/table/reflect") + +local InteractionType = table.freeze(reflect({ + Ping = 1, + ApplicationCommand = 2, + MessageComponent = 3, + ApplicationCommandAutocomplete = 4, + ModalSubmit = 5, +})) + +local ApplicationCommandOptionType = table.freeze(reflect({ + SubCommand = 1, + SubCommandGroup = 2, + String = 3, + Integer = 4, + Boolean = 5, + User = 6, + Channel = 7, + Role = 8, + Mentionable = 9, + Number = 10, + Attachment = 11, +})) + +local InteractionContextType = table.freeze(reflect({ + Guild = 0, + BotDm = 1, + PrivateChannel = 2, +})) + +local ApplicationCommandType = table.freeze(reflect({ + ChatInput = 1, + User = 2, + Message = 3, + PrimaryEntryPoiant = 4, +})) + +export type InteractionType = + "Ping" + | "ApplicationCommand" + | "MessageComponent" + | "ApplicationCommandAutocomplete" + | "ModalSubmit" + +export type ApplicationCommandOptionType = + "SubCommand" + | "SubCommandGroup" + | "String" + | "Integer" + | "Boolean" + | "User" + | "Channel" + | "Role" + | "Mentionable" + | "Number" + | "Attachment" + +export type ApplicationCommandType = "ChatInput" | "User" | "Message" | "PrimaryEntryPoiant" + +export type InteractionContextType = "Guild" | "BotDm" | "PrivateChannel" + +return { + InteractionType = InteractionType, + ApplicationCommandOptionType = ApplicationCommandOptionType, + InteractionContextType = InteractionContextType, + ApplicationCommandType = ApplicationCommandType, +} diff --git a/packages/api-types/src/message.luau b/packages/api-types/src/message.luau new file mode 100644 index 0000000..2801f10 --- /dev/null +++ b/packages/api-types/src/message.luau @@ -0,0 +1,135 @@ +local reflect = require("@utils/table/reflect") + +local MessageType = table.freeze(reflect({ + Default = 0, + RecipientAdd = 1, + RecipientRemove = 2, + Call = 3, + ChannelNameChange = 4, + ChannelIconChange = 5, + ChannelPinnedMessage = 6, + UserJoin = 7, + GuildBoost = 8, + GuildBoostTier1 = 9, + GuildBoostTier2 = 10, + GuildBoostTier3 = 11, + ChannelFollowAdd = 12, + GuildDiscoveryDisqualified = 14, + GuildDiscoveryRequalified = 15, + GuildDiscoveryGracePeriodInitialWarning = 16, + GuildDiscoveryGracePeriodFinalWarning = 17, + ThreadCreated = 18, + Reply = 19, + ChatInputCommand = 20, + ThreadStarterMessage = 21, + GuildInviteReminder = 22, + ContextMenuCommand = 23, + AutoModerationAction = 24, + RoleSubscriptionPurchase = 25, + InteractionPremiumUpsell = 26, + StageStart = 27, + StageEnd = 28, + StageSpeaker = 29, + StageTopic = 31, + GuildApplicationPremiumSubscription = 32, + GuildIncidentAlertModeEnabled = 36, + GuildIncidentAlertModeDisabled = 37, + GuildIncidentReportRaid = 38, + GuildIncidentReportFalseAlarm = 39, + PurchaseNotification = 44, + PollResult = 46, +})) + +local MessageAcitvityType = table.freeze(reflect({ + Join = 1, + Spectate = 2, + Listen = 3, + JoinRequest = 5, +})) + +local MessageReferenceType = table.freeze(reflect({ + Default = 0, + Forward = 1, +})) + +local ButtonStyle = table.freeze(reflect({ + Blurple = 1, + Grey = 2, + Green = 3, + Red = 4, +})) + +local TextInputStyle = table.freeze(reflect({ + Short = 1, + Paragraph = 2, +})) + +local SelectDefaultValueType = table.freeze(reflect({ + Role = "role", + User = "user", + Channel = "channel", +})) + +local PollLayoutType = table.freeze(reflect({ + Default = 1, +})) + +export type MessageType = + "Default" + | "RecipientAdd" + | "RecipientRemove" + | "Call" + | "ChannelNameChange" + | "ChannelIconChange" + | "ChannelPinnedMessage" + | "UserJoin" + | "GuildBoost" + | "GuildBoostTier1" + | "GuildBoostTier2" + | "GuildBoostTier3" + | "ChannelFollowAdd" + | "GuildDiscoveryDisqualified" + | "GuildDiscoveryRequalified" + | "GuildDiscoveryGracePeriodInitialWarning" + | "GuildDiscoveryGracePeriodFinalWarning" + | "ThreadCreated" + | "Reply" + | "ChatInputCommand" + | "ThreadStarterMessage" + | "GuildInviteReminder" + | "ContextMenuCommand" + | "AutoModerationAction" + | "RoleSubscriptionPurchase" + | "InteractionPremiumUpsell" + | "StageStart" + | "StageEnd" + | "StageSpeaker" + | "StageTopic" + | "GuildApplicationPremiumSubscription" + | "GuildIncidentAlertModeEnabled" + | "GuildIncidentAlertModeDisabled" + | "GuildIncidentReportRaid" + | "GuildIncidentReportFalseAlarm" + | "PurchaseNotification" + | "PollResult" + +export type MessageAcitvityType = "Join" | "Spectate" | "Listen" | "JoinRequest" +export type MessageReferenceType = "Default" | "Forward" + +export type ButtonStyle = "Blurple" | "Grey" | "Green" | "Red" | "Grey" + +export type SelectDefaultValueType = "User" | "Role" | "Channel" + +export type TextInputStyle = "Short" | "Paragraph" + +export type PollLayoutType = "Default" + +return { + MessageType = MessageType, + MessageAcitvityType = MessageAcitvityType, + MessageReferenceType = MessageReferenceType, + ButtonStyle = ButtonStyle, + TextInputStyle = TextInputStyle, + SelectDefaultValueType = SelectDefaultValueType, + PollLayoutType = PollLayoutType, +} diff --git a/packages/api-types/src/permission.luau b/packages/api-types/src/permission.luau new file mode 100644 index 0000000..eb8c342 --- /dev/null +++ b/packages/api-types/src/permission.luau @@ -0,0 +1,108 @@ +local reflect = require("@utils/table/reflect") + +local bit = require("@vendor/bit") + +local Permissions = table.freeze(reflect({ + CreateInstantInvite = bit.lshift(1, 0), + KickMembers = bit.lshift(1, 1), + BanMembers = bit.lshift(1, 2), + Administrator = bit.lshift(1, 3), + ManageChannels = bit.lshift(1, 4), + ManageGuild = bit.lshift(1, 5), + AddReactions = bit.lshift(1, 6), + ViewAuditLog = bit.lshift(1, 7), + PrioritySpeaker = bit.lshift(1, 8), + Stream = bit.lshift(1, 9), + ViewChannel = bit.lshift(1, 10), + SendMessages = bit.lshift(1, 11), + SendTTSMessages = bit.lshift(1, 12), + ManageMessages = bit.lshift(1, 13), + EmbedLinks = bit.lshift(1, 14), + AttachFiles = bit.lshift(1, 15), + ReadMessageHistory = bit.lshift(1, 16), + MentionEveryone = bit.lshift(1, 17), + UseExternalEmojis = bit.lshift(1, 18), + ViewGuildInsights = bit.lshift(1, 19), + Connect = bit.lshift(1, 20), + Speak = bit.lshift(1, 21), + MuteMembers = bit.lshift(1, 22), + DeafenMembers = bit.lshift(1, 23), + MoveMembers = bit.lshift(1, 24), + UseVAD = bit.lshift(1, 25), + ChangeNickname = bit.lshift(1, 26), + ManageNicknames = bit.lshift(1, 27), + ManageRoles = bit.lshift(1, 28), + ManageWebhooks = bit.lshift(1, 29), + ManageGuildExpressions = bit.lshift(1, 30), + UseApplicationCommands = bit.lshift(1, 31), + RequestToSpeak = bit.lshift(1, 32), + ManageEvents = bit.lshift(1, 33), + ManageThreads = bit.lshift(1, 34), + CreatePublicThreads = bit.lshift(1, 35), + CreatePrivateThreads = bit.lshift(1, 36), + UseExternalStickers = bit.lshift(1, 37), + SendMessagesInThreads = bit.lshift(1, 38), + UseEmbeddedActivities = bit.lshift(1, 39), + ModerateMembers = bit.lshift(1, 40), + ViewCreatorMonetizationAnalytics = bit.lshift(1, 41), + UseSoundboard = bit.lshift(1, 42), + UseExternalSounds = bit.lshift(1, 45), + SendVoiceMessages = bit.lshift(1, 46), + SendPolls = bit.lshift(1, 49), + UseExternalApps = bit.lshift(1, 50), +})) + +export type Permissions = + "CreateInstantInvite" + | "KickMembers" + | "BanMembers" + | "Administrator" + | "ManageChannels" + | "ManageGuild" + | "AddReactions" + | "ViewAuditLog" + | "PrioritySpeaker" + | "Stream" + | "ViewChannel" + | "SendMessages" + | "SendTtsMessages" + | "ManageMessages" + | "EmbedLinks" + | "AttachFiles" + | "ReadMessageHistory" + | "MentionEveryone" + | "UseExternalEmojis" + | "ViewGuildInsights" + | "Connect" + | "Speak" + | "MuteMembers" + | "DeafenMembers" + | "MoveMembers" + | "UseVad" + | "ChangeNickname" + | "ManageNicknames" + | "ManageRoles" + | "ManageWebhooks" + | "ManageEmojisAndStickers" + | "UseApplicationCommands" + | "RequestToSpeak" + | "ManageEvents" + | "ManageThreads" + | "CreatePublicThreads" + | "CreatePrivateThreads" + | "UseExternalStickers" + | "SendMessagesInThreads" + | "UseEmbeddedActivities" + | "ModerateMembers" + | "ViewCreatorMonetizationAnalytics" + | "UseSoundboard" + | "CreateGuildExpressions" + | "CreateEvents" + | "UseExternalSounds" + | "SendVoiceMessages" + | "SendPolls" + | "UseExternalApps" + +return { + Permissions = Permissions, +} diff --git a/packages/api-types/src/poll.luau b/packages/api-types/src/poll.luau new file mode 100644 index 0000000..b2a8d65 --- /dev/null +++ b/packages/api-types/src/poll.luau @@ -0,0 +1,3 @@ +export type PollLayoutType = "Default" + +return "DISCORD_V10_APPLICATION_TYPES" diff --git a/packages/api-types/src/rest/endpoints.luau b/packages/api-types/src/rest/endpoints.luau new file mode 100644 index 0000000..e8b8a13 --- /dev/null +++ b/packages/api-types/src/rest/endpoints.luau @@ -0,0 +1,745 @@ +--[[ + Mapping out all discord V10 endpoints. +]] + +local endpoints = table.freeze({ + -- https://discord.com/developers/docs/interactions/receiving-and-responding#create-interaction-response + CreateInteractionResponse = "/interactions/%s/%s/callback", + + -- https://discord.com/developers/docs/interactions/receiving-and-responding#get-original-interaction-response + GetOriginalInteractionResponse = "/webhooks/%s/%s/messages/@original", + + -- https://discord.com/developers/docs/interactions/receiving-and-responding#edit-original-interaction-response + EditOriginalInteractionResponse = "/webhooks/%s/%s/messages/@original", + + -- https://discord.com/developers/docs/interactions/receiving-and-responding#delete-original-interaction-response + DeleteOriginalInteractionResponse = "/webhooks/%s/%s/messages/@original", + + -- https://discord.com/developers/docs/interactions/receiving-and-responding#create-followup-message + CreateFollowupMessage = "/webhooks/%s/%s", + + -- https://discord.com/developers/docs/interactions/receiving-and-responding#get-followup-message + GetFollowupMessage = "/webhooks/%s/%s/messages/%s", + + -- https://discord.com/developers/docs/interactions/receiving-and-responding#edit-followup-message + EditFollowupMessage = "/webhooks/%s/%s/messages/%s", + + -- https://discord.com/developers/docs/interactions/receiving-and-responding#delete-followup-message + DeleteFollowupMessage = "/webhooks/%s/%s/messages/%s", + + -- https://discord.com/developers/docs/interactions/application-commands#get-global-application-commands + GetGlobalApplicationCommands = "/applications/%s/commands", + + -- https://discord.com/developers/docs/interactions/application-commands#create-global-application-command + CreateGlobalApplicationCommand = "/applications/%s/commands", + + -- https://discord.com/developers/docs/interactions/application-commands#get-global-application-command + GetGlobalApplicationCommand = "/applications/%s/commands/%s", + + -- https://discord.com/developers/docs/interactions/application-commands#edit-global-application-command + EditGlobalApplicationCommand = "/applications/%s/commands/%s", + + -- https://discord.com/developers/docs/interactions/application-commands#delete-global-application-command + DeleteGlobalApplicationCommand = "/applications/%s/commands/%s", + + -- https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-global-application-commands + BulkOverwriteGlobalApplicationCommands = "/applications/%s/commands", + + -- https://discord.com/developers/docs/interactions/application-commands#get-guild-application-commands + GetGuildApplicationCommands = "/applications/%s/guilds/%s/commands", + + -- https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command + CreateGuildApplicationCommand = "/applications/%s/guilds/%s/commands", + + -- https://discord.com/developers/docs/interactions/application-commands#get-guild-application-command + GetGuildApplicationCommand = "/applications/%s/guilds/%s/commands/%s", + + -- https://discord.com/developers/docs/interactions/application-commands#edit-guild-application-command + EditGuildApplicationCommand = "/applications/%s/guilds/%s/commands/%s", + + -- https://discord.com/developers/docs/interactions/application-commands#delete-guild-application-command + DeleteGuildApplicationCommand = "/applications/%s/guilds/%s/commands/%s", + + -- https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-guild-application-commands + BulkOverwriteGuildApplicationCommands = "/applications/%s/guilds/%s/commands", + + -- https://discord.com/developers/docs/interactions/application-commands#get-guild-application-command-permissions + GetGuildApplicationCommandPermissions = "/applications/%s/guilds/%s/commands/permissions", + + -- https://discord.com/developers/docs/interactions/application-commands#get-application-command-permissions + GetApplicationCommandPermissions = "/applications/%s/guilds/%s/commands/%s/permissions", + + -- https://discord.com/developers/docs/interactions/application-commands#edit-application-command-permissions + EditApplicationCommandPermissions = "/applications/%s/guilds/%s/commands/%s/permissions", + + -- https://discord.com/developers/docs/resources/application#get-current-application + GetCurrentApplication = "/applications/@me", + + -- https://discord.com/developers/docs/resources/application#edit-current-application + EditCurrentApplication = "/applications/@me", + + -- https://discord.com/developers/docs/resources/application-role-connection-metadata#get-application-role-connection-metadata-records + GetApplicationRoleConnectionMetadataRecords = "/applications/%s/role-connections/metadata", + + -- https://discord.com/developers/docs/resources/application-role-connection-metadata#update-application-role-connection-metadata-records + UpdateApplicationRoleConnectionMetadataRecords = "/applications/%s/role-connections/metadata", + + -- https://discord.com/developers/docs/resources/audit-log#get-guild-audit-log + GetGuildAuditLog = "/guilds/%s/audit-logs", + + -- https://discord.com/developers/docs/resources/auto-moderation#list-auto-moderation-rules-for-guild + ListAutoModerationRulesForGuild = "/guilds/%s/auto-moderation/rules", + + -- https://discord.com/developers/docs/resources/auto-moderation#get-auto-moderation-rule + GetAutoModerationRule = "/guilds/%s/auto-moderation/rules/%s", + + -- https://discord.com/developers/docs/resources/auto-moderation#create-auto-moderation-rule + CreateAutoModerationRule = "/guilds/%s/auto-moderation/rules", + + -- https://discord.com/developers/docs/resources/auto-moderation#modify-auto-moderation-rule + ModifyAutoModerationRule = "/guilds/%s/auto-moderation/rules/%s", + + -- https://discord.com/developers/docs/resources/auto-moderation#delete-auto-moderation-rule + DeleteAutoModerationRule = "/guilds/%s/auto-moderation/rules/%s", + + -- https://discord.com/developers/docs/resources/channel#get-channel + GetChannel = "/channels/%s", + + -- https://discord.com/developers/docs/resources/channel#modify-channel + ModifyChannel = "/channels/%s", + + -- https://discord.com/developers/docs/resources/channel#deleteclose-channel + DeleteOrCloseChannel = "/channels/%s", + + -- https://discord.com/developers/docs/resources/channel#get-channel-messages + GetChannelMessages = "/channels/%s/messages", + + -- https://discord.com/developers/docs/resources/channel#get-channel-message + GetChannelMessage = "/channels/%s/messages/%s", + + -- https://discord.com/developers/docs/resources/channel#get-channel-message + CreateMessage = "/channels/%s/messages", + + -- https://discord.com/developers/docs/resources/channel#crosspost-message + CrosspostMessage = "/channels/%s/messages/%s/crosspost", + + -- https://discord.com/developers/docs/resources/channel#create-reaction + CreateReaction = "/channels/%s/messages/%s/reactions/%s/@me", + + -- https://discord.com/developers/docs/resources/channel#delete-own-reaction + DeleteOwnReaction = "/channels/%s/messages/%s/reactions/%s/@me", + + -- https://discord.com/developers/docs/resources/channel#delete-user-reaction + DeleteUserReaction = "/channels/%s/messages/%s/reactions/%s/%s", + + -- https://discord.com/developers/docs/resources/channel#get-reactions + GetReactions = "/channels/%s/messages/%s/reactions/%s", + + -- https://discord.com/developers/docs/resources/channel#delete-all-reactions + DeleteAllReactions = "/channels/%s/messages/%s/reactions", + + -- https://discord.com/developers/docs/resources/channel#delete-all-reactions-for-emoji + DeleteAllReactionsForEmoji = "/channels/%s/messages/%s/reactions/%s", + + -- https://discord.com/developers/docs/resources/channel#edit-message + EditMessage = "/channels/%s/messages/%s", + + -- https://discord.com/developers/docs/resources/channel#delete-message + DeleteMessage = "/channels/%s/messages/%s", + + -- https://discord.com/developers/docs/resources/channel#bulk-delete-messages + BulkDeleteMessages = "/channels/%s/messages/bulk-delete", + + -- https://discord.com/developers/docs/resources/channel#edit-channel-permissions + EditChannelPermissions = "/channels/%s/permissions/%s", + + -- https://discord.com/developers/docs/resources/channel#get-channel-invites + GetChannelInvites = "/channels/%s/invites", + + -- https://discord.com/developers/docs/resources/channel#create-channel-invite + CreateChannelInvite = "/channels/%s/invites", + + -- https://discord.com/developers/docs/resources/channel#delete-channel-permission + DeleteChannelPermission = "/channels/%s/permissions/%s", + + -- https://discord.com/developers/docs/resources/channel#follow-announcement-channel + FollowAnnouncementChannel = "/channels/%s/followers", + + -- https://discord.com/developers/docs/resources/channel#trigger-typing-indicator + TriggerTypingChannel = "/channels/%s/typing", + + -- https://discord.com/developers/docs/resources/channel#get-pinned-messages + GetPinnedMessages = "/channels/%s/pins", + + -- https://discord.com/developers/docs/resources/channel#get-pinned-messages + PinMessage = "/channels/%s/pins/%s", + + -- https://discord.com/developers/docs/resources/channel#unpin-message + UnpinMessage = "/channels/%s/pins/%s", + + -- https://discord.com/developers/docs/resources/channel#group-dm-add-recipient + GroupDMAddRecipient = "/channels/%s/recipients/%s", + + -- https://discord.com/developers/docs/resources/channel#group-dm-remove-recipient + GroupDMRemoveRecipient = "/channels/%s/recipients/%s", + + -- https://discord.com/developers/docs/resources/channel#start-thread-from-message + StartThreadFromMessage = "/channels/%s/messages/%s/threads", + + -- https://discord.com/developers/docs/resources/channel#start-thread-without-message + StartThreadWithoutMessage = "/channels/%s/threads", + + -- https://discord.com/developers/docs/resources/channel#start-thread-in-forum-or-media-channel + StartThreadInForumOrMediaChannel = "/channels/%s/threads", + + -- https://discord.com/developers/docs/resources/channel#join-thread + JoinThread = "/channels/%s/thread-members/@me", + + -- https://discord.com/developers/docs/resources/channel#add-thread-member + AddThreadMember = "/channels/%s/thread-members/%s", + + -- https://discord.com/developers/docs/resources/channel#leave-thread + LeaveThread = "/channels/%s/thread-members/@me", + + -- https://discord.com/developers/docs/resources/channel#remove-thread-member + RemoveThreadMember = "/channels/%s/thread-members/%s", + + -- https://discord.com/developers/docs/resources/channel#get-thread-member + GetThreadMember = "/channels/%s/thread-members/%s", + + -- https://discord.com/developers/docs/resources/channel#list-thread-members + ListThreadMembers = "/channels/%s/thread-members", + + -- https://discord.com/developers/docs/resources/channel#list-public-archived-threads + ListPublicArchivedThreads = "/channels/%s/threads/archived/public", + + -- https://discord.com/developers/docs/resources/channel#list-public-archived-threads + ListPrivateArchivedThreads = "/channels/%s/threads/archived/private", + + -- https://discord.com/developers/docs/resources/channel#list-joined-private-archived-threads + ListJoinedPrivateArchivedThreads = "/channels/%s/users/@me/threads/archived/private", + + -- https://discord.com/developers/docs/resources/emoji#list-guild-emojis + ListGuildEmojis = "/guilds/%s/emojis", + + -- https://discord.com/developers/docs/resources/emoji#get-guild-emoji + GetGuildEmoji = "/guilds/%s/emojis/%s", + + -- https://discord.com/developers/docs/resources/emoji#create-guild-emoji + CreateGuildEmoji = "/guilds/%s/emojis", + + -- https://discord.com/developers/docs/resources/emoji#modify-guild-emoji + ModifyGuildEmoji = "/guilds/%s/emojis/%s", + + -- https://discord.com/developers/docs/resources/emoji#delete-guild-emoji + DeleteGuildEmoji = "/guilds/%s/emojis/%s", + + -- https://discord.com/developers/docs/resources/guild#create-guild + CreateGuild = "/guilds", + + -- https://discord.com/developers/docs/resources/guild#get-guild + GetGuild = "/guilds/%s", + + -- https://discord.com/developers/docs/resources/guild#get-guild-preview + GetGuildPreview = "/guilds/%s/preview", + + -- https://discord.com/developers/docs/resources/guild#modify-guild + ModifyGuild = "/guilds/%s", + + -- https://discord.com/developers/docs/resources/guild#delete-guild + DeleteGuild = "/guilds/%s", + + -- https://discord.com/developers/docs/resources/guild#get-guild-channels + GetGuildChannels = "/guilds/%s/channels", + + -- https://discord.com/developers/docs/resources/guild#create-guild-channel + CreateGuildChannel = "/guilds/%s/channels", + + -- https://discord.com/developers/docs/resources/guild#modify-guild-channel-positions + ModifyGuildChannelPositions = "/guilds/%s/channels", + + -- https://discord.com/developers/docs/resources/guild#list-active-guild-threads + ListActiveGuildThreads = "/guilds/%s/threads/active", + + -- https://discord.com/developers/docs/resources/guild#get-guild-member + GetGuildMember = "/guilds/%s/members/%s", + + -- https://discord.com/developers/docs/resources/guild#list-guild-members + ListGuildMembers = "/guilds/%s/members?limit=%s&after=%s", + + -- https://discord.com/developers/docs/resources/guild#search-guild-members + SearchGuildMembers = "/guilds/%s/members/search?query=%s&limit=%s", + + -- https://discord.com/developers/docs/resources/guild#add-guild-member + AddGuildMember = "/guilds/%s/members/%s", + + -- https://discord.com/developers/docs/resources/guild#modify-guild-member + ModifyGuildMember = "/guilds/%s/members/%s", + + -- https://discord.com/developers/docs/resources/guild#modify-current-member + ModifyCurrentMember = "/guilds/%s/members/@me", + + -- https://discord.com/developers/docs/resources/guild#add-guild-member-role + AddGuildMemberRole = "/guilds/%s/members/%s/roles/%s", + + -- https://discord.com/developers/docs/resources/guild#remove-guild-member-role + RemoveGuildMemberRole = "/guilds/%s/members/%s/roles/%s", + + -- https://discord.com/developers/docs/resources/guild#remove-guild-member + RemoveGuildMember = "/guilds/%s/members/%s", + + -- https://discord.com/developers/docs/resources/guild#get-guild-bans + GetGuildBans = "/guilds/%s/bans?before=%s&after=%s&limit=%s", + + -- https://discord.com/developers/docs/resources/guild#get-guild-ban + GetGuildBan = "/guilds/%s/bans/%s", + + -- https://discord.com/developers/docs/resources/guild#create-guild-ban + CreateGuildBan = "/guilds/%s/bans/%s", + + -- https://discord.com/developers/docs/resources/guild#remove-guild-ban + RemoveGuildBan = "/guilds/%s/bans/%s", + + -- https://discord.com/developers/docs/resources/guild#bulk-guild-ban + BulkGuildBan = "/guilds/%s/bulk-ban", + + -- https://discord.com/developers/docs/resources/guild#get-guild-roles + GetGuildRoles = "/guilds/%s/roles", + + -- https://discord.com/developers/docs/resources/guild#create-guild-role + CreateGuildRole = "/guilds/%s/roles", + + -- https://discord.com/developers/docs/resources/guild#modify-guild-role-positions + ModifyGuildRolePositions = "/guilds/%s/roles", + + -- https://discord.com/developers/docs/resources/guild#modify-guild-role + ModifyGuildRole = "/guilds/%s/roles/%s", + + -- https://discord.com/developers/docs/resources/guild#modify-guild-mfa-level + ModifyGuildMFALevel = "/guilds/%s/mfa", + + -- https://discord.com/developers/docs/resources/guild#delete-guild-role + DeleteGuildRole = "/guilds/%s/roles/%s", + + -- https://discord.com/developers/docs/resources/guild#get-guild-prune-count + GetGuildPruneCount = "/guilds/%s/prune", + + -- https://discord.com/developers/docs/resources/guild#begin-guild-prune + BeginGuildPrune = "/guilds/%s/prune", + + -- https://discord.com/developers/docs/resources/guild#get-guild-voice-regions + GetGuildVoiceRegions = "/guilds/%s/regions", + + -- https://discord.com/developers/docs/resources/guild#get-guild-invites + GetGuildInvites = "/guilds/%s/invites", + + -- https://discord.com/developers/docs/resources/guild#get-guild-integrations + GetGuildIntegrations = "/guilds/%s/integrations", + + -- https://discord.com/developers/docs/resources/guild#delete-guild-integration + DeleteGuildIntegration = "/guilds/%s/integrations/%s", + + -- https://discord.com/developers/docs/resources/guild#get-guild-widget-settings + GetGuildWidgetSettings = "/guilds/%s/widget", + + -- https://discord.com/developers/docs/resources/guild#modify-guild-widget + ModifyGuildWidget = "/guilds/%s/widget", + + -- https://discord.com/developers/docs/resources/guild#get-guild-widget + GetGuildWidget = "/guilds/%s/widget.json", + + -- https://discord.com/developers/docs/resources/guild#get-guild-vanity-url + GetGuildVanityURL = "/guilds/%s/vanity-url", + + -- https://discord.com/developers/docs/resources/guild#get-guild-widget-image + GetGuildWidgetImage = "/guilds/%s/widget.png?style=%s", + + -- https://discord.com/developers/docs/resources/guild#get-guild-welcome-screen + GetGuildWelcomeScreen = "/guilds/%s/welcome-screen", + + -- https://discord.com/developers/docs/resources/guild#modify-guild-welcome-screen + ModifyGuildWelcomeScreen = "/guilds/%s/welcome-screen", + + -- https://discord.com/developers/docs/resources/guild#get-guild-onboarding + GetGuildOnboarding = "/guilds/%s/onboarding", + + -- https://discord.com/developers/docs/resources/guild#modify-guild-onboarding + ModifyGuildOnboarding = "/guilds/%s/onboarding", + + -- https://discord.com/developers/docs/resources/guild#modify-current-user-voice-state + ModifyCurrentUserVoiceState = "/guilds/%s/voice-states/@me", + + -- https://discord.com/developers/docs/resources/guild#modify-user-voice-state + ModifyUserVoiceState = "/guilds/%s/voice-states/%s", + + -- https://discord.com/developers/docs/resources/guild-scheduled-event#list-scheduled-events-for-guild + ListGuildScheduledForEvents = "/guilds/%s/scheduled-events", + + -- https://discord.com/developers/docs/resources/guild-scheduled-event#create-guild-scheduled-event + CreateGuildScheduledEvent = "/guilds/%s/scheduled-events", + + -- https://discord.com/developers/docs/resources/guild-scheduled-event#get-guild-scheduled-event + GetGuildScheduledEvent = "/guilds/%s/scheduled-events/%s", + + -- https://discord.com/developers/docs/resources/guild-scheduled-event#modify-guild-scheduled-event + ModifyGuildScheduledEvent = "/guilds/%s/scheduled-events/%s", + + -- https://discord.com/developers/docs/resources/guild-scheduled-event#delete-guild-scheduled-event + DeleteGuildScheduledEvent = "/guilds/%s/scheduled-events/%s", + + -- https://discord.com/developers/docs/resources/guild-scheduled-event#get-guild-scheduled-event-users + GetGuildScheduledEventUsers = "/guilds/%s/scheduled-events/%s/users", + + -- https://discord.com/developers/docs/resources/guild-template#get-guild-template + GetGuildTemplate = "/guilds/templates/%s", + + -- https://discord.com/developers/docs/resources/guild-template#create-guild-from-guild-template + CreateGuildFromTemplate = "/guilds/templates/%s", + + -- https://discord.com/developers/docs/resources/guild-template#get-guild-templates + GetGuildTemplates = "/guilds/%s/templates", + + -- https://discord.com/developers/docs/resources/guild-template#create-guild-template + CreateGuildTemplate = "/guilds/%s/templates", + + -- https://discord.com/developers/docs/resources/guild-template#sync-guild-template + SyncGuildTemplate = "/guilds/%s/templates/%s", + + -- https://discord.com/developers/docs/resources/guild-template#modify-guild-template + ModifyGuildTemplate = "/guilds/%s/templates/%s", + + -- https://discord.com/developers/docs/resources/guild-template#delete-guild-template + DeleteGuildTemplate = "/guilds/%s/templates/%s", + + -- https://discord.com/developers/docs/resources/invite#get-invite + GetInvite = "/invites/%s", + + -- https://discord.com/developers/docs/resources/invite#delete-invite + DeleteInvite = "/invites/%s", + + -- https://discord.com/developers/docs/resources/poll#get-answer-voters + GetAnswerVoters = "/channels/%s/polls/%s/answers/%s", + + -- https://discord.com/developers/docs/resources/poll#end-poll + EndPoll = "/channels/%s/polls/%s/expire", + + -- https://discord.com/developers/docs/resources/stage-instance#create-stage-instance + CreateStageInstance = "/stage-instances", + + -- https://discord.com/developers/docs/resources/stage-instance#get-stage-instance + GetStageInstance = "/stage-instances/%s", + + -- https://discord.com/developers/docs/resources/stage-instance#modify-stage-instance + ModifyStageInstance = "/stage-instances/%s", + + -- https://discord.com/developers/docs/resources/stage-instance#delete-stage-instance + DeleteStageInstance = "/stage-instances/%s", + + -- https://discord.com/developers/docs/resources/sticker#get-sticker + GetSticker = "/stickers/%s", + + -- https://discord.com/developers/docs/resources/sticker#list-sticker-packs + ListStickerPacks = "/sticker-packs", + + -- https://discord.com/developers/docs/resources/sticker#list-guild-stickers + ListGuildStickers = "/guilds/%s/stickers", + + -- https://discord.com/developers/docs/resources/sticker#get-guild-sticker + GetGuildSticker = "/guilds/%s/stickers/%s", + + -- https://discord.com/developers/docs/resources/voice#get-current-user-voice-state + GetCurrentUserVoiceState = "/guilds/%s/voice-states/@me", + + -- https://discord.com/developers/docs/resources/voice#get-user-voice-state + GetUserVoiceState = "/guilds/%s/voice-states/%s", + + -- https://discord.com/developers/docs/resources/sticker#create-guild-sticker + CreateGuildSticker = "/guilds/%s/stickers", + + -- https://discord.com/developers/docs/resources/sticker#modify-guild-sticker + ModifyGuildSticker = "/guilds/%s/stickers/%s", + + -- https://discord.com/developers/docs/resources/sticker#delete-guild-sticker + DeleteGuildSticker = "/guilds/%s/stickers/%s", + + -- https://discord.com/developers/docs/resources/user#get-current-user + GetCurrentUser = "/users/@me", + + -- https://discord.com/developers/docs/resources/user#get-user + GetUser = "/users/%s", + + -- https://discord.com/developers/docs/resources/user#modify-current-user + ModifyCurrentUser = "/users/@me", + + -- https://discord.com/developers/docs/resources/user#get-current-user-guilds + GetCurrentUserGuilds = "/users/@me/guilds", + + -- https://discord.com/developers/docs/resources/user#get-current-user-guild-member + GetCurrentUserGuildMember = "/users/@me/guilds/%s/member", + + -- https://discord.com/developers/docs/resources/user#leave-guild + LeaveGuild = "/users/@me/guilds/%s", + + -- https://discord.com/developers/docs/resources/user#create-dm + CreateDM = "/users/@me/channels", + + -- https://discord.com/developers/docs/resources/user#create-group-dm + CreateGroupDM = "/users/@me/channels", + + -- https://discord.com/developers/docs/resources/user#get-current-user-connections + GetCurrentUserConnections = "/users/@me/connections", + + -- https://discord.com/developers/docs/resources/user#get-current-user-application-role-connection + GetCurrentUserApplicationRolConnections = "/users/@me/applications/%s/role-connections", + + -- https://discord.com/developers/docs/resources/user#update-current-user-application-role-connection + UpdateCurrentUserApplicationRoleConnection = "/users/@me/applications/%s/role-connections/%s", + + -- https://discord.com/developers/docs/resources/voice#list-voice-regions + ListVoiceRegions = "/voice/regions", + + -- https://discord.com/developers/docs/resources/webhook#create-webhook + CreateWebhook = "/channels/%s/webhooks", + + -- https://discord.com/developers/docs/resources/webhook#get-channel-webhooks + GetChannelWebhooks = "/channels/%s/webhooks", + + -- https://discord.com/developers/docs/resources/webhook#get-guild-webhooks + GetGuildWebhooks = "/guilds/%s/webhooks", + + -- https://discord.com/developers/docs/resources/webhook#get-webhook + GetWebhook = "/webhooks/%s", + + -- https://discord.com/developers/docs/resources/webhook#get-webhook-with-token + GetWebhookWithToken = "/webhooks/%s/%s", + + -- https://discord.com/developers/docs/resources/webhook#modify-webhook + ModifyWebhook = "/webhooks/%s", + + -- https://discord.com/developers/docs/resources/webhook#modify-webhook-with-token + ModifyWebhookWithToken = "/webhooks/%s/%s", + + -- https://discord.com/developers/docs/resources/webhook#delete-webhook + DeleteWebhook = "/webhooks/%s", + + -- https://discord.com/developers/docs/resources/webhook#delete-webhook-with-token + DeleteWebhookWithToken = "/webhooks/%s/%s", + + -- https://discord.com/developers/docs/resources/webhook#execute-webhook + ExecuteWebhook = "/webhooks/%s/%s", + + -- https://discord.com/developers/docs/resources/webhook#execute-slackcompatible-webhook + ExecuteSlackCompatibleWebhook = "/webhooks/%s/%s/slack", + + -- https://discord.com/developers/docs/resources/webhook#execute-githubcompatible-webhook + ExecuteGithubCompatibleWebhook = "/webhooks/%s/%s/github", + + -- https://discord.com/developers/docs/resources/webhook#get-webhook-message + GetWebhookMessage = "/webhooks/%s/%s/messages/%s", + + -- https://discord.com/developers/docs/resources/webhook#edit-webhook-message + EditWebhookMessage = "/webhooks/%s/%s/messages/%s", + + -- https://discord.com/developers/docs/resources/webhook#delete-webhook-message + DeleteWebhookMessage = "/webhooks/%s/%s/messages/%s", + + -- https://discord.com/developers/docs/topics/gateway#get-gateway + GetGateway = "/gateway", + + -- https://discord.com/developers/docs/topics/gateway#get-gateway-bot + GetGatewayBot = "/gateway/bot", + + -- https://discord.com/developers/docs/topics/oauth2#get-current-bot-application-information + GetCurrentBotApplicationInformation = "/oauth2/applications/@me", + + -- https://discord.com/developers/docs/topics/oauth2#get-current-authorization-information + GetCurrentAuthorizationInformation = "/oauth2/@me", +}) + +export type Endpoint = + "CreateInteractionResponse" + | "GetOriginalInteractionResponse" + | "EditOriginalInteractionResponse" + | "DeleteOriginalInteractionResponse" + | "CreateFollowupMessage" + | "GetFollowupMessage" + | "EditFollowupMessage" + | "DeleteFollowupMessage" + | "GetGlobalApplicationCommands" + | "CreateGlobalApplicationCommand" + | "GetGlobalApplicationCommand" + | "EditGlobalApplicationCommand" + | "DeleteGlobalApplicationCommand" + | "BulkOverwriteGlobalApplicationCommands" + | "GetGuildApplicationCommands" + | "CreateGuildApplicationCommand" + | "GetGuildApplicationCommand" + | "EditGuildApplicationCommand" + | "DeleteGuildApplicationCommand" + | "BulkOverwriteGuildApplicationCommands" + | "GetGuildApplicationCommandPermissions" + | "GetApplicationCommandPermissions" + | "EditApplicationCommandPermissions" + | "GetCurrentApplication" + | "EditCurrentApplication" + | "GetApplicationRoleConnectionMetadataRecords" + | "UpdateApplicationRoleConnectionMetadataRecords" + | "GetGuildAuditLog" + | "BatchEditApplicationCommandPermissions" + | "ListAutoModerationRulesForGuild" + | "GetAutoModerationRule" + | "CreateAutoModerationRule" + | "ModifyAutoModerationRule" + | "DeleteAutoModerationRule" + | "GetChannel" + | "ModifyChannel" + | "DeleteOrCloseChannel" + | "GetChannelMessages" + | "GetChannelMessage" + | "CreateMessage" + | "CrosspostMessage" + | "CreateReaction" + | "DeleteOwnReaction" + | "DeleteUserReaction" + | "GetReactions" + | "DeleteAllReactions" + | "DeleteAllReactionsForEmoji" + | "EditMessage" + | "DeleteMessage" + | "BulkDeleteMessages" + | "EditChannelPermissions" + | "GetChannelInvites" + | "CreateChannelInvite" + | "DeleteChannelPermission" + | "FollowAnnouncementChannel" + | "TriggerTypingChannel" + | "GetPinnedMessages" + | "PinMessage" + | "UnpinMessage" + | "GroupDMAddRecipient" + | "GroupDMRemoveRecipient" + | "StartThreadFromMessage" + | "StartThreadWithoutMessage" + | "StartThreadInForumOrMediaChannel" + | "JoinThread" + | "AddThreadMember" + | "LeaveThread" + | "RemoveThreadMember" + | "GetThreadMember" + | "ListThreadMembers" + | "ListPublicArchivedThreads" + | "ListPrivateArchivedThreads" + | "ListJoinedPrivateArchivedThreads" + | "ListGuildEmojis" + | "GetGuildEmoji" + | "CreateGuildEmoji" + | "ModifyGuildEmoji" + | "DeleteGuildEmoji" + | "CreateGuild" + | "GetGuild" + | "GetGuildPreview" + | "ModifyGuild" + | "DeleteGuild" + | "GetGuildChannels" + | "CreateGuildChannel" + | "ModifyGuildChannelPositions" + | "ListActiveGuildThreads" + | "GetGuildMember" + | "ListGuildMembers" + | "SearchGuildMembers" + | "AddGuildMember" + | "ModifyGuildMember" + | "ModifyCurrentMember" + | "ModifyCurrentUserNick" + | "AddGuildMemberRole" + | "RemoveGuildMemberRole" + | "RemoveGuildMember" + | "GetGuildBans" + | "GetGuildBan" + | "CreateGuildBan" + | "RemoveGuildBan" + | "BulkGuildBan" + | "GetGuildRoles" + | "CreateGuildRole" + | "ModifyGuildRolePositions" + | "ModifyGuildRole" + | "ModifyGuildMFALevel" + | "DeleteGuildRole" + | "GetGuildPruneCount" + | "BeginGuildPrune" + | "GetGuildVoiceRegions" + | "GetGuildInvites" + | "GetGuildIntegrations" + | "DeleteGuildIntegration" + | "GetGuildWidgetSettings" + | "ModifyGuildWidget" + | "GetGuildWidget" + | "GetGuildVanityURL" + | "GetGuildWidgetImage" + | "GetGuildWelcomeScreen" + | "ModifyGuildWelcomeScreen" + | "GetGuildOnboarding" + | "ModifyGuildOnboarding" + | "ModifyCurrentUserVoiceState" + | "ModifyUserVoiceState" + | "ListGuildScheduledForEvents" + | "CreateGuildScheduledEvent" + | "GetGuildScheduledEvent" + | "ModifyGuildScheduledEvent" + | "DeleteGuildScheduledEvent" + | "GetGuildScheduledEventUsers" + | "GetGuildTemplate" + | "CreateGuildFromTemplate" + | "GetGuildTemplates" + | "CreateGuildTemplate" + | "SyncGuildTemplate" + | "ModifyGuildTemplate" + | "DeleteGuildTemplate" + | "GetInvite" + | "DeleteInvite" + | "GetAnswerVoters" + | "EndPoll" + | "CreateStageInstance" + | "GetStageInstance" + | "ModifyStageInstance" + | "DeleteStageInstance" + | "GetSticker" + | "ListStickerPacks" + | "ListGuildStickers" + | "GetGuildSticker" + | "CreateGuildSticker" + | "ModifyGuildSticker" + | "DeleteGuildSticker" + | "GetCurrentUser" + | "GetUser" + | "ModifyCurrentUser" + | "GetCurrentUserGuilds" + | "GetCurrentUserGuildMember" + | "LeaveGuild" + | "CreateDM" + | "CreateGroupDM" + | "GetCurrentUserConnections" + | "GetCurrentUserApplicationRolConnections" + | "UpdateCurrentUserApplicationRoleConnection" + | "ListVoiceRegions" + | "CreateWebhook" + | "GetChannelWebhooks" + | "GetGuildWebhooks" + | "GetWebhook" + | "GetWebhookWithToken" + | "ModifyWebhook" + | "ModifyWebhookWithToken" + | "DeleteWebhook" + | "DeleteWebhookWithToken" + | "ExecuteWebhook" + | "ExecuteSlackCompatibleWebhook" + | "ExecuteGithubCompatibleWebhook" + | "GetWebhookMessage" + | "EditWebhookMessage" + | "DeleteWebhookMessage" + | "GetGateway" + | "GetGatewayBot" + | "GetCurrentBotApplicationInformation" + | "GetCurrentAuthorizationInformation" + +return endpoints diff --git a/packages/api-types/src/rest/types.luau b/packages/api-types/src/rest/types.luau new file mode 100644 index 0000000..8aeafaf --- /dev/null +++ b/packages/api-types/src/rest/types.luau @@ -0,0 +1,1203 @@ +--[[ + Mapping out all discord responses that we can get from the Discord v10 REST/HTTP API. +]] + +local objects = require("@api-types/apiTypes") + +-- [[ Base ]] -- + +export type Response = DATA +export type Request = DATA + +-- [[ Requests ]] -- + +-- https://discord.com/developers/docs/interactions/receiving-and-responding#create-interaction-response +export type CreateInteractionRequest = Request + +-- https://discord.com/developers/docs/interactions/receiving-and-responding#edit-original-interaction-response +export type EditOriginalInteractionRequest = Request> + +-- https://discord.com/developers/docs/interactions/receiving-and-responding#create-followup-message +export type CreateFollowupMessageRequest = Request> + +-- https://discord.com/developers/docs/interactions/receiving-and-responding#edit-followup-message +export type EditFollowupMessageRequest = Request> + +-- https://discord.com/developers/docs/resources/auto-moderation#create-auto-moderation-rule +export type CreateAutoModerationRuleRequest = Request<{ + name: string, -- the rule name + event_type: objects.AutomoderationRuleEventType, -- the event type + trigger_type: objects.AutomoderationRuleTriggerType, -- the trigger type + trigger_metadata: objects.AutomoderationRuleTriggerMetadataObject?, -- the trigger metadata + actions: { objects.AutomoderationActionObject }, -- the actions which will execute when the rule is triggered + enabled: boolean?, -- whether the rule is enabled (False by default) + exempt_roles: { objects.Snowflake }?, -- the role ids that should not be affected by the rule (Maximum of 20) + exempt_channels: { objects.Snowflake }?, -- the channel ids that should not be affected by the rule (Maximum of 50) +}> + +-- https://discord.com/developers/docs/resources/auto-moderation#modify-auto-moderation-rule +export type ModifyAutoModerationRuleRequest = Request<{ + name: string, -- the rule name + event_type: objects.AutomoderationRuleEventType, -- the event type + trigger_metadata: objects.AutomoderationRuleTriggerMetadataObject?, -- the trigger metadata + actions: { objects.AutomoderationActionObject }, -- the actions which will execute when the rule is triggered + enabled: boolean?, -- whether the rule is enabled (False by default) + exempt_roles: { objects.Snowflake }?, -- the role ids that should not be affected by the rule (Maximum of 20) + exempt_channels: { objects.Snowflake }?, -- the channel ids that should not be affected by the rule (Maximum of 50) +}> + +-- https://discord.com/developers/docs/resources/application#edit-current-application +export type EditCurrentApplicationRequest = Request<{ + custom_install_url: string, -- Default custom authorization URL for the app, if enabled + description: string, -- Description of the app + role_connections_verification_url: string, -- Role connection verification URL for the app + install_params: objects.InstallParamsObject, -- Settings for the app's default in-app authorization link, if enabled + integration_types_config: { [objects.IntegrationType]: unknown }, -- In preview. Default scopes and permissions for each supported installation context. Value for each key is an integration type configuration object + flags: number, -- App's public flags + icon: string?, -- Icon for the app + cover_image: string?, -- Default rich presence invite cover image for the app + interactions_endpoint_url: string, -- Interactions endpoint URL for the app + tags: { string }, -- List of tags describing the content and functionality of the app (max of 20 characters per tag). Max of 5 tags. +}> + +-- https://discord.com/developers/docs/resources/channel#modify-channel +export type ModifyChannelRequest = Request<{ + name: string, -- 1-100 character channel name + type: objects.ChannelType, -- the type of channel; only conversion between text and announcement is supported and only in guilds with the "NEWS" feature + icon: string, -- (if a group DM, the base64 encoded icon for the group DM) + position: number?, -- the position of the channel in the left-hand listing (channels with the same position are sorted by id) + topic: string?, -- 0-1024 character channel topic (0-4096 characters for GUILD_FORUM and GUILD_MEDIA channels) + nsfw: boolean?, -- whether the channel is nsfw + rate_limit_per_user: number?, -- amount of seconds a user has to wait before sending another message (0-21600); bots, as well as users with the permission manage_messages or manage_channel, are unaffected + bitrate: number?, -- the bitrate (in bits) of the voice or stage channel; min 8000 + user_limit: number?, -- the user limit of the voice or stage channel, max 99 for voice channels and 10,000 for stage channels (0 refers to no limit) + permission_overwrites: { [objects.Snowflake]: objects.OverwriteObject }?, -- channel or category-specific permissions + parent_id: objects.Snowflake?, -- id of the new parent category for a channel + rtc_region: string?, -- channel voice region id, automatic when set to null + video_quality_mode: objects.VideoQualityMode, -- the camera video quality mode of the voice channel + default_auto_archive_duration: number?, -- the default duration that the clients use (not the API) for newly created threads in the channel, in minutes, to automatically archive the thread after recent activity + flags: number?, -- channel flags combined as a bitfield. Currently only REQUIRE_TAG (1 << 4) is supported by GUILD_FORUM and GUILD_MEDIA channels. HIDE_MEDIA_DOWNLOAD_OPTIONS (1 << 15) is supported only by GUILD_MEDIA channels + available_tags: { objects.ForumTagObject }?, -- the set of tags that can be used in a GUILD_FORUM or a GUILD_MEDIA channel; limited to 20 + default_reaction_emoji: objects.EmojiObject?, -- the emoji to show in the add reaction button on a thread in a GUILD_FORUM or a GUILD_MEDIA channel + default_thread_rate_limit_per_user: number?, -- the initial rate_limit_per_user to set on newly created threads in a channel. this field is copied to the thread at creation time and does not live update. + default_sort_order: objects.SortOrderType?, -- the default sort order type used to order posts in GUILD_FORUM and GUILD_MEDIA channels + default_forum_layout: objects.ForumLayoutType?, -- the default forum layout type used to display posts in GUILD_FORUM channels +}> + +-- https://discord.com/developers/docs/resources/channel#create-message +export type CreateMessageRequest = Request<{ + content: string?, -- Message contents (up to 2000 characters) + nonce: (string | number)?, -- Can be used to verify a message was sent (up to 25 characters). Value will appear in the Message Create event. + tts: boolean?, -- true if this is a TTS message + embeds: { objects.EmbedObject }?, -- Up to 10 rich embeds (up to 6000 characters) + allowed_mentions: { objects.AllowedMentionObject }?, -- Allowed mentions for the message + message_reference: objects.MessageReferenceObject?, -- Include to make your message a reply or a forward + components: { objects.ComponentObjects }?, -- Components to include with the message + sticker_ids: { objects.Snowflake }?, -- IDs of up to 3 stickers in the server to send in the message + -- files: { }, + -- payload_json: string, + attachments: { objects.AttachmentObject }?, -- Attachment objects with filename and description. See Uploading Files + flags: number?, -- Message flags combined as a bitfield (only SUPPRESS_EMBEDS and SUPPRESS_NOTIFICATIONS can be set) + enforce_nonse: boolean?, -- If true and nonce is present, it will be checked for uniqueness in the past few minutes. If another message was created by the same author with the same nonce, that message will be returned and no new message will be created. + poll: objects.PollObject?, -- The discord poll object +}> + +-- https://discord.com/developers/docs/resources/channel#edit-message +export type EditMessageRequest = Request<{ + content: string?, -- Message contents (up to 2000 characters) + embeds: { objects.EmbedObject }?, -- Up to 10 rich embeds (up to 6000 characters) + allowed_mentions: { objects.AllowedMentionObject }?, -- Allowed mentions for the message + components: { objects.ComponentObjects }?, -- Components to include with the message + -- files: { }, + -- payload_json: string, + attachments: { objects.AttachmentObject }?, -- Attachment objects with filename and description. See Uploading Files + flags: number?, -- Message flags combined as a bitfield (only SUPPRESS_EMBEDS and SUPPRESS_NOTIFICATIONS can be set) +}> + +-- https://discord.com/developers/docs/resources/channel#create-channel-invite +export type CreateChannelInviteRequest = Request<{ + max_age: number, -- duration of invite in seconds before expiry, or 0 for never. between 0 and 604800 (7 days) + max_uses: number, -- max number of uses or 0 for unlimited. between 0 and 100 + temporary: boolean, -- whether this invite only grants temporary membership + unique: boolean, -- if true, don't try to reuse a similar invite (useful for creating many unique one time use invites) + target_type: objects.InviteTargetTypes, -- the type of target for this voice channel invite + target_user_id: objects.Snowflake, -- the id of the user whose stream to display for this invite, required if target_type is 1, the user must be streaming in the channel + target_application_id: objects.Snowflake?, -- the id of the embedded application to open for this invite, required if target_type is 2, the application must have the EMBEDDED flag +}> + +-- https://discord.com/developers/docs/resources/channel#bulk-delete-messages +export type BulkDeleteMessagesRequest = Request<{ + messages: { objects.Snowflake }, +}> + +-- https://discord.com/developers/docs/resources/channel#follow-announcement-channel +export type FollowAnnouncementChannelRequest = Request<{ + webhook_channel_id: objects.Snowflake, -- id of target channel +}> + +-- https://discord.com/developers/docs/resources/channel#group-dm-add-recipient +export type GroupDMAddRecipientRequest = Request<{ + access_token: string, -- access token of a user that has granted your app the gdm.join scope + nick: string, -- nickname of the user being added +}> + +-- https://discord.com/developers/docs/resources/channel#start-thread-from-message +export type StartThreadFromMessageRequest = Request<{ + name: string, -- 1-100 character channel name + auto_archive_duration: number?, -- the thread will stop showing in the channel list after auto_archive_duration minutes of inactivity, can be set to: 60, 1440, 4320, 10080 + rate_limit_per_user: number?, -- amount of seconds a user has to wait before sending another message (0-21600) +}> + +-- https://discord.com/developers/docs/resources/channel#start-thread-without-message +export type StartThreadWithoutMessageRequest = Request<{ + name: string, -- 1-100 character channel name + auto_archive_duration: number?, -- the thread will stop showing in the channel list after auto_archive_duration minutes of inactivity, can be set to: 60, 1440, 4320, 10080 + type: objects.ChannelType?, -- the type of thread to create + invitable: boolean?, -- whether non-moderators can add other non-moderators to a thread; only available when creating a private thread + rate_limit_per_user: number?, -- amount of seconds a user has to wait before sending another message (0-21600) +}> + +-- https://discord.com/developers/docs/resources/channel#start-thread-in-forum-or-media-channel +export type StartThreadInForumOrMediaChannelRequest = Request<{ + name: string, -- 1-100 character channel name + auto_archive_duration: number?, -- the thread will stop showing in the channel list after auto_archive_duration minutes of inactivity, can be set to: 60, 1440, 4320, 10080 + rate_limit_per_user: number?, -- amount of seconds a user has to wait before sending another message (0-21600) + --files: { }, + -- payload_json: string, + applied_tags: { objects.Snowflake }, -- the IDs of the set of tags that have been applied to a thread in a GUILD_FORUM or a GUILD_MEDIA channel + message: objects.ForumAndMediaThreadMessageObject?, -- contents of the first message in the forum/media thread +}> + +-- https://discord.com/developers/docs/resources/emoji#create-guild-emoji +export type CreateGuildEmojiRequest = Request<{ + name: string, -- name of the emoji + image: string, -- the 128x128 emoji image + roles: { objects.Snowflake }, -- roles allowed to use this emoji +}> + +-- https://discord.com/developers/docs/resources/emoji#modify-guild-emoji +export type ModifyGuildEmojiRequest = Request<{ + name: string, -- name of the emoji + roles: { objects.Snowflake }, -- roles allowed to use this emoji +}> + +-- https://discord.com/developers/docs/resources/guild#create-guild +export type CreateGuildRequest = Request<{ + name: string, -- name of the guild (2-100 characters) + region: string?, -- voice region id (deprecated) + icon: string?, -- base64 128x128 image for the guild icon + verification_level: objects.VerificationLevel?, -- verification level + default_message_notifications: objects.DefaultMessageNotification?, -- default message notification level + explicit_content_filter: objects.ExplicitContentFilterLevel?, -- explicit content filter level + roles: { objects.GuildRoleObject }?, -- new guild roles + channels: { objects.ChannelObject }?, -- new guild's channels + afk_channel_id: objects.Snowflake, -- id for afk channel + afk_timeout: number, -- afk timeout in seconds, can be set to: 60, 300, 900, 1800, 3600 + system_channel_id: objects.Snowflake?, -- the id of the channel where guild notices such as welcome messages and boost events are posted + system_channel_flags: objects.SystemChannelFlags?, -- system channel flags +}> + +-- https://discord.com/developers/docs/resources/guild#modify-guild +export type ModifyGuildRequest = Request<{ + name: string, -- guild name + region: string?, -- guild voice region id (deprecated) + verificationn_level: objects.VerificationLevel?, -- verification level + default_message_notifications: objects.DefaultMessageNotification?, -- default message notification level + explicit_content_filter: objects.ExplicitContentFilterLevel?, -- explicit content filter level + afk_channel_id: objects.Snowflake?, -- id for afk channel + afk_timeout: number?, -- afk timeout in seconds, can be set to: 60, 300, 900, 1800, 3600 + icon: string?, -- base64 1024x1024 png/jpeg/gif image for the guild icon (can be animated gif when the server has the ANIMATED_ICON feature) + owner_id: objects.Snowflake, -- user id to transfer guild ownership to (must be owner) + splash: string?, -- base64 16:9 png/jpeg image for the guild splash (when the server has the INVITE_SPLASH feature) + discovery_splash: string?, -- base64 16:9 png/jpeg image for the guild discovery splash (when the server has the DISCOVERABLE feature) + banner: string?, -- base64 16:9 png/jpeg image for the guild banner (when the server has the BANNER feature; can be animated gif when the server has the ANIMATED_BANNER feature) + system_channel_id: objects.Snowflake?, -- the id of the channel where guild notices such as welcome messages and boost events are posted + system_channel_flags: objects.SystemChannelFlags?, -- system channel flags + rules_channel_id: objects.Snowflake?, -- the id of the channel where Community guilds display rules and/or guidelines + public_updates_channel_id: objects.Snowflake?, -- the id of the channel where admins and moderators of Community guilds receive notices from Discord + preferred_locale: objects.LanguageLocales?, -- the preferred locale of a Community guild used in server discovery and notices from Discord; defaults to "en-US" + features: { objects.GuildFeature }?, -- enabled guild features + description: string?, -- the description for the guild + premium_progress_bar_enabled: boolean?, -- whether the guild's boost progress bar should be enabled + safety_alerts_channel_id: objects.Snowflake?, -- the id of the channel where admins and moderators of Community guilds receive safety alerts from Discord +}> + +-- https://discord.com/developers/docs/resources/guild#create-guild-channel +export type CreateGuildChannelRequest = Request<{ + name: string, -- channel name (1-100 characters) + type: objects.ChannelType, -- the type of channel + topic: string?, -- channel topic (0-1024 characters) + bitrate: number?, -- the bitrate (in bits) of the voice or stage channel; min 8000 + user_limit: number?, -- the user limit of the voice channel + rate_limit_per_user: number?, -- amount of seconds a user has to wait before sending another message (0-21600); bots, as well as users with the permission manage_messages or manage_channel, are unaffected + position: number, -- sorting position of the channel (channels with the same position are sorted by id) + permission_overwrites: { objects.OverwriteObject }, -- the channel's permission overwrites + parent_id: objects.Snowflake?, -- id of the parent category for a channel + nsfw: boolean?, -- whether the channel is nsfw + rtc_region: string?, -- channel voice region id of the voice or stage channel, automatic when set to null + video_quality_mode: objects.VideoQualityMode?, -- the camera video quality mode of the voice channel + default_auto_archive_duration: number?, -- the default duration that the clients use (not the API) for newly created threads in the channel, in minutes, to automatically archive the thread after recent activity + default_reaction_emoji: objects.EmojiObject?, -- emoji to show in the add reaction button on a thread in a GUILD_FORUM or a GUILD_MEDIA channel + available_tags: { objects.ForumTagObject }?, -- set of tags that can be used in a GUILD_FORUM or a GUILD_MEDIA channel + default_sort_order: objects.SortOrderType?, -- the default sort order type used to order posts in GUILD_FORUM and GUILD_MEDIA channels + default_forum_layout: objects.ForumLayoutType?, -- the default forum layout view used to display posts in GUILD_FORUM channels + default_thread_rate_limit_per_user: number?, -- the initial rate_limit_per_user to set on newly created threads in a channel. this field is copied to the thread at creation time and does not live update. +}> + +-- https://discord.com/developers/docs/resources/guild#modify-guild-channel-positions +export type ModifyGuildChannelPositionsRequest = Request<{ + id: objects.Snowflake, -- channel id + position: number?, -- sorting position of the channel (channels with the same position are sorted by id) + lock_permissions: boolean?, -- syncs the permission overwrites with the new parent, if moving to a new category + parent_id: objects.Snowflake?, -- the new parent ID for the channel that is moved +}> + +-- https://discord.com/developers/docs/resources/guild#add-guild-member +export type AddGuildMemberRequest = Request<{ + access_token: string, -- an oauth2 access token granted with the guilds.join to the bot's application for the user you want to add to the guild + nick: string, -- value to set user's nickname to + roles: { objects.Snowflake }, -- array of role ids the member is assigned + mute: boolean, -- whether the user is muted in voice channels + deaf: boolean, -- whether the user is deafened in voice channels +}> + +-- https://discord.com/developers/docs/resources/guild#modify-guild-member +export type ModifyGuildMemberRequest = Request<{ + nick: string, -- value to set user's nickname to + roles: { objects.Snowflake }, -- array of role ids the member is assigned + mute: boolean, -- whether the user is muted in voice channels. Will throw a 400 error if the user is not in a voice channel + deaf: boolean, -- whether the user is deafened in voice channels. Will throw a 400 error if the user is not in a voice channel + channel_id: objects.Snowflake, -- id of channel to move user to (if they are connected to voice) + communication_disabled_until: string?, -- when the user's timeout will expire and the user will be able to communicate in the guild again (up to 28 days in the future), set to null to remove timeout. Will throw a 403 error if the user has the ADMINISTRATOR permission or is the owner of the guild + flags: objects.GuildMemberFlags?, -- guild member flags +}> + +-- https://discord.com/developers/docs/resources/guild#modify-current-member +export type ModifyCurrentMemberRequest = Request<{ + nick: string, -- value to set user's nickname to +}> + +-- https://discord.com/developers/docs/resources/guild#create-guild-ban +export type CreateGuildBanRequest = Request<{ + delete_message_days: number?, -- number of days to delete messages for (0-7) (deprecated) + delete_message_seconds: number?, -- number of seconds to delete messages for, between 0 and 604800 (7 days) +}> + +-- https://discord.com/developers/docs/resources/guild#bulk-guild-ban +export type BulkGuildBanRequest = Request<{ + user_ids: { objects.Snowflake }, -- list of user ids to ban (max 200) + delete_message_seconds: number, -- number of seconds to delete messages for, between 0 and 604800 (7 days) +}> + +-- https://discord.com/developers/docs/resources/guild#create-guild-role +export type CreateGuildRoleRequest = Request<{ + name: string, -- name of the role, max 100 characters + permissions: string, -- bitwise value of the enabled/disabled permissions + color: number, -- RGB color value + hoist: boolean, -- whether the role should be displayed separately in the sidebar + icon: string, -- the role's icon image (if the guild has the ROLE_ICONS feature) + unicode_emoji: string, -- the role's unicode emoji as a standard emoji (if the guild has the ROLE_ICONS feature) + mentionable: boolean, -- whether the role should be mentionable +}> + +-- https://discord.com/developers/docs/resources/guild#modify-guild-role-positions +export type ModifyGuildRolePositionsRequest = Request<{ + id: objects.Snowflake, -- the target role id + position: number, -- sorting position of the role (roles with the same position are sorted by id) +}> + +-- https://discord.com/developers/docs/resources/guild#modify-guild-role +export type ModifyGuildRoleRequest = Request<{ + name: string, -- name of the role, max 100 characters + permissions: string, -- bitwise value of the enabled/disabled permissions + color: number, -- RGB color value + hoist: boolean, -- whether the role should be displayed separately in the sidebar + icon: string, -- the role's icon image (if the guild has the ROLE_ICONS feature) + unicode_emoji: string, -- the role's unicode emoji as a standard emoji (if the guild has the ROLE_ICONS feature) + mentionabl: boolean, -- whether the role should be mentionable +}> + +-- https://discord.com/developers/docs/resources/guild#modify-guild-mfa-level +export type ModifyGuildMFALevelRequest = Request<{ + level: objects.MFALevel, -- The updated MFA level +}> + +-- https://discord.com/developers/docs/resources/guild#begin-guild-prune +export type BeginGuildPruneRequest = Request<{ + days: number, -- number of days to prune (1-30) + compute_prune_count: boolean, -- whether pruned is returned, discouraged for large guilds + include_ruoles: { objects.Snowflake }, -- role(s) to include + reason: string?, -- reason for the prune (deprecated) +}> + +-- https://discord.com/developers/docs/resources/guild#modify-guild-welcome-screen +export type ModifyGuildWelcomeScreenRequest = Request<{ + enabled: boolean, -- whether the welcome screen is enabled + welcome_channels: { objects.WelcomeScreenChannelObject }, -- channels linked in the welcome screen and their display options + description: string, -- the server description to show in the welcome screen +}> + +-- https://discord.com/developers/docs/resources/guild#modify-guild-onboarding +export type ModifyGuildOnboardingRequest = Request<{ + prompts: { objects.OnboardingPromptObject }, -- Prompts shown during onboarding and in customize community + default_channel_ids: { objects.Snowflake }, -- Channel IDs that members get opted into automatically + enabled: boolean, -- Whether onboarding is enabled in the guild + mode: objects.OnboardingMode, -- Current mode of onboarding +}> + +-- https://discord.com/developers/docs/resources/guild#modify-current-user-voice-state +export type ModifyCurrentUserVoiceStateRequest = Request<{ + channel_id: objects.Snowflake?, -- the id of the channel the user is currently in + suppress: boolean?, -- toggles the user's suppress state + request_to_speak_timestamp: string?, -- sets the user's request to speak +}> + +-- https://discord.com/developers/docs/resources/guild#modify-user-voice-state +export type ModifyUserVoiceStateRequest = Request<{ + channel_id: objects.Snowflake?, -- the id of the channel the user is currently in + suppress: boolean?, -- toggles the user's suppress state +}> + +-- https://discord.com/developers/docs/resources/guild-scheduled-event#create-guild-scheduled-event +export type CreateGuildScheduledEventRequest = Request<{ + channel_id: objects.Snowflake?, -- the channel id of the scheduled event. + entity_metadata: objects.GuildScheduledEventEntityMetadata, -- the entity metadata of the scheduled event + name: string, -- the name of the scheduled event + privacy_level: objects.PrivacyLevel, -- the privacy level of the scheduled event + scheduled_start_time: string, -- the time to schedule the scheduled event + scheduled_end_time: string?, -- the time when the scheduled event is scheduled to end + description: string?, -- the description of the scheduled event + entity_type: objects.GuildScheduledEventEntityType, -- the entity type of the scheduled event + image: string?, -- the cover image of the scheduled event +}> + +-- https://discord.com/developers/docs/resources/guild-scheduled-event#modify-guild-scheduled-event +export type ModifyGuildScheduledEventRequest = Request<{ + channel_id: objects.Snowflake?, -- the channel id of the scheduled event. + entity_metadata: objects.GuildScheduledEventEntityMetadata, -- the entity metadata of the scheduled event + name: string, -- the name of the scheduled event + privacy_level: objects.PrivacyLevel, -- the privacy level of the scheduled event + scheduled_start_time: string, -- the time to schedule the scheduled event + scheduled_end_time: string?, -- the time when the scheduled event is scheduled to end + description: string?, -- the description of the scheduled event + entity_type: objects.GuildScheduledEventEntityType, -- the entity type of the scheduled event + status: objects.GuildScheduledEventStatus?, -- the status of the scheduled event + image: string?, -- the cover image of the scheduled event +}> + +-- https://discord.com/developers/docs/resources/guild-template#create-guild-from-guild-template +export type CreateGuildFromGuildTemplateRequest = Request<{ + name: string, -- name of the guild (2-100 characters) + icon: string?, -- base64 128x128 image for the guild icon +}> + +-- https://discord.com/developers/docs/resources/guild-template#create-guild-template +export type CreateGuildTemplateRequest = Request<{ + name: string, -- name of the template (1-100 characters) + description: string?, -- description for the template (0-120 characters) +}> + +-- https://discord.com/developers/docs/resources/guild-template#modify-guild-template +export type ModifyGuildTemplateRequest = Request<{ + name: string?, -- name of the template (1-100 characters) + description: string?, -- description for the template (0-120 characters) +}> + +-- https://discord.com/developers/docs/resources/stage-instance#create-stage-instance +export type CreateStageInstanceRequest = Request<{ + channel_id: objects.Snowflake, -- The id of the Stage channel + topic: string, -- The topic of the Stage instance (1-120 characters) + privacy_level: objects.PrivacyLevel?, -- The privacy level of the Stage instance (default GUILD_ONLY) + send_start_notification: boolean?, -- Notify @everyone that a Stage instance has started + guild_scheduled_event_id: objects.Snowflake?, -- The guild scheduled event associated with this Stage instance +}> + +-- https://discord.com/developers/docs/resources/stage-instance#modify-stage-instance +export type ModifyStageInstanceRequest = Request<{ + topic: string, -- The topic of the Stage instance (1-120 characters) + privacy_level: objects.PrivacyLevel, -- The privacy level of the Stage instance +}> + +-- https://discord.com/developers/docs/resources/sticker#create-guild-sticker +export type CreateGuildStickerRequest = Request<{ + name: string, -- name of the sticker (2-30 characters) + description: string, -- description of the sticker (empty or 2-100 characters) + tags: { string }, -- autocomplete/suggestion tags for the sticker (max 200 characters) + file: string, -- the sticker file to upload, must be a PNG, APNG, GIF, or Lottie JSON file, max 512 KiB +}> + +-- https://discord.com/developers/docs/resources/sticker#modify-guild-sticker +export type ModifyGuildStickerRequest = Request<{ + name: string, -- name of the sticker (2-30 characters) + description: string?, -- description of the sticker (2-100 characters) + tags: string, -- autocomplete/suggestion tags for the sticker (max 200 characters) +}> + +-- https://discord.com/developers/docs/resources/user#modify-current-user +export type ModifyCurrentUserRequest = Request<{ + username: string, -- user's username, if changed may cause the user's discriminator to be randomized. + avatar: string?, -- if passed, modifies the user's avatar + banner: string?, -- if passed, modifies the user's banner +}> + +-- https://discord.com/developers/docs/resources/user#create-dm +export type CreateDMRequest = Request<{ + recipient_id: objects.Snowflake, -- the recipient to open a DM channel with +}> + +-- https://discord.com/developers/docs/resources/user#create-group-dm +export type CreateGroupDMRequest = Request<{ + access_tokens: { string }, -- access tokens of users that have granted your app the gdm.join scope + nicks: { [objects.Snowflake]: string }, -- a dictionary of user ids to their respective nicknames +}> + +-- https://discord.com/developers/docs/resources/user#update-current-user-application-role-connection +export type UpdateCurrentUserApplicationRoleConnectionRequest = Request<{ + platform_name: string?, -- the vanity name of the platform a bot has connected (max 50 characters) + platform_username: string?, -- the username on the platform a bot has connected (max 100 characters) + metadata: objects.ApplicationRoleConnectionMetadataObject?, -- object mapping application role connection metadata keys to their string-ified value (max 100 characters) for the user on the platform a bot has connected +}> + +-- https://discord.com/developers/docs/resources/webhook#create-webhook +export type CreateWebhookRequest = Request<{ + name: string, -- name of the webhook (1-80 characters) + avatar: string?, -- image for the default webhook avatar +}> + +-- https://discord.com/developers/docs/resources/webhook#modify-webhook +export type ModifyWebhookRequest = Request<{ + name: string, + avatar: string, + channel_id: objects.Snowflake, +}> + +-- https://discord.com/developers/docs/resources/webhook#execute-webhook +export type ExecuteWebhookRequest = Request<{ + content: string, -- the message contents (up to 2000 characters) + username: string, -- override the default username of the webhook + avatar_url: string, -- override the default avatar of the webhook + tts: boolean, -- true if this is a TTS message + embeds: { objects.EmbedObject }, -- embedded rich content + allowed_mentions: objects.AllowedMentionObject, -- allowed mentions for the message + components: { objects.ComponentObjects }, -- the components to include with the message + -- files: { }, + -- payload_json: string, + attachments: { objects.AttachmentObject }, -- attachment objects with filename and description + flags: number, -- message flags combined as a bitfield (only SUPPRESS_EMBEDS and SUPPRESS_NOTIFICATIONS can be set can be set) + thread_name: string, -- name of thread to create (requires the webhook channel to be a forum or media channel) + applied_tags: { objects.Snowflake }, -- array of tag ids to apply to the thread (requires the webhook channel to be a forum or media channel) + poll: objects.PollObject, -- discord poll +}> + +-- https://discord.com/developers/docs/resources/webhook#execute-slackcompatible-webhook +export type ExecuteSlackCompatibleWebhookRequest = Request<{ + -- lol i dont want to look into block kit.. please use execute webhook request + -- if that's not an option, feel free to contribute; https://api.slack.com/block-kit +}> + +-- https://discord.com/developers/docs/resources/webhook#execute-githubcompatible-webhook +export type ExecuteGithubCompatibleWebhookRequest = Request<{ + -- ok, likewise with the slack comment, feel free to contribute; https://docs.github.com/en/webhooks/webhook-events-and-payloads +}> + +-- https://discord.com/developers/docs/resources/webhook#edit-webhook-message +export type EditWebhookMessageRequest = Request<{ + content: string, -- the message contents (up to 2000 characters) + embeds: { objects.EmbedObject }, -- embedded rich content + allowed_mentions: objects.AllowedMentionObject, -- allowed mentions for the message + components: { objects.ComponentObjects }, -- the components to include with the message + -- files: { }, + -- payload_json: string, + attachments: objects.AttachmentObject, -- attached files to keep and possible descriptions for new files +}> + +-- https://discord.com/developers/docs/resources/channel#edit-channel-permissions +export type EditChannelPermissionsRequest = Request<{ + allow: string?, -- the bitwise value of all allowed permissions (default "0") + deny: string?, -- the bitwise value of all disallowed permissions (default "0") + type: number, -- 0 for a role or 1 for a member +}> + +-- https://discord.com/developers/docs/resources/guild#modify-guild-widget +export type ModifyGuildWidgetRequest = Request + +-- https://discord.com/developers/docs/resources/channel#crosspost-message +export type CrosspostMessageRequest = Request + +-- https://discord.com/developers/docs/interactions/application-commands#create-global-application-command +export type CreateGlobalApplicationCommandRequest = Request<{ + name: string, -- Name of command, 1-32 characters + name_localizations: { [objects.LanguageLocales]: string }?, -- Localization dictionary for the name field. Values follow the same restrictions as name + description: string?, -- 1-100 character description for CHAT_INPUT commands + description_localizations: { [objects.LanguageLocales]: string }?, -- Localization dictionary for the description field. Values follow the same restrictions as description + options: { objects.ApplicationCommandOptionObject }?, -- the parameters for the command, max of 25 + default_member_permissions: string?, -- Set of permissions represented as a bit set + dm_permission: boolean?, -- Deprecated (use contexts instead); Indicates whether the command is available in DMs with the app, only for globally-scoped commands. By default, commands are visible. + default_permission: boolean, -- Replaced by default_member_permissions and will be deprecated in the future. Indicates whether the command is enabled by default when the app is added to a guild. Defaults to true + integration_types: { objects.IntegrationType }?, -- Installation context(s) where the command is available + contexts: { objects.InteractionContextType }?, -- Interaction context(s) where the command can be used + type: objects.ApplicationCommandType?, -- Type of command, defaults 1 if not set + nsfw: boolean?, -- Indicates whether the command is age-restricted +}> + +-- https://discord.com/developers/docs/interactions/application-commands#edit-global-application-command +export type EditGlobalApplicationCommandRequest = Request<{ + name: string, -- Name of command, 1-32 characters + name_localizations: { [objects.LanguageLocales]: string }?, -- Localization dictionary for the name field. Values follow the same restrictions as name + description: string?, -- 1-100 character description for CHAT_INPUT commands + description_localizations: { [objects.LanguageLocales]: string }?, -- Localization dictionary for the description field. Values follow the same restrictions as description + options: { objects.ApplicationCommandOptionObject }?, -- the parameters for the command, max of 25 + default_member_permissions: string?, -- Set of permissions represented as a bit set + dm_permission: boolean?, -- Deprecated (use contexts instead); Indicates whether the command is available in DMs with the app, only for globally-scoped commands. By default, commands are visible. + default_permission: boolean, -- Replaced by default_member_permissions and will be deprecated in the future. Indicates whether the command is enabled by default when the app is added to a guild. Defaults to true + integration_types: { objects.IntegrationType }?, -- Installation context(s) where the command is available + contexts: { objects.InteractionContextType }?, -- Interaction context(s) where the command can be used + nsfw: boolean?, -- Indicates whether the command is age-restricted +}> + +-- https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command +export type CreateGuildApplicationCommandRequest = Request<{ + name: string, -- Name of command, 1-32 characters + name_localizations: { [objects.LanguageLocales]: string }?, -- Localization dictionary for the name field. Values follow the same restrictions as name + description: string?, -- 1-100 character description for CHAT_INPUT commands + description_localizations: { [objects.LanguageLocales]: string }?, -- Localization dictionary for the description field. Values follow the same restrictions as description + options: { objects.ApplicationCommandOptionObject }?, -- the parameters for the command, max of 25 + default_member_permissions: string?, -- Set of permissions represented as a bit set + dm_permission: boolean?, -- Deprecated (use contexts instead); Indicates whether the command is available in DMs with the app, only for globally-scoped commands. By default, commands are visible. + default_permission: boolean, -- Replaced by default_member_permissions and will be deprecated in the future. Indicates whether the command is enabled by default when the app is added to a guild. Defaults to true + integration_types: { objects.IntegrationType }?, -- Installation context(s) where the command is available + contexts: { objects.InteractionContextType }?, -- Interaction context(s) where the command can be used + type: objects.ApplicationCommandType?, -- Type of command, defaults 1 if not set + nsfw: boolean?, -- Indicates whether the command is age-restricted +}> + +-- https://discord.com/developers/docs/interactions/application-commands#edit-guild-application-command +export type EditGuildApplicationCommandRequest = Request<{ + name: string, -- Name of command, 1-32 characters + name_localizations: { [objects.LanguageLocales]: string }?, -- Localization dictionary for the name field. Values follow the same restrictions as name + description: string?, -- 1-100 character description for CHAT_INPUT commands + description_localizations: { [objects.LanguageLocales]: string }?, -- Localization dictionary for the description field. Values follow the same restrictions as description + options: { objects.ApplicationCommandOptionObject }?, -- the parameters for the command, max of 25 + default_member_permissions: string?, -- Set of permissions represented as a bit set + dm_permission: boolean?, -- Deprecated (use contexts instead); Indicates whether the command is available in DMs with the app, only for globally-scoped commands. By default, commands are visible. + default_permission: boolean, -- Replaced by default_member_permissions and will be deprecated in the future. Indicates whether the command is enabled by default when the app is added to a guild. Defaults to true + integration_types: { objects.IntegrationType }?, -- Installation context(s) where the command is available + contexts: { objects.InteractionContextType }?, -- Interaction context(s) where the command can be used + nsfw: boolean?, -- Indicates whether the command is age-restricted +}> + +-- https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-global-application-commands +export type BulkOverwriteGlobalApplicationCommandsRequest = Request<{ objects.ApplicationCommandObject }> + +-- https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-guild-application-commands +export type BulkOverwriteGuildApplicationCommandsRequest = Request< + { + { + id: objects.Snowflake?, -- ID of the command, if known + name: string, -- Name of command, 1-32 characters + name_localizations: { [objects.LanguageLocales]: string }?, -- Localization dictionary for the name field. Values follow the same restrictions as name + description: string?, -- 1-100 character description for CHAT_INPUT commands + description_localizations: { [objects.LanguageLocales]: string }?, -- Localization dictionary for the description field. Values follow the same restrictions as description + options: { objects.ApplicationCommandOptionObject }?, -- the parameters for the command, max of 25 + default_member_permissions: string?, -- Set of permissions represented as a bit set + dm_permission: boolean?, -- Deprecated (use contexts instead); Indicates whether the command is available in DMs with the app, only for globally-scoped commands. By default, commands are visible. + default_permission: boolean, -- Replaced by default_member_permissions and will be deprecated in the future. Indicates whether the command is enabled by default when the app is added to a guild. Defaults to true + integration_types: { objects.IntegrationType }?, -- Installation context(s) where the command is available + contexts: { objects.InteractionContextType }?, -- Interaction context(s) where the command can be used + type: objects.ApplicationCommandType?, -- Type of command, defaults 1 if not set + nsfw: boolean?, -- Indicates whether the command is age-restricted + } + } +> + +-- https://discord.com/developers/docs/interactions/application-commands#edit-application-command-permissions +export type EditApplicationCommandPermissionsRequest = Request<{ + permissions: { objects.GuildApplicationCommandPermissionObject }, +}> + +-- [[ Responses ]] -- + +-- https://discord.com/developers/docs/resources/voice#get-current-user-voice-state +export type GetCurrentUserVoiceStateResponse = Response + +-- https://discord.com/developers/docs/resources/voice#get-user-voice-state +export type GetUserVoiceStateResponse = Response + +-- https://discord.com/developers/docs/interactions/application-commands#get-global-application-commands +export type GetGlobalApplicationCommandsResponse = Response<{ objects.ApplicationCommandObject }> + +-- https://discord.com/developers/docs/interactions/application-commands#create-global-application-command +export type CreateGlobalApplicationCommandResponse = Response + +-- https://discord.com/developers/docs/interactions/application-commands#get-global-application-command +export type GetGlobalApplicationCommandResponse = Response + +-- https://discord.com/developers/docs/interactions/application-commands#edit-global-application-command +export type EditGlobalApplicationCommandResponse = Response + +-- https://discord.com/developers/docs/interactions/application-commands#delete-global-application-command +export type DeleteGlobalApplicationCommandResponse = Response + +-- https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-global-application-commands +export type BulkOverwriteGlobalApplicationCommandsResponse = Response<{ objects.ApplicationCommandObject }> + +-- https://discord.com/developers/docs/interactions/application-commands#get-guild-application-commands +export type GetGuildApplicationCommandsResponse = Response<{ objects.ApplicationCommandObject }> + +-- https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command +export type CreateGuildApplicationCommandResponse = Response + +-- https://discord.com/developers/docs/interactions/application-commands#get-guild-application-command +export type GetGuildApplicationCommandResponse = Response + +-- https://discord.com/developers/docs/interactions/application-commands#edit-guild-application-command +export type EditGuildApplicationCommandResponse = Response + +-- https://discord.com/developers/docs/interactions/application-commands#delete-guild-application-command +export type DeleteGuildApplicationCommandResponse = Response + +-- https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-guild-application-commands +export type BulkOverwriteGuildApplicationCommandsResponse = Response<{ objects.ApplicationCommandObject }> + +-- https://discord.com/developers/docs/interactions/application-commands#get-guild-application-command-permissions +export type GetGuildApplicationCommandPermissionsResponse = Response< + { objects.GuildApplicationCommandPermissionsObject } +> + +-- https://discord.com/developers/docs/interactions/application-commands#get-application-command-permissions +export type GetApplicationCommandPermissionsResponse = Response<{ objects.GuildApplicationCommandPermissionsObject }> + +-- https://discord.com/developers/docs/interactions/application-commands#edit-application-command-permissions +export type EditApplicationCommandPermissionsResponse = Response + +-- https://discord.com/developers/docs/resources/application#get-current-application +export type GetCurrentApplicationResponse = Response<{ objects.ApplicationObject }> + +-- https://discord.com/developers/docs/resources/application#edit-current-application +export type EditCurrentApplicationResponse = Response<{ objects.ApplicationObject }> + +-- https://discord.com/developers/docs/resources/application-role-connection-metadata#get-application-role-connection-metadata-records +export type GetApplicationRoleConnectionMetadataRecordsResponse = Response< + objects.ApplicationRoleConnectionMetadataObject +> + +-- https://discord.com/developers/docs/resources/application-role-connection-metadata#update-application-role-connection-metadata-records +export type UpdateApplicationRoleConnectionMetadataRecordsResponse = Response< + objects.ApplicationRoleConnectionMetadataObject +> + +-- https://discord.com/developers/docs/resources/audit-log#get-guild-audit-log +export type GetGuildAuditLogResponse = Response + +-- https://discord.com/developers/docs/resources/auto-moderation#list-auto-moderation-rules-for-guild +export type ListAutoModerationRulesForGuildResponse = Response<{ objects.AutomoderationRuleObject }> + +-- https://discord.com/developers/docs/resources/auto-moderation#get-auto-moderation-rule +export type GetAutoModerationRuleResponse = Response + +-- https://discord.com/developers/docs/resources/auto-moderation#create-auto-moderation-rule +export type CreateAutoModerationRuleResponse = Response + +-- https://discord.com/developers/docs/resources/auto-moderation#modify-auto-moderation-rule +export type ModifyAutoModerationRuleResponse = Response + +-- https://discord.com/developers/docs/resources/auto-moderation#delete-auto-moderation-rule +export type DeleteAutoModerationRuleResponse = Response + +-- https://discord.com/developers/docs/resources/channel#get-channel +export type GetChannelResponse = Response + +-- https://discord.com/developers/docs/resources/channel#modify-channel +export type ModifyChannelResponse = Response + +-- https://discord.com/developers/docs/resources/channel#deleteclose-channel +export type DeleteCloseChannelResponse = Response + +-- https://discord.com/developers/docs/resources/channel#get-channel-messages +export type GetChannelMessagesResponse = Response<{ objects.MessageObject }> + +-- https://discord.com/developers/docs/resources/channel#get-channel-message +export type GetChannelMessageResponse = Response + +-- https://discord.com/developers/docs/resources/channel#create-message +export type CreateMessageResponse = Response + +-- https://discord.com/developers/docs/resources/channel#crosspost-message +export type CrosspostMessageResponse = Response + +-- https://discord.com/developers/docs/resources/channel#create-reaction +export type CreateReactionResponse = Response + +-- https://discord.com/developers/docs/resources/channel#delete-own-reaction +export type DeleteOwnReactionResponse = Response + +-- https://discord.com/developers/docs/resources/channel#delete-user-reaction +export type DeleteUserReactionResponse = Response + +-- https://discord.com/developers/docs/resources/channel#get-reactions +export type GetReactionsResponse = Response<{ objects.UserObject }> + +-- https://discord.com/developers/docs/resources/channel#delete-all-reactions +export type DeleteAllReactionsResponse = Response + +-- https://discord.com/developers/docs/resources/channel#delete-all-reactions-for-emoji +export type DeleteAllReactionsForEmojiResponse = Response + +-- https://discord.com/developers/docs/resources/channel#edit-message +export type EditMessageResponse = Response + +-- https://discord.com/developers/docs/resources/channel#delete-message +export type DeleteMessageResponse = Response + +-- https://discord.com/developers/docs/resources/channel#bulk-delete-messages +export type BulkDeleteMessageResponse = Response + +-- https://discord.com/developers/docs/resources/channel#edit-channel-permissions +export type EditChannelPermissionsResponse = Response + +-- https://discord.com/developers/docs/resources/channel#get-channel-invites +export type GetChannelInvitesResponse = Response<{ objects.InviteObject & objects.InviteMetadataObject }> + +-- https://discord.com/developers/docs/resources/channel#create-channel-invite +export type CreateChannelInviteResponse = Response + +-- https://discord.com/developers/docs/resources/channel#delete-channel-permission +export type DeleteChannelPermissionResponse = Response + +-- https://discord.com/developers/docs/resources/channel#follow-announcement-channel +export type FollowAnnouncementChannelResponse = Response + +-- https://discord.com/developers/docs/resources/channel#trigger-typing-indicator +export type TriggerTypingIndicatorResponse = Response + +-- https://discord.com/developers/docs/resources/channel#get-pinned-messages +export type GetPinnedMessagesResponse = Response<{ objects.MessageObject }> + +-- https://discord.com/developers/docs/resources/channel#pin-message +export type PinMessageResponse = Response + +-- https://discord.com/developers/docs/resources/channel#unpin-message +export type UnpinMessageResponse = Response + +-- https://discord.com/developers/docs/resources/channel#group-dm-add-recipient +export type GroupDMAddRecipientResponse = Response + +-- https://discord.com/developers/docs/resources/channel#group-dm-remove-recipient +export type GroupDMRemoveRecipientResponse = Response + +-- https://discord.com/developers/docs/resources/channel#start-thread-from-message +export type StartThreadFromMessageResponse = Response + +-- https://discord.com/developers/docs/resources/channel#start-thread-without-message +export type StartThreadWithoutMessageResponse = Response + +-- https://discord.com/developers/docs/resources/channel#start-thread-in-forum-or-media-channel +export type StartThreadInForumOrMediaChannelResponse = Response + +-- https://discord.com/developers/docs/resources/channel#join-thread +export type JoinThreadResponse = Response + +-- https://discord.com/developers/docs/resources/channel#add-thread-member +export type AddThreadMemberResponse = Response + +-- https://discord.com/developers/docs/resources/channel#leave-thread +export type LeaveThreadResponse = Response + +-- https://discord.com/developers/docs/resources/channel#remove-thread-member +export type RemoveThreadMemberResponse = Response + +-- https://discord.com/developers/docs/resources/channel#get-thread-member +export type GetThreadMemberResponse = Response + +-- https://discord.com/developers/docs/resources/channel#list-thread-members +export type ListThreadMembersResponse = Response<{ objects.ThreadMemberObject }> + +-- https://discord.com/developers/docs/resources/channel#list-public-archived-threads +export type ListPublicArchivedThreadsResponse = Response<{ + threads: { objects.ChannelObject }, -- the public, archived threads + members: { objects.ThreadMemberObject }, -- a thread member object for each returned thread the current user has joined + has_more: boolean, -- whether there are potentially additional threads that could be returned on a subsequent call +}> + +-- https://discord.com/developers/docs/resources/channel#list-private-archived-threads +export type ListPrivateArchivedThreadsResponse = Response<{ + threads: { objects.ChannelObject }, -- the public, archived threads + members: { objects.ThreadMemberObject }, -- a thread member object for each returned thread the current user has joined + has_more: boolean, -- whether there are potentially additional threads that could be returned on a subsequent call +}> + +-- https://discord.com/developers/docs/resources/channel#list-joined-private-archived-threads +export type ListJoinedPrivateArchivedThreadsResponse = Response<{ + threads: { objects.ChannelObject }, -- the public, archived threads + members: { objects.ThreadMemberObject }, -- a thread member object for each returned thread the current user has joined + has_more: boolean, -- whether there are potentially additional threads that could be returned on a subsequent call +}> + +-- https://discord.com/developers/docs/resources/emoji#list-guild-emojis +export type ListGuildEmojisResponse = Response<{ objects.EmojiObject }> + +-- https://discord.com/developers/docs/resources/emoji#get-guild-emoji +export type GetGuildEmojiResponse = Response + +-- https://discord.com/developers/docs/resources/emoji#create-guild-emoji +export type CreateGuildEmojiResponse = Response + +-- https://discord.com/developers/docs/resources/emoji#modify-guild-emoji +export type ModifyGuildEmojiResponse = Response + +-- https://discord.com/developers/docs/resources/emoji#delete-guild-emoji +export type DeleteGuildEmojiResponse = Response + +-- https://discord.com/developers/docs/resources/guild#create-guild +export type CreateGuildResponse = Response + +-- https://discord.com/developers/docs/resources/guild#get-guild +export type GetGuildResponse = Response + +-- https://discord.com/developers/docs/resources/guild#get-guild +export type GetGuildPreviewResponse = Response + +-- https://discord.com/developers/docs/resources/guild#modify-guild +export type ModifyGuildResponse = Response + +-- https://discord.com/developers/docs/resources/guild#delete-guild +export type DeleteGuildResponse = Response + +-- https://discord.com/developers/docs/resources/guild#get-guild-channels +export type GetGuildChannelsResponse = Response<{ objects.ChannelObject }> + +-- https://discord.com/developers/docs/resources/guild#create-guild-channel +export type CreateGuildChannelResponse = Response + +-- https://discord.com/developers/docs/resources/guild#modify-guild-channel-positions +export type ModifyGuildChannelPositionsResponse = Response + +-- https://discord.com/developers/docs/resources/guild#list-active-guild-threads +export type ListActiveGuildThreadsResponse = Response<{ + threads: { objects.ChannelObject }, -- the active threads + members: { objects.ThreadMemberObject }, -- a thread member object for each returned thread the current user has joined +}> + +-- https://discord.com/developers/docs/resources/guild#get-guild-member +export type GetGuildMemberResponse = Response + +-- https://discord.com/developers/docs/resources/guild#list-guild-members +export type ListGuildMembersResponse = Response<{ objects.GuildMemberObject }> + +-- https://discord.com/developers/docs/resources/guild#search-guild-members +export type SearchGuildMembersResponse = Response<{ objects.GuildMemberObject }> + +-- https://discord.com/developers/docs/resources/guild#add-guild-member +export type AddGuildMemberResponse = Response + +-- https://discord.com/developers/docs/resources/guild#modify-guild-member +export type ModifyGuildMemberResponse = Response + +-- https://discord.com/developers/docs/resources/guild#modify-current-member +export type ModifyCurrentMemberResponse = Response + +-- https://discord.com/developers/docs/resources/guild#add-guild-member-role +export type AddGuildMemberRoleResponse = Response + +-- https://discord.com/developers/docs/resources/guild#remove-guild-member-role +export type RemoveGuildMemberRoleResponse = Response + +-- https://discord.com/developers/docs/resources/guild#remove-guild-member +export type RemoveGuildMemberResponse = Response + +-- https://discord.com/developers/docs/resources/guild#get-guild-bans +export type GetGuildBansResponse = Response<{ objects.BanObject }> + +-- https://discord.com/developers/docs/resources/guild#get-guild-ban +export type GetGuildBanResponse = Response + +-- https://discord.com/developers/docs/resources/guild#create-guild-ban +export type CreateGuildBanResponse = Response + +-- https://discord.com/developers/docs/resources/guild#remove-guild-ban +export type RemoveGuildBanResponse = Response + +-- https://discord.com/developers/docs/resources/guild#bulk-guild-ban +export type BulkGuildBanResponse = Response<{ + banned_users: { objects.Snowflake }, + failed_users: { objects.Snowflake }, +}> + +-- https://discord.com/developers/docs/resources/guild#get-guild-roles +export type GetGuildRolesResponse = Response<{ objects.GuildRoleObject }> + +-- https://discord.com/developers/docs/resources/guild#create-guild-role +export type CreateGuildRoleResponse = Response + +-- https://discord.com/developers/docs/resources/guild#modify-guild-role-positions +export type ModifyGuildRolePositionsResponse = Response<{ objects.GuildRoleObject }> + +-- https://discord.com/developers/docs/resources/guild#modify-guild-role +export type ModifyGuildRoleResponse = Response + +-- https://discord.com/developers/docs/resources/guild#modify-guild-mfa-level +export type ModifyGuildMFALevelResponse = Response + +-- https://discord.com/developers/docs/resources/guild#delete-guild-role +export type DeleteGuildRoleResponse = Response + +-- https://discord.com/developers/docs/resources/guild#get-guild-prune-count +export type GetGuildPruneCountResponse = Response<{ pruned: number }> + +-- https://discord.com/developers/docs/resources/guild#begin-guild-prune +export type BeginGuildPruneResponse = Response<{ pruned: number }> + +-- https://discord.com/developers/docs/resources/guild#get-guild-voice-regions +export type GetGuildVoiceRegionsResponse = Response<{ objects.VoiceRegionObject }> + +-- https://discord.com/developers/docs/resources/guild#get-guild-invites +export type GetGuildInvitesResponse = Response<{ objects.InviteObject & objects.InviteMetadataObject }> + +-- https://discord.com/developers/docs/resources/guild#get-guild-integrations +export type GetGuildIntegrationsResponse = Response + +-- https://discord.com/developers/docs/resources/guild#delete-guild-integration +export type DeleteGuildIntegrationResponse = Response + +-- https://discord.com/developers/docs/resources/guild#get-guild-widget-settings +export type GetGuildWidgetSettingsResponse = Response + +-- https://discord.com/developers/docs/resources/guild#modify-guild-widget +export type ModifyGuildWidgetResponse = Response + +-- https://discord.com/developers/docs/resources/guild#get-guild-widget +export type GetGuildWidgetResponse = Response + +-- https://discord.com/developers/docs/resources/guild#get-guild-vanity-url +export type GetGuildVanityUrlResponse = Response + +-- https://discord.com/developers/docs/resources/guild#get-guild-widget-image +export type GetGuildWidgetImageResponse = Response + +-- https://discord.com/developers/docs/resources/guild#get-guild-welcome-screen +export type GetGuildWelcomeScreenResponse = Response + +-- https://discord.com/developers/docs/resources/guild#modify-guild-welcome-screen +export type ModifyGuildWelcomeScreenResponse = Response + +-- https://discord.com/developers/docs/resources/guild#get-guild-onboarding +export type GetGuildOnboardingResponse = Response + +-- https://discord.com/developers/docs/resources/guild#modify-guild-onboarding +export type ModifyGuildOnboardingResponse = Response + +-- https://discord.com/developers/docs/resources/guild#modify-current-user-voice-state +export type ModifyCurrentUserVoiceStateResponse = Response + +-- https://discord.com/developers/docs/resources/guild#modify-user-voice-state +export type ModifyUserVoiceStateResponse = Response + +-- https://discord.com/developers/docs/resources/guild-scheduled-event#list-scheduled-events-for-guild +export type ListScheduledEventsForGuildResponse = Response<{ objects.GuildScheduledEventObject }> + +-- https://discord.com/developers/docs/resources/guild-scheduled-event#create-guild-scheduled-event +export type CreateGuildScheduledEventResponse = Response + +-- https://discord.com/developers/docs/resources/guild-scheduled-event#get-guild-scheduled-event +export type GetGuildScheduledEventResponse = Response + +-- https://discord.com/developers/docs/resources/guild-scheduled-event#modify-guild-scheduled-event +export type ModifyGuildScheduledEventResponse = Response + +-- https://discord.com/developers/docs/resources/guild-scheduled-event#delete-guild-scheduled-event +export type DeleteGuildScheduledEventResponse = Response + +-- https://discord.com/developers/docs/resources/guild-scheduled-event#get-guild-scheduled-event-users +export type GetGuildScheduledEventUsersResponse = Response<{ objects.GuildScheduledEventUserObject }> + +-- https://discord.com/developers/docs/resources/guild-template#get-guild-template +export type GetGuildTemplateResponse = Response + +-- https://discord.com/developers/docs/resources/guild-template#create-guild-from-guild-template +export type CreateGuildFromGuildTemplateResponse = Response + +-- https://discord.com/developers/docs/resources/guild-template#get-guild-templates +export type GetGuildTemplatesResponse = Response + +-- https://discord.com/developers/docs/resources/guild-template#create-guild-template +export type CreateGuildTemplateResponse = Response + +-- https://discord.com/developers/docs/resources/guild-template#sync-guild-template +export type SyncGuildTemplateResponse = Response + +-- https://discord.com/developers/docs/resources/guild-template#modify-guild-template +export type ModifyGuildTemplateResponse = Response + +-- https://discord.com/developers/docs/resources/guild-template#delete-guild-template +export type DeleteGuildTemplateResponse = Response + +-- https://discord.com/developers/docs/resources/invite#get-invite +export type GetInviteResponse = Response + +-- https://discord.com/developers/docs/resources/invite#delete-invite +export type DeleteInviteResponse = Response + +-- https://discord.com/developers/docs/resources/poll#get-answer-voters +export type GetAnswerVotersResponse = Response<{ objects.UserObject }> + +-- https://discord.com/developers/docs/resources/poll#end-poll +export type EndPollResponse = Response + +-- https://discord.com/developers/docs/resources/stage-instance#create-stage-instance +export type CreateStageInstanceResponse = Response + +-- https://discord.com/developers/docs/resources/stage-instance#get-stage-instance +-- discord-fixme: make a PR to declare what this API returns. +export type GetStageInstanceResponse = Response + +-- https://discord.com/developers/docs/resources/stage-instance#modify-stage-instance +export type ModifyStageInstanceResponse = Response + +-- https://discord.com/developers/docs/resources/stage-instance#delete-stage-instance +export type DeleteStageInstanceResponse = Response + +-- https://discord.com/developers/docs/resources/sticker#get-sticker +export type GetStickerResponse = Response + +-- https://discord.com/developers/docs/resources/sticker#list-sticker-packs +export type ListStickerPacksResponse = Response<{ + sticker_packs: { objects.StickerPackObject }, -- array of sticker pack objects +}> + +-- https://discord.com/developers/docs/resources/sticker#list-guild-stickers +export type ListGuildStickersResponse = Response<{ objects.StickerObject }> + +-- https://discord.com/developers/docs/resources/sticker#get-guild-sticker +export type GetGuildStickerResponse = Response + +-- https://discord.com/developers/docs/resources/sticker#create-guild-sticker +export type CreateGuildStickerResponse = Response + +-- https://discord.com/developers/docs/resources/sticker#modify-guild-sticker +export type ModifyGuildStickerResponse = Response + +-- https://discord.com/developers/docs/resources/sticker#delete-guild-sticker +export type DeleteGuildStickerResponse = Response + +-- https://discord.com/developers/docs/resources/user#get-current-user +export type GetCurrentUserResponse = Response + +-- https://discord.com/developers/docs/resources/user#get-user +export type GetUserResponse = Response + +-- https://discord.com/developers/docs/resources/user#modify-current-user +export type ModifyCurrentUserResponse = Response + +-- https://discord.com/developers/docs/resources/user#get-current-user-guilds +export type GetCurrentUserGuildsResponse = Response<{ objects.GuildObject }> + +-- https://discord.com/developers/docs/resources/user#get-current-user-guild-member +export type GetCurrentUserGuildMemberResponse = Response + +-- https://discord.com/developers/docs/resources/user#leave-guild +export type LeaveGuildResponse = Response + +-- https://discord.com/developers/docs/resources/user#create-dm +export type CreateDMResponse = Response + +-- https://discord.com/developers/docs/resources/user#create-group-dm +export type CreateGroupDMResponse = Response + +-- https://discord.com/developers/docs/resources/user#get-current-user-connections +export type GetCurrentUserConnectionResponse = Response + +-- https://discord.com/developers/docs/resources/user#get-current-user-application-role-connection +export type GetCurrentUserApplicationRoleConnectionResponse = Response + +-- https://discord.com/developers/docs/resources/user#update-current-user-application-role-connection +export type UpdateCurrentUserApplicationRoleConnectionResponse = Response + +-- https://discord.com/developers/docs/resources/voice +export type ListVoiceRegionsResponse = Response<{ objects.VoiceRegionObject }> + +-- https://discord.com/developers/docs/resources/webhook#create-webhook +export type CreateWebhookResponse = Response + +-- https://discord.com/developers/docs/resources/webhook#get-channel-webhooks +export type GetChannelWebhooksResponse = Response<{ objects.WebhookObject }> + +-- https://discord.com/developers/docs/resources/webhook#get-guild-webhooks +export type GetGuildWebhooksResponse = Response<{ objects.WebhookObject }> + +-- https://discord.com/developers/docs/resources/webhook#get-webhook +export type GetWebhookResponse = Response + +-- https://discord.com/developers/docs/resources/webhook#get-webhook-with-token +export type GetWebhookWithTokenResponse = Response + +-- https://discord.com/developers/docs/resources/webhook#modify-webhook +export type ModifyWebhookResponse = Response + +-- https://discord.com/developers/docs/resources/webhook#modify-webhook-with-token +export type ModifyWebhookWithTokenResponse = Response + +-- https://discord.com/developers/docs/resources/webhook#delete-webhook +export type DeleteWebhookResponse = Response + +-- https://discord.com/developers/docs/resources/webhook#delete-webhook-with-token +export type DeleteWebhookWitHTokenResponse = Response + +-- https://discord.com/developers/docs/resources/webhook#execute-webhook +export type ExecuteWebhookResponse = Response + +-- https://discord.com/developers/docs/resources/webhook#execute-slackcompatible-webhook +export type ExecuteSlackCompatibleWebhookResponse = Response + +-- https://discord.com/developers/docs/resources/webhook#execute-githubcompatible-webhook +export type ExecuteGitCompatibleWebhookResponse = Response + +-- https://discord.com/developers/docs/resources/webhook#get-webhook-message +export type GetWebhookMessageResponse = Response + +-- https://discord.com/developers/docs/resources/webhook#edit-webhook-message +export type EditWebhookMessageResponse = Response + +-- https://discord.com/developers/docs/resources/webhook#delete-webhook-message +export type DeleteWebhookMessageResponse = Response + +-- https://discord.com/developers/docs/topics/gateway#get-gateway +export type GetGatewayResponse = Response<{ + url: string, -- WSS URL that can be used for connecting to the Gateway +}> + +-- https://discord.com/developers/docs/topics/gateway#get-gateway-bot +export type GetGatewayBotResponse = Response<{ + url: string, -- WSS URL that can be used for connecting to the Gateway + shards: number, -- Recommended number of shards to use when connecting + session_start_limit: objects.SessionStartLimitObject, -- Information on the current session start limit +}> + +-- https://discord.com/developers/docs/interactions/receiving-and-responding#create-interaction-response +export type CreateInteractionResponse = Response<{ + resource: { + message: objects.MessageObject?, + }, +}> + +-- https://discord.com/developers/docs/interactions/receiving-and-responding#get-original-interaction-response +export type GetOriginalInteractionResponse = Response + +-- https://discord.com/developers/docs/interactions/receiving-and-responding#edit-original-interaction-response +export type EditOriginalInteractionResponse = Response + +-- https://discord.com/developers/docs/interactions/receiving-and-responding#delete-original-interaction-response +export type DeleteOriginalInteractionResponse = Response + +-- https://discord.com/developers/docs/interactions/receiving-and-responding#create-followup-message +export type CreateFollowupMessageResponse = Response + +-- https://discord.com/developers/docs/interactions/receiving-and-responding#get-followup-message +export type GetFollowupMessageResponse = Response + +-- https://discord.com/developers/docs/interactions/receiving-and-responding#edit-followup-message +export type EditFollowupMessageResponse = Response + +-- https://discord.com/developers/docs/interactions/receiving-and-responding#delete-followup-message +export type DeleteFollowupMessageResponse = Response + +return "DISCORD_V10_PAYLOAD_TYPES" diff --git a/packages/api-types/src/sticker.luau b/packages/api-types/src/sticker.luau new file mode 100644 index 0000000..6da3428 --- /dev/null +++ b/packages/api-types/src/sticker.luau @@ -0,0 +1,21 @@ +local reflect = require("@utils/table/reflect") + +local StickerTypes = table.freeze(reflect({ + Standard = 1, + Guild = 2, +})) + +local StickerFormatTypes = table.freeze(reflect({ + PNG = 1, + APNG = 2, + LOTTIE = 3, + GIF = 4, +})) + +export type StickerType = "Standard" | "Guild" +export type StickerFormatType = "PNG" | "APNG" | "LOTTIE" | "GIF" | "Unknown" + +return { + StickerTypes = StickerTypes, + StickerFormatTypes = StickerFormatTypes, +} diff --git a/packages/api-types/src/user.luau b/packages/api-types/src/user.luau new file mode 100644 index 0000000..228f930 --- /dev/null +++ b/packages/api-types/src/user.luau @@ -0,0 +1,14 @@ +local reflect = require("@utils/table/reflect") + +local NitroType = table.freeze(reflect({ + None = 0, + NitroClassic = 1, + Nitro = 2, + NitroBasic = 3, +})) + +export type NitroType = "None" | "NitroClassic" | "Nitro" | "NitroBasic" + +return { + NitroType = NitroType, +} diff --git a/packages/api-types/src/voice/.gitkeep b/packages/api-types/src/voice/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/builders/README.md b/packages/builders/README.md new file mode 100644 index 0000000..50c3c6f --- /dev/null +++ b/packages/builders/README.md @@ -0,0 +1,17 @@ +
+

+ discord-luau +

+
+ +## DiscordLuau - Builders + +This package is a collection of builders that can be used to create Objects that can be parsed by the Discord API. + +--- + +Builders take the following approach: +- Support the creation of new Builders which represent structures the Discord API understands. +- Support the modification of existing data structures that the Discord API understands. + +Builders should cover both the creation and modification of discord data structures. \ No newline at end of file diff --git a/packages/builders/src/attachment.luau b/packages/builders/src/attachment.luau new file mode 100644 index 0000000..7c80d60 --- /dev/null +++ b/packages/builders/src/attachment.luau @@ -0,0 +1,59 @@ +--[[ + Implementation of discords Attachment object as a Luau builder. + + https://discord.com/developers/docs/resources/Attachment#Attachment-object +]] + +local Attachment = {} + +Attachment.Prototype = {} +Attachment.Interface = {} + +--[[ + Set the ID of the Attachment to use. +]] +function Attachment.Prototype.setId(self: Attachment, attachmentId: string): Attachment + self.attachmentId = attachmentId + + return self +end + +--[[ + Responsible for building the Attachment JSON that can be parsed by the Discord API. +]] +function Attachment.Prototype.build(self: Attachment): JSON + return { + id = self.attachmentId, + } +end + +--[[ + Responsible for creating a new Attachment. + + ```lua + + ``` +]] +function Attachment.Interface.new(resource: { + attachmentId: string, +}?): Attachment + local self = setmetatable({} :: Attachment, { + __index = Attachment.Prototype, + }) + + if resource then + if resource.attachmentId then + self:setId(resource.attachmentId) + end + end + + return self +end + +export type Attachment = typeof(Attachment.Prototype) & { + attachmentId: string?, +} + +export type JSON = typeof(Attachment.Prototype.build(nil :: any)) + +return Attachment.Interface diff --git a/packages/builders/src/channel.luau b/packages/builders/src/channel.luau new file mode 100644 index 0000000..afdf2bc --- /dev/null +++ b/packages/builders/src/channel.luau @@ -0,0 +1,436 @@ +--[[ + Implementation of a discord Channel as a luau builder. + + https://discord.com/developers/docs/resources/guild#create-guild-channel +]] + +local apiTypes = require("@api-types/apiTypes") +local channelTypes = require("@api-types/channel") + +local validateKebabCase = require("@utils/validateKebabCase") + +local GUILD_CHANNEL_TYPE_MAP = { + ["GuildText"] = 0, + ["Dm"] = 1, + ["GuildVoice"] = 2, + ["GroupDm"] = 3, + ["GuildCategory"] = 4, + ["GuildAnnouncement"] = 5, + ["AnnouncementThread"] = 10, + ["PublicThread"] = 11, + ["PrivateThread"] = 12, + ["GuildStageVoice"] = 13, + ["GuildDirectory"] = 14, + ["GuildForum"] = 15, + ["GuildMedia"] = 16, +} + +local GUILD_VIDEO_QUALITY_MODE_MAP = { + ["Auto"] = 1, + ["Full"] = 2, +} + +local GUILD_SORT_ORDER_MAP = { + ["LatestActivity"] = 0, + ["CreationDate"] = 1, +} + +local GUILD_FORUM_LAYOUT_MAP = { + ["NotSet"] = 0, + ["ListView"] = 1, + ["GalleryView"] = 2, +} + +local Channel = {} + +Channel.Prototype = {} +Channel.Interface = {} + +--[[ + Set the name of this Channel +]] +function Channel.Prototype.setName(self: Channel, tagName: string): Channel + assert(#tagName <= 100, `Channel name must be less than 100 characters.`) + assert(#tagName > 1, `Channel name must be more than 1 characters.`) + + assert(validateKebabCase(tagName), `Channel name must be kebab-case.`) + + self.name = tagName + + return self +end + +--[[ + Set the Type of channel this is. +]] +function Channel.Prototype.setType(self: Channel, channelType: channelTypes.ChannelType): Channel + self.type = channelType + + return self +end + +--[[ + Sets the position of the channel in the discord channels list. + + channels with the same position are sorted by id +]] +function Channel.Prototype.setPosition(self: Channel, channelPosition: number): Channel + self.position = channelPosition + + return self +end + +--[[ + Adds an overwrite object to the channel's permission overwrites. + + channel overwrites are responsible for handling user/role permission on a specific channel. +]] +function Channel.Prototype.setOverwrites(self: Channel, overwriteObjects: { apiTypes.OverwriteObject }): Channel + self.permissionOverwrites = overwriteObjects + + return self +end + +--[[ + Sets the channels topic, topics appear at the top of the discord app and help to inform players about + the channel's purpose. +]] +function Channel.Prototype.setTopic(self: Channel, channelTopic: string): Channel + assert( + self.type == "GuildText" + or self.type == "GuildAnnouncement" + or self.type == "GuildForum" + or self.type == "GuildMedia", + `Channel topic is only supported for guild text, announcement, forum, and media channels.` + ) + + assert(#channelTopic <= 1024, `Channel topic must be less than 1024 characters.`) + assert(#channelTopic > 0, `Channel topic must be more than 0 characters.`) + + self.topic = channelTopic + + return self +end + +--[[ + Sets the bitrate of the voice channel, the bitrate (in bits) of the voice or stage channel. +]] +function Channel.Prototype.setBitrate(self: Channel, bitrate: number): Channel + assert( + self.type == "GuildVoice" or self.type == "GuildStageVoice", + `Channel bitrate is only supported for guild voice and stage voice channels.` + ) + + assert(bitrate < 96000, `Channel bitrate must be less than 96kbit/s.`) + assert(bitrate > 8000, `Channel bitrate must be more than 8kbit/s.`) + + self.bitrate = bitrate + + return self +end + +--[[ + the user limit of the voice channel + + the user limit of a voice channel is the maximum number of users allowed in the channel at one time. +]] +function Channel.Prototype.setUserLimit(self: Channel, userLimit: number?): Channel + assert( + self.type == "GuildVoice" or self.type == "GuildStageVoice", + `Channel user limit is only supported for guild voice and stage voice channels.` + ) + + self.userLimit = userLimit + + return self +end + +--[[ + amount of seconds a user has to wait before sending another message; bots, as well as users with + the permission manage_messages or manage_channel, are unaffected +]] +function Channel.Prototype.setRateLimitPerUser(self: Channel, rateLimitPerUser: number?): Channel + assert( + self.type == "GuildText" + or self.type == "GuildVoice" + or self.type == "GuildStageVoice" + or self.type == "GuildForum" + or self.type == "GuildMedia", + `Channel rate limit per user is only supported for guild text, voice, stage voice, forum, and media channels.` + ) + + assert(rateLimitPerUser <= 21600, `Channel rate limit per user must be less than 21600 seconds.`) + assert(rateLimitPerUser >= 0, `Channel rate limit per user must be more than 0 seconds.`) + + self.rateLimitPerUser = rateLimitPerUser + + return self +end + +--[[ + set the parent of the channel, channel parents are categories and can help break down a discord guild. +]] +function Channel.Prototype.setParentId(self: Channel, parentId: apiTypes.Snowflake?): Channel + assert( + self.type == "GuildText" + or self.type == "GuildVoice" + or self.type == "GuildAnnouncement" + or self.type == "GuildStageVoice" + or self.type == "GuildForum" + or self.type == "GuildMedia", + `Channel parent id is only supported for guild text, announcement, voice, stage voice, forum, and media channels.` + ) + + self.parentId = parentId + + return self +end + +--[[ + set weather this channel is NSFW or not. +]] +function Channel.Prototype.setNSFW(self: Channel, isNSFW: boolean): Channel + assert( + self.type == "GuildText" + or self.type == "GuildVoice" + or self.type == "GuildAnnouncement" + or self.type == "GuildStageVoice" + or self.type == "GuildForum", + `Channel nsfw is only supported for guild text, announcement, voice, stage voice and forum channels.` + ) + + self.nsfw = isNSFW + + return self +end + +--[[ + set the voice region (requires you to query for the voice region ids) of the voice channel. + + voice regions are regions that discord uses to host voice channels. +]] +function Channel.Prototype.setVoiceRegion(self: Channel, voiceRegionId: string): Channel + assert( + self.type == "GuildVoice" or self.type == "GuildStageVoice", + `Channel voice region is only supported for voice and stage voice.` + ) + + self.rtcRegion = voiceRegionId + + return self +end + +--[[ + sets the video quality mode of the voice channel. +]] +function Channel.Prototype.setVideoQualityMode(self: Channel, videoQualityMode: channelTypes.VideoQualityMode): Channel + assert( + self.type == "GuildVoice" or self.type == "GuildStageVoice", + `Channel video quality mode is only supported for voice and stage voice.` + ) + + self.videoQualityMode = videoQualityMode + + return self +end + +--[[ + sets the default auto archive duration for the channel, the default duration that the clients use (not the API) + for newly created threads in the channel, in minutes, to automatically archive the thread after recent activity +]] +function Channel.Prototype.setDefaultAutoArchiveDuration(self: Channel, autoArchiveDuration: number): Channel + assert( + self.type == "GuildText" + or self.type == "GuildAnnouncement" + or self.type == "GuildForum" + or self.type == "GuildMedia", + `Channel auto archive duration is only supported for guild text, announcement, forum, and media channels.` + ) + + self.defaultAutoArchiveDuration = autoArchiveDuration + + return self +end + +--[[ + sets the default reaction emoji for either a forum or media channel. + + This is the default emoji that the channel will use for the forum or media channel. +]] +function Channel.Prototype.setDefaultReactionEmoji( + self: Channel, + defaultReactionEmoji: apiTypes.DefaultReactionObject +): Channel + assert( + self.type == "GuildForum" or self.type == "GuildMedia", + `Channel default reaction is only supported for forum, and media channels.` + ) + + self.defaultReactionEmoji = defaultReactionEmoji + + return self +end + +--[[ + specify channel forum tags that are available to the channel. + + This is the list of tags that the channel will use for the forum. +]] +function Channel.Prototype.setForumTags(self: Channel, forumTags: { apiTypes.ForumTagObject }): Channel + assert( + self.type == "GuildForum" or self.type == "GuildMedia", + `Channel forum tags are only supported for forum, and media channels.` + ) + + self.availableTags = forumTags + + return self +end + +--[[ + sets the default sort order for the channel. + + This is the sort order that the channel will use for newly created threads. +]] +function Channel.Prototype.setDefaultSortOrder(self: Channel, sortOrder: channelTypes.ForumSortOrder): Channel + assert( + self.type == "GuildForum" or self.type == "GuildMedia", + `Channel sort order is only supported for forum, and media channels.` + ) + + self.defaultSortOrder = sortOrder + + return self +end + +--[[ + sets the default forum layout for the channel. + + This is the layout that the channel will use for newly created threads. +]] +function Channel.Prototype.setDefaultForumLayout(self: Channel, forumLayout: channelTypes.ForumLayout): Channel + assert(self.type == "GuildForum", `Channel forum layout is only supported for forum channels.`) + + self.defaultForumLayout = forumLayout + + return self +end + +--[[ + sets the default thread rate limit per user for the channel. + + This is the amount of seconds a user has to wait before sending another message in the thread, +]] +function Channel.Prototype.setDefaultThreadRateLimitPerUser(self: Channel, threadRateLimitPerUser: number): Channel + assert( + self.type == "GuildText" + or self.type == "GuildAnnouncement" + or self.type == "GuildForum" + or self.type == "GuildMedia", + `Channel default thread rate limit per user is only supported for forum channels.` + ) + + self.defaultThreadRateLimitPerUser = threadRateLimitPerUser + + return self +end + +--[[ + Responsible for buillding the channel object that the Discord API can understand. +]] +function Channel.Prototype.build(self: Channel): JSON + return { + id = self.id, + + name = self.name, + type = GUILD_CHANNEL_TYPE_MAP[self.type], + position = self.position, + permission_overwrites = self.permissionOverwrites, + + topic = self.topic, + bitrate = self.bitrate, + user_limit = self.userLimit, + rate_limit_per_user = self.rateLimitPerUser, + parent_id = self.parentId, + nsfw = self.nsfw, + rtc_region = self.rtcRegion, + video_quality_mode = GUILD_VIDEO_QUALITY_MODE_MAP[self.videoQualityMode], + default_auto_archive_duration = self.defaultAutoArchiveDuration, + default_reaction_emoji = self.defaultReactionEmoji, + available_tags = self.availableTags, + default_sort_order = GUILD_SORT_ORDER_MAP[self.defaultSortOrder], + default_forum_layout = GUILD_FORUM_LAYOUT_MAP[self.defaultForumLayout], + default_thread_rate_limit_per_user = self.defaultThreadRateLimitPerUser, + } +end + +--[[ + Constructor for the Discord Channel Builder. +]] +function Channel.Interface.new(resource: { + channelName: string?, + channelType: channelTypes.ChannelType?, + channelPosition: number?, + channelPermissionOverwrites: { apiTypes.OverwriteObject }?, +}?): Channel + local self = setmetatable( + { + permissionOverwrites = {}, + } :: Channel, + { __index = Channel.Prototype } + ) + + if resource and resource.channelType then + self:setType(resource.channelType) + end + + if resource and resource.channelName then + self:setName(resource.channelName) + end + + if resource and resource.channelPosition then + self:setPosition(resource.channelPosition) + end + + if resource and resource.channelPermissionOverwrites then + self:setOverwrites(resource.channelPermissionOverwrites) + end + + return self +end + +export type Channel = typeof(Channel.Prototype) & { + id: apiTypes.Snowflake?, + + name: string, + type: channelTypes.ChannelType, + position: number, + permissionOverwrites: { apiTypes.OverwriteObject }, + + -- TEXT + topic: string?, + rateLimitPerUser: number?, + parentId: apiTypes.Snowflake?, + nsfw: boolean?, + defaultAutoArchiveDuration: number?, + defaultThreadRateLimitPerUser: number?, + + -- MEDIA + defaultReactionEmoji: apiTypes.DefaultReactionObject?, + availableTags: { apiTypes.ForumTagObject }?, + defaultSortOrder: channelTypes.ForumSortOrder?, + + -- FORUM + defaultForumLayout: channelTypes.ForumLayout?, + + -- VOICE + videoQualityMode: channelTypes.VideoQualityMode?, + rtcRegion: string?, + rateLimitPerUser: number?, + userLimit: number?, + bitrate: number?, +} + +export type JSON = typeof(Channel.Prototype.build(nil :: any)) + +return Channel.Interface diff --git a/packages/builders/src/defaultReaction.luau b/packages/builders/src/defaultReaction.luau new file mode 100644 index 0000000..ee56f69 --- /dev/null +++ b/packages/builders/src/defaultReaction.luau @@ -0,0 +1,55 @@ +--[[ + Implementation of a discord default reaction object as a luau builder. + + An object that specifies the emoji to use as the default way to react to a forum post. Exactly one of emoji_id and emoji_name must be set. + + https://discord.com/developers/docs/resources/channel#default-reaction-object +]] + +local apiTypes = require("@api-types/apiTypes") + +local DefaultReaction = {} + +DefaultReaction.Prototype = {} +DefaultReaction.Interface = {} + +--[[ + Responsible for buillding the default reaction object that the Discord API can understand. +]] +function DefaultReaction.Prototype.build(self: DefaultReaction): JSON + return { + emoji_id = self.emojiId, + emoji_name = self.emojiName, + } +end + +--[[ + Constructor for the Discord Default Reaction Builder. + + ```lua + local defaultReaction = DefaultReaction.new("000000000000000000", "secret-emoji"):build() + ``` +]] +function DefaultReaction.Interface.new(resource: { + emojiId: apiTypes.Snowflake?, + emojiName: string?, +}): DefaultReaction + local self = setmetatable( + { + emojiId = resource.emojiId, + emojiName = resource.emojiName, + } :: DefaultReaction, + { __index = DefaultReaction.Prototype } + ) + + return self +end + +export type DefaultReaction = typeof(DefaultReaction.Prototype) & { + emojiId: apiTypes.Snowflake?, + emojiName: string?, +} + +export type JSON = typeof(DefaultReaction.Prototype.build(nil :: any)) + +return DefaultReaction.Interface diff --git a/packages/builders/src/embed/author.luau b/packages/builders/src/embed/author.luau new file mode 100644 index 0000000..1a879f4 --- /dev/null +++ b/packages/builders/src/embed/author.luau @@ -0,0 +1,96 @@ +--[[ + Implementation of discords Author object as a Luau builder. + + https://discord.com/developers/docs/resources/message#embed-object-embed-author-structure +]] + +local Author = {} + +Author.Prototype = {} +Author.Interface = {} + +--[[ + Set the image url for this embed. +]] +function Author.Prototype.setName(self: Author, name: string): Author + assert(#name <= 256, "Name must be less than 256 characters") + assert(#name > 1, "Name must be greater than 1 characters") + + self.name = name + + return self +end + +--[[ + Set the image height for this embed. +]] +function Author.Prototype.setUrl(self: Author, url: string): Author + self.url = url + + return self +end + +--[[ + Set the image height for this embed. +]] +function Author.Prototype.setIcon(self: Author, iconUrl: string, proxyIconUrl: string?): Author + self.iconUrl = iconUrl + self.proxyIconUrl = proxyIconUrl + + return self +end + +--[[ + Responsible for building the Author JSON that can be parsed by the Discord API. +]] +function Author.Prototype.build(self: Author): JSON + return { + name = self.name, + url = self.url, + } +end + +--[[ + Responsible for creating a new Author. + + ```lua + + ``` +]] +function Author.Interface.new(resource: { + name: string?, + url: string?, + iconUrl: string?, + proxyIconUrl: string?, +}?): Author + local self = setmetatable({} :: Author, { + __index = Author.Prototype, + }) + + if resource then + if resource.name then + self:setName(resource.name) + end + + if resource.url then + self:setUrl(resource.url) + end + + if resource.iconUrl then + self:setIcon(resource.iconUrl, resource.proxyIconUrl) + end + end + + return self +end + +export type Author = typeof(Author.Prototype) & { + name: string?, + url: string?, + iconUrl: string?, + proxyIconUrl: string?, +} + +export type JSON = typeof(Author.Prototype.build({} :: any)) + +return Author.Interface diff --git a/packages/builders/src/embed/embed.luau b/packages/builders/src/embed/embed.luau new file mode 100644 index 0000000..cdd4d6e --- /dev/null +++ b/packages/builders/src/embed/embed.luau @@ -0,0 +1,279 @@ +--[[ + Implementation of discords Embed object as a Luau builder. + + https://discord.com/developers/docs/resources/message#embed-object +]] + +local embedTypes = require("@api-types/embed") + +local datetime = require("@std-polyfills/datetime") + +local embedAuthor = require("@builders/embed/author") +local embedField = require("@builders/embed/field") +local embedFooter = require("@builders/embed/footer") +local embedImage = require("@builders/embed/image") +local embedProvider = require("@builders/embed/provider") +local embedThumbnail = require("@builders/embed/thumbnail") +local embedVideo = require("@builders/embed/video") + +local Embed = {} + +Embed.Prototype = {} +Embed.Interface = {} + +--[[ + Set the title of the embed. +]] +function Embed.Prototype.setTitle(self: Embed, title: string): Embed + assert(#title <= 256, "Title must be less than 256 characters") + assert(#title > 1, "Title must be greater than 1 characters") + + self.title = title + + return self +end + +--[[ + Sets the Type of the embed. +]] +function Embed.Prototype.setType(self: Embed, type: embedTypes.EmbedType): Embed + self.type = type + + return self +end + +--[[ + Set the description of the embed. +]] +function Embed.Prototype.setDescription(self: Embed, description: string): Embed + assert(#description <= 4096, "Description must be less than 4096 characters") + assert(#description > 1, "Description must be greater than 1 characters") + + self.description = description + + return self +end + +--[[ + Set the URL of the embed, URL will provide a hyperlinked title. +]] +function Embed.Prototype.setUrl(self: Embed, url: string): Embed + self.url = url + + return self +end + +--[[ + Set the timestamp of the embed, timestamps will exist at the bottom/footer of the embed. +]] +function Embed.Prototype.setTimestamp(self: Embed, timestamp: datetime.DateTime): Embed + self.timestamp = timestamp.unixTimestampMillis + + return self +end + +--[[ + Set the color of the embed, colors are numbers and in luau you can render hex as: 0x + + example: + - Red Color: 0xFF0000 + - Green Color: 0x00FF00 + - Blue Clor: 0x0000FF +]] +function Embed.Prototype.setColor(self: Embed, color: number): Embed + self.color = color + + return self +end + +--[[ + Set the footer of the embed. +]] +function Embed.Prototype.setFooter(self: Embed, footer: embedFooter.JSON): Embed + self.footer = footer + + return self +end + +--[[ + Set the image of the embed. +]] +function Embed.Prototype.setImage(self: Embed, image: embedImage.JSON): Embed + self.image = image + + return self +end + +--[[ + Set the thumbnail of the embed. +]] +function Embed.Prototype.setThumbnail(self: Embed, thumbnail: embedThumbnail.JSON): Embed + self.thumbnail = thumbnail + + return self +end + +--[[ + Set the video of the embed. +]] +function Embed.Prototype.setVideo(self: Embed, video: embedVideo.JSON): Embed + self.video = video + + return self +end + +--[[ + Set the provider of the embed. +]] +function Embed.Prototype.setProvider(self: Embed, provider: embedProvider.JSON): Embed + self.provider = provider + + return self +end + +--[[ + Set the author of the embed. +]] +function Embed.Prototype.setAuthor(self: Embed, author: embedAuthor.JSON): Embed + self.author = author + + return self +end + +--[[ + Add a field to the embeds. +]] +function Embed.Prototype.addField(self: Embed, field: embedField.JSON): Embed + assert(#self.fields > 25, `Embeds can only house up to 25 embeds!`) + + table.insert(self.fields, field) + + return self +end + +--[[ + Responsible for building the Embed JSON that can be parsed by the Discord API. +]] +function Embed.Prototype.build(self: Embed): JSON + return { + title = self.title, + type = embedTypes.EmbedType[self.type], + description = self.description, + url = self.url, + timestamp = self.timestamp, + color = self.color, + footer = self.footer, + image = self.image, + thumbnail = self.thumbnail, + video = self.video, + provider = self.provider, + author = self.author, + fields = self.fields, + } +end + +--[[ + Responsible for creating a new Embed. + + ```lua + + ``` +]] +function Embed.Interface.new(resource: { + title: string?, + type: embedTypes.EmbedType?, + description: string?, + url: string?, + timestamp: datetime.DateTime?, + color: number?, + footer: embedFooter.JSON?, + image: embedImage.JSON?, + thumbnail: embedThumbnail.JSON?, + video: embedVideo.JSON?, + provider: embedProvider.JSON?, + author: embedAuthor.JSON?, + fields: { embedField.JSON }, +}?): Embed + local self = setmetatable( + { + fields = {}, + } :: Embed, + { + __index = Embed.Prototype, + } + ) + + if resource then + if resource.title then + self:setTitle(resource.title) + end + + if resource.type then + self:setType(resource.type) + end + + if resource.description then + self:setDescription(resource.description) + end + + if resource.timestamp then + self:setTimestamp(resource.timestamp) + end + + if resource.color then + self:setColor(resource.color) + end + + if resource.footer then + self:setFooter(resource.footer) + end + + if resource.image then + self:setImage(resource.image) + end + + if resource.thumbnail then + self:setThumbnail(resource.thumbnail) + end + + if resource.video then + self:setVideo(resource.video) + end + + if resource.provider then + self:setProvider(resource.provider) + end + + if resource.author then + self:setAuthor(resource.author) + end + + if resource.fields then + for _, field in resource.fields do + self:addField(field) + end + end + end + + return self +end + +export type Embed = typeof(Embed.Prototype) & { + title: string?, + type: embedTypes.EmbedType?, + description: string?, + url: string?, + timestamp: number?, + color: number?, + footer: embedFooter.JSON?, + image: embedImage.JSON?, + thumbnail: embedThumbnail.JSON?, + video: embedVideo.JSON?, + provider: embedProvider.JSON?, + author: embedAuthor.JSON?, + fields: { embedField.JSON }, +} + +export type JSON = typeof(Embed.Prototype.build(nil :: any)) + +return Embed.Interface diff --git a/packages/builders/src/embed/field.luau b/packages/builders/src/embed/field.luau new file mode 100644 index 0000000..af5fe59 --- /dev/null +++ b/packages/builders/src/embed/field.luau @@ -0,0 +1,94 @@ +--[[ + Implementation of discords Field object as a Luau builder. + + https://discord.com/developers/docs/resources/message#embed-object-embed-field-structure +]] + +local Field = {} + +Field.Prototype = {} +Field.Interface = {} + +--[[ + Set the name of the field +]] +function Field.Prototype.setName(self: Field, name: string): Field + assert(#name <= 256, "Name must be less than 256 characters") + assert(#name > 1, "Name must be greater than 1 characters") + + self.name = name + + return self +end + +--[[ + Set the value of the field +]] +function Field.Prototype.setValue(self: Field, value: string): Field + self.value = value + + return self +end + +--[[ + Set if the field is inline. +]] +function Field.Prototype.setIsInline(self: Field, isInline: boolean): Field + self.inline = isInline + + return self +end + +--[[ + Responsible for building the Field JSON that can be parsed by the Discord API. +]] +function Field.Prototype.build(self: Field): JSON + return { + name = self.name, + value = self.value, + inline = self.inline, + } +end + +--[[ + Responsible for creating a new Field. + + ```lua + + ``` +]] +function Field.Interface.new(resource: { + name: string?, + value: string?, + inline: boolean?, +}?): Field + local self = setmetatable({} :: Field, { + __index = Field.Prototype, + }) + + if resource then + if resource.name then + self:setName(resource.name) + end + + if resource.value then + self:setValue(resource.value) + end + + if resource.inline then + self:setIsInline(resource.inline) + end + end + + return self +end + +export type Field = typeof(Field.Prototype) & { + name: string?, + value: string?, + inline: boolean?, +} + +export type JSON = typeof(Field.Prototype.build({} :: any)) + +return Field.Interface diff --git a/packages/builders/src/embed/footer.luau b/packages/builders/src/embed/footer.luau new file mode 100644 index 0000000..4d101e7 --- /dev/null +++ b/packages/builders/src/embed/footer.luau @@ -0,0 +1,82 @@ +--[[ + Implementation of discords Footer object as a Luau builder. + + https://discord.com/developers/docs/resources/message#embed-object-embed-footer-structure +]] + +local Footer = {} + +Footer.Prototype = {} +Footer.Interface = {} + +--[[ + +]] +function Footer.Prototype.setText(self: Footer, text: string): Footer + assert(#text <= 2048, "Name must be less than 2048 characters") + assert(#text > 1, "Name must be greater than 1 characters") + + self.text = text + + return self +end + +--[[ + +]] +function Footer.Prototype.setIcon(self: Footer, iconUrl: string, proxyIconUrl: string?): Footer + self.iconUrl = iconUrl + self.proxyIconUrl = proxyIconUrl + + return self +end + +--[[ + Responsible for building the Footer JSON that can be parsed by the Discord API. +]] +function Footer.Prototype.build(self: Footer): JSON + return { + text = self.text, + icon_url = self.iconUrl, + proxy_icon_url = self.proxyIconUrl, + } +end + +--[[ + Responsible for creating a new Footer. + + ```lua + + ``` +]] +function Footer.Interface.new(resource: { + text: string?, + iconUrl: string?, + proxyIconUrl: string?, +}?): Footer + local self = setmetatable({} :: Footer, { + __index = Footer.Prototype, + }) + + if resource then + if resource.text then + self:setText(resource.text) + end + + if resource.iconUrl then + self:setIcon(resource.iconUrl, resource.proxyIconUrl) + end + end + + return self +end + +export type Footer = typeof(Footer.Prototype) & { + text: string?, + iconUrl: string?, + proxyIconUrl: string?, +} + +export type JSON = typeof(Footer.Prototype.build({} :: any)) + +return Footer.Interface diff --git a/packages/builders/src/embed/image.luau b/packages/builders/src/embed/image.luau new file mode 100644 index 0000000..fca22c5 --- /dev/null +++ b/packages/builders/src/embed/image.luau @@ -0,0 +1,96 @@ +--[[ + Implementation of discords Image object as a Luau builder. + + https://discord.com/developers/docs/resources/message#embed-object-embed-image-structure +]] + +local Image = {} + +Image.Prototype = {} +Image.Interface = {} + +--[[ + Set the image url for this embed. +]] +function Image.Prototype.setUrl(self: Image, url: string, proxyUrl: string?): Image + self.url = url + self.proxyUrl = proxyUrl + + return self +end + +--[[ + Set the image height for this embed. +]] +function Image.Prototype.setHeight(self: Image, height: number): Image + self.height = height + + return self +end + +--[[ + Set the image width for this embed. +]] +function Image.Prototype.setWidth(self: Image, width: number): Image + self.width = width + + return self +end + +--[[ + Responsible for building the function Image.Prototype.build(self: Image): JSON + JSON that can be parsed by the Discord API. +]] +function Image.Prototype.build(self: Image): JSON + return { + url = self.url, + proxy_url = self.proxyUrl, + height = self.height, + width = self.width, + } +end + +--[[ + Responsible for creating a new Image. + + ```lua + + ``` +]] +function Image.Interface.new(resource: { + url: string, + proxyUrl: string?, + height: number?, + width: number?, +}?): Image + local self = setmetatable({} :: Image, { + __index = Image.Prototype, + }) + + if resource then + if resource.url then + self:setUrl(resource.url, resource.proxyUrl) + end + + if resource.height then + self:setHeight(resource.height) + end + + if resource.width then + self:setWidth(resource.width) + end + end + + return self +end + +export type Image = typeof(Image.Prototype) & { + url: string, + proxyUrl: string?, + height: number?, + width: number?, +} + +export type JSON = typeof(Image.Prototype.build({} :: any)) + +return Image.Interface diff --git a/packages/builders/src/embed/provider.luau b/packages/builders/src/embed/provider.luau new file mode 100644 index 0000000..12531b5 --- /dev/null +++ b/packages/builders/src/embed/provider.luau @@ -0,0 +1,78 @@ +--[[ + Implementation of discords Provider object as a Luau builder. + + https://discord.com/developers/docs/resources/message#embed-object-embed-provider-structure +]] + +local Provider = {} + +Provider.Prototype = {} +Provider.Interface = {} + +--[[ + Set the image url for this embed. +]] +function Provider.Prototype.setName(self: Provider, name: string): Provider + assert(#name <= 256, "Name must be less than 256 characters") + assert(#name > 1, "Name must be greater than 1 characters") + + self.name = name + + return self +end + +--[[ + Set the image height for this embed. +]] +function Provider.Prototype.setUrl(self: Provider, url: string): Provider + self.url = url + + return self +end + +--[[ + Responsible for building the Provider JSON that can be parsed by the Discord API. +]] +function Provider.Prototype.build(self: Provider): JSON + return { + name = self.name, + url = self.url, + } +end + +--[[ + Responsible for creating a new Provider. + + ```lua + + ``` +]] +function Provider.Interface.new(resource: { + name: string?, + url: string?, +}?): Provider + local self = setmetatable({} :: Provider, { + __index = Provider.Prototype, + }) + + if resource then + if resource.name then + self:setName(resource.name) + end + + if resource.url then + self:setUrl(resource.url) + end + end + + return self +end + +export type Provider = typeof(Provider.Prototype) & { + name: string?, + url: string?, +} + +export type JSON = typeof(Provider.Prototype.build({} :: any)) + +return Provider.Interface diff --git a/packages/builders/src/embed/thumbnail.luau b/packages/builders/src/embed/thumbnail.luau new file mode 100644 index 0000000..364f524 --- /dev/null +++ b/packages/builders/src/embed/thumbnail.luau @@ -0,0 +1,95 @@ +--[[ + Implementation of discords Thumbnail object as a Luau builder. + + https://discord.com/developers/docs/resources/message#embed-object-embed-thumbnail-structure +]] + +local Thumbnail = {} + +Thumbnail.Prototype = {} +Thumbnail.Interface = {} + +--[[ + Set the image url for this embed. +]] +function Thumbnail.Prototype.setUrl(self: Thumbnail, url: string, proxyUrl: string?): Thumbnail + self.url = url + self.proxyUrl = proxyUrl + + return self +end + +--[[ + Set the image height for this embed. +]] +function Thumbnail.Prototype.setHeight(self: Thumbnail, height: number): Thumbnail + self.height = height + + return self +end + +--[[ + Set the image width for this embed. +]] +function Thumbnail.Prototype.setWidth(self: Thumbnail, width: number): Thumbnail + self.width = width + + return self +end + +--[[ + Responsible for building the Thumbnail JSON that can be parsed by the Discord API. +]] +function Thumbnail.Prototype.build(self: Thumbnail): JSON + return { + url = self.url, + proxy_url = self.proxyUrl, + height = self.height, + width = self.width, + } +end + +--[[ + Responsible for creating a new Thumbnail. + + ```lua + + ``` +]] +function Thumbnail.Interface.new(resource: { + url: string, + proxyUrl: string?, + height: number?, + width: number?, +}?): Thumbnail + local self = setmetatable({} :: Thumbnail, { + __index = Thumbnail.Prototype, + }) + + if resource then + if resource.url then + self:setUrl(resource.url, resource.proxyUrl) + end + + if resource.height then + self:setHeight(resource.height) + end + + if resource.width then + self:setWidth(resource.width) + end + end + + return self +end + +export type Thumbnail = typeof(Thumbnail.Prototype) & { + url: string, + proxyUrl: string?, + height: number?, + width: number?, +} + +export type JSON = typeof(Thumbnail.Prototype.build({} :: any)) + +return Thumbnail.Interface diff --git a/packages/builders/src/embed/video.luau b/packages/builders/src/embed/video.luau new file mode 100644 index 0000000..b976dd6 --- /dev/null +++ b/packages/builders/src/embed/video.luau @@ -0,0 +1,95 @@ +--[[ + Implementation of discords Video object as a Luau builder. + + https://discord.com/developers/docs/resources/message#embed-object-embed-video-structure +]] + +local Video = {} + +Video.Prototype = {} +Video.Interface = {} + +--[[ + Set the image url for this embed. +]] +function Video.Prototype.setUrl(self: Video, url: string, proxyUrl: string?): Video + self.url = url + self.proxyUrl = proxyUrl + + return self +end + +--[[ + Set the image height for this embed. +]] +function Video.Prototype.setHeight(self: Video, height: number): Video + self.height = height + + return self +end + +--[[ + Set the image width for this embed. +]] +function Video.Prototype.setWidth(self: Video, width: number): Video + self.width = width + + return self +end + +--[[ + Responsible for building the Video JSON that can be parsed by the Discord API. +]] +function Video.Prototype.build(self: Video): JSON + return { + url = self.url, + proxy_url = self.proxyUrl, + height = self.height, + width = self.width, + } +end + +--[[ + Responsible for creating a new Video. + + ```lua + + ``` +]] +function Video.Interface.new(resource: { + url: string, + proxyUrl: string?, + height: number?, + width: number?, +}?): Video + local self = setmetatable({} :: Video, { + __index = Video.Prototype, + }) + + if resource then + if resource.url then + self:setUrl(resource.url, resource.proxyUrl) + end + + if resource.height then + self:setHeight(resource.height) + end + + if resource.width then + self:setWidth(resource.width) + end + end + + return self +end + +export type Video = typeof(Video.Prototype) & { + url: string, + proxyUrl: string?, + height: number?, + width: number?, +} + +export type JSON = typeof(Video.Prototype.build({} :: any)) + +return Video.Interface diff --git a/packages/builders/src/emoji.luau b/packages/builders/src/emoji.luau new file mode 100644 index 0000000..bca2b01 --- /dev/null +++ b/packages/builders/src/emoji.luau @@ -0,0 +1,79 @@ +--[[ + Implementation of discords Emoji object as a Luau builder. + + https://discord.com/developers/docs/resources/emoji#emoji-object +]] + +local validateKebabCase = require("@utils/validateKebabCase") + +local Emoji = {} + +Emoji.Prototype = {} +Emoji.Interface = {} + +--[[ + Set the ID of the emoji to use. +]] +function Emoji.Prototype.setId(self: Emoji, emojiId: string): Emoji + self.emojiId = emojiId + + return self +end + +--[[ + Set the name of the emoji to use. +]] +function Emoji.Prototype.setName(self: Emoji, emojiName: string): Emoji + assert(validateKebabCase(emojiName), `Emoji names are Kebab case!`) + + self.emojiName = emojiName + + return self +end + +--[[ + Responsible for building the Emoji JSON that can be parsed by the Discord API. +]] +function Emoji.Prototype.build(self: Emoji): JSON + return { + id = self.emojiId, + name = self.emojiName, + } +end + +--[[ + Responsible for creating a new Emoji. + + ```lua + + ``` +]] +function Emoji.Interface.new(resource: { + emojiId: string, + emojiName: string, +}?): Emoji + local self = setmetatable({} :: Emoji, { + __index = Emoji.Prototype, + }) + + if resource then + if resource.emojiId then + self:setId(resource.emojiId) + end + + if resource.emojiName then + self:setName(resource.emojiName) + end + end + + return self +end + +export type Emoji = typeof(Emoji.Prototype) & { + emojiId: string?, + emojiName: string?, +} + +export type JSON = typeof(Emoji.Prototype.build(nil :: any)) + +return Emoji.Interface diff --git a/packages/builders/src/forumTag.luau b/packages/builders/src/forumTag.luau new file mode 100644 index 0000000..1893c5e --- /dev/null +++ b/packages/builders/src/forumTag.luau @@ -0,0 +1,120 @@ +--[[ + Implementation of a discord forum tag object as a luau builder. + + https://discord.com/developers/docs/resources/channel#forum-tag-object +]] + +local apiTypes = require("@api-types/apiTypes") + +local validateKebabCase = require("@utils/validateKebabCase") + +local ForumTag = {} + +ForumTag.Prototype = {} +ForumTag.Interface = {} + +--[[ + Sets either the id of a guilds custom emoji, or a unicode character of an emoji. +]] +function ForumTag.Prototype.setEmoji(self: ForumTag, emojiId: string?, emojiName: string?): ForumTag + self.emojiId = emojiId + self.emojiName = emojiName + + return self +end + +--[[ + Sets whether this tag can only be added to or removed from threads by a member with the MANAGE_THREADS permission +]] +function ForumTag.Prototype.setModerated(self: ForumTag, isModerated: boolean): ForumTag + self.moderated = isModerated + + return self +end + +--[[ + Set the name of this Forum Tag. +]] +function ForumTag.Prototype.setName(self: ForumTag, tagName: string): ForumTag + assert(#tagName <= 20, `Tag name must be less than 20 characters.`) + assert(#tagName > 0, `Tag name must be more than 0 characters.`) + + assert(validateKebabCase(tagName), `Tag name must be kebab-case.`) + + self.name = tagName + + return self +end + +--[[ + Responsible for buillding the forum tag object that the Discord API can understand. +]] +function ForumTag.Prototype.build(self: ForumTag): JSON + return { + name = self.name, + moderated = self.moderated, + emoji_id = self.emojiId, + emoji_name = self.emojiName, + } :: apiTypes.ForumTagObject +end + +--[[ + Constructs a new tag builder. + + ```lua + local tag = ForumTag.new("tag-name") + :setModerated(true) + :build() + ``` +]] +function ForumTag.Interface.new(resource: { + tagName: string, + emoji: { + name: string, + id: apiTypes.Snowflake, + }?, +}): ForumTag + local self = setmetatable( + { + name = resource.tagName, + moderated = true, + + emojiId = resource.emoji and resource.emoji.id, + emojiName = resource.emoji and resource.emoji.name, + } :: ForumTag, + { __index = ForumTag.Prototype } + ) + + return self +end + +--[[ + Enables us to construct a Tag Builder object from an existing tag object, used when modifying forum tags. +]] +function ForumTag.Interface.fromId(resource: { + tagId: apiTypes.Snowflake, + tagName: string, + emoji: { + name: string, + id: apiTypes.Snowflake, + }?, +}): ForumTag + local self = ForumTag.Interface.new(resource) + + self.id = resource.tagId + + return self +end + +export type ForumTag = typeof(ForumTag.Prototype) & { + emojiId: apiTypes.Snowflake?, + emojiName: string?, + id: apiTypes.Snowflake?, + + moderated: boolean, + name: string, +} + +export type JSON = typeof(ForumTag.Prototype.build(nil :: any)) + +return ForumTag.Interface diff --git a/packages/builders/src/guild/guild.luau b/packages/builders/src/guild/guild.luau new file mode 100644 index 0000000..c24ff71 --- /dev/null +++ b/packages/builders/src/guild/guild.luau @@ -0,0 +1,390 @@ +--[[ + Implementation of discords Permission Guild object as a Luau builder. + + See permissions for more information about the allow and deny fields. + + https://discord.com/developers/docs/resources/guild +]] + +local apiTypes = require("@api-types/apiTypes") +local guildTypes = require("@api-types/guild") + +local Guild = {} + +Guild.Prototype = {} +Guild.Interface = {} + +--[[ + set the name for this guild +]] +function Guild.Prototype.setName(self: Guild, name: string): Guild + assert(#name <= 100, "Name must be less than 100 characters") + assert(#name > 1, "Name must be greater than 1 characters") + + self.name = name + + return self +end + +--[[ + set the explicit verification level for this guild +]] +function Guild.Prototype.setVerificationLevel(self: Guild, verificationLevel: guildTypes.VerificationLevel): Guild + assert(guildTypes.VerificationLevel[verificationLevel], "Invalid verification level") + + self.verificationLevel = verificationLevel + + return self +end + +--[[ + set the explicit default message notifications for this guild +]] +function Guild.Prototype.setDefaultMessageNotifications( + self: Guild, + defaultMessageNotifications: guildTypes.DefaultMessageNotification +): Guild + assert( + guildTypes.DefaultMessageNotification[defaultMessageNotifications], + "Invalid defaultMessageNotifications level" + ) + + self.defaultMessageNotifications = defaultMessageNotifications + + return self +end + +--[[ + set the explicit content filter level for this guild +]] +function Guild.Prototype.setExplicitContentFilter( + self: Guild, + explicitContentFilter: guildTypes.ExplicitContentFilterLevel +): Guild + assert(guildTypes.ExplicitContentFilterLevel[explicitContentFilter], "Invalid explicitContentFilter level") + + self.explicitContentFilter = explicitContentFilter + + return self +end + +--[[ + set the id for afk channel +]] +function Guild.Prototype.setAfkChannelId(self: Guild, afkChannelId: apiTypes.Snowflake): Guild + self.afkChannelId = afkChannelId + + return self +end + +--[[ + afk timeout in seconds, can be set to: 60, 300, 900, 1800, 3600 +]] +function Guild.Prototype.setAfkTimeout(self: Guild, afkTimeout: number): Guild + self.afkTimeout = afkTimeout + + return self +end + +--[[ + base64 1024x1024 png/jpeg/gif image for the guild icon (can be animated gif when the server has + the ANIMATED_ICON feature) +]] +function Guild.Prototype.setIcon(self: Guild, source: string): Guild + self.icon = source + + return self +end + +--[[ + user id to transfer guild ownership to (must be owner) +]] +function Guild.Prototype.setOwnerId(self: Guild, ownerId: apiTypes.Snowflake): Guild + self.ownerId = ownerId + + return self +end + +--[[ + base64 16:9 png/jpeg image for the guild splash (when the server has the INVITE_SPLASH feature) +]] +function Guild.Prototype.setSplash(self: Guild, source: string): Guild + self.splash = source + + return self +end + +--[[ + base64 16:9 png/jpeg image for the guild discovery splash (when the server has the DISCOVERABLE feature) +]] +function Guild.Prototype.setDiscoverySplash(self: Guild, source: string): Guild + self.discoverySplash = source + + return self +end + +--[[ + base64 16:9 png/jpeg image for the guild banner (when the server has the BANNER feature; can be animated + gif when the server has the ANIMATED_BANNER feature) +]] +function Guild.Prototype.setBanner(self: Guild, source: string): Guild + self.banner = source + + return self +end + +--[[ + the id of the channel where guild notices such as welcome messages and boost events are posted +]] +function Guild.Prototype.setSystemChannelId(self: Guild, systemChannelId: apiTypes.Snowflake): Guild + self.systemChannelId = systemChannelId + + return self +end + +--[[ + set the system channel flags +]] +function Guild.Prototype.setSystemChannelFlags(self: Guild, systemChannelFlags: number): Guild + self.systemChannelFlags = systemChannelFlags + + return self +end + +--[[ + the id of the channel where Community guilds display rules and/or guidelines +]] +function Guild.Prototype.setRulesChannelId(self: Guild, rulesChannelId: apiTypes.Snowflake): Guild + self.rulesChannelId = rulesChannelId + + return self +end + +--[[ + the id of the channel where admins and moderators of Community guilds receive notices from Discord +]] +function Guild.Prototype.setPublicUpdatesChannelId(self: Guild, publicUpdatesChannelId: apiTypes.Snowflake): Guild + self.publicUpdatesChannelId = publicUpdatesChannelId + + return self +end + +--[[ + the preferred locale of a Community guild used in server discovery and notices from Discord; defaults to "en-US" +]] +function Guild.Prototype.setPreferredLocale(self: Guild, preferredLocale: apiTypes.LanguageLocales): Guild + self.preferredLocale = preferredLocale + + return self +end + +--[[ + enable a specific guild feature for this guild +]] +function Guild.Prototype.addFeature(self: Guild, feature: apiTypes.GuildFeature): Guild + table.insert(self.features, feature) + + return self +end + +--[[ + the description for the guild +]] +function Guild.Prototype.setDescription(self: Guild, description: string): Guild + self.description = description + + return self +end + +--[[ + whether the guild's boost progress bar should be enabled +]] +function Guild.Prototype.setPremiumProgressBarEnabled(self: Guild, premiumProgressBarEnabled: boolean): Guild + self.premiumProgressBarEnabled = premiumProgressBarEnabled + + return self +end + +--[[ + the id of the channel where admins and moderators of Community guilds receive safety alerts from Discord +]] +function Guild.Prototype.setSafetyAlertsChannelId(self: Guild, safetyAlertsChannelId: apiTypes.Snowflake): Guild + self.safetyAlertsChannelId = safetyAlertsChannelId + + return self +end + +--[[ + Responsible for building the Guild JSON that can be parsed by the Discord API. +]] +function Guild.Prototype.build(self: Guild): JSON + return { + name = self.name, + verification_level = guildTypes.VerificationLevel[self.verificationLevel], + default_message_notifications = guildTypes.DefaultMessageNotification[self.defaultMessageNotifications], + explicit_content_filter = guildTypes.ExplicitContentFilterLevel[self.explicitContentFilter], + afk_channel_id = self.afkChannelId, + afk_timeout = self.afkTimeout, + icon = self.icon, + owner_id = self.ownerId, + splash = self.splash, + discovery_splash = self.discoverySplash, + banner = self.banner, + system_channel_id = self.systemChannelId, + system_channel_flags = self.systemChannelFlags, + rules_channel_id = self.rulesChannelId, + public_updates_channel_id = self.publicUpdatesChannelId, + preferred_locale = self.preferredLocale, + features = #self.features ~= 0 and self.features or nil, + description = self.description, + premium_progress_bar_enabled = self.premiumProgressBarEnabled, + safety_alerts_channel_id = self.safetyAlertsChannelId, + } +end + +--[[ + Responsible for creating a new Guild. + + ```lua + + ``` +]] +function Guild.Interface.new(resource: { + name: string?, + verificationLevel: guildTypes.VerificationLevel, + defaultMessageNotifications: guildTypes.DefaultMessageNotification, + explicitContentFilter: guildTypes.ExplicitContentFilterLevel, + afkChannelId: apiTypes.Snowflake?, + afkTimeout: number?, + icon: string?, + ownerId: apiTypes.Snowflake?, + splash: string?, + discoverySplash: string?, + banner: string?, + systemChannelId: apiTypes.Snowflake?, + systemChannelFlags: number?, + rulesChannelId: apiTypes.Snowflake?, + publicUpdatesChannelId: apiTypes.Snowflake?, + preferredLocale: apiTypes.LanguageLocales?, + features: { apiTypes.GuildFeature }, + description: string?, + premiumProgressBarEnabled: boolean?, + safetyAlertsChannelId: apiTypes.Snowflake?, +}?): Guild + local self = setmetatable({} :: Guild, { + __index = Guild.Prototype, + }) + + if resource then + if resource.name then + self:setName(resource.name) + end + + if resource.verificationLevel then + self:setVerificationLevel(resource.verificationLevel) + end + + if resource.defaultMessageNotifications then + self:setDefaultMessageNotifications(resource.defaultMessageNotifications) + end + + if resource.explicitContentFilter then + self:setExplicitContentFilter(resource.explicitContentFilter) + end + + if resource.afkChannelId then + self:setAfkChannelId(resource.afkChannelId) + end + + if resource.afkTimeout then + self:setAfkTimeout(resource.afkTimeout) + end + + if resource.icon then + self:setIcon(resource.icon) + end + + if resource.ownerId then + self:setOwnerId(resource.ownerId) + end + + if resource.splash then + self:setSplash(resource.splash) + end + + if resource.discoverySplash then + self:setDiscoverySplash(resource.discoverySplash) + end + + if resource.banner then + self:setBanner(resource.banner) + end + + if resource.systemChannelId then + self:setSystemChannelId(resource.systemChannelId) + end + + if resource.systemChannelFlags then + self:setSystemChannelFlags(resource.systemChannelFlags) + end + + if resource.rulesChannelId then + self:setRulesChannelId(resource.rulesChannelId) + end + + if resource.publicUpdatesChannelId then + self:setPublicUpdatesChannelId(resource.publicUpdatesChannelId) + end + + if resource.preferredLocale then + self:setPreferredLocale(resource.preferredLocale) + end + + if resource.features then + for _, feature: apiTypes.GuildFeature in resource.features do + self:addFeature(feature) + end + end + + if resource.description then + self:setDescription(resource.description) + end + + if resource.premiumProgressBarEnabled then + self:setPremiumProgressBarEnabled(resource.premiumProgressBarEnabled) + end + + if resource.safetyAlertsChannelId then + self:setSafetyAlertsChannelId(resource.safetyAlertsChannelId) + end + end + + return self +end + +export type Guild = typeof(Guild.Prototype) & { + name: string?, + verificationLevel: guildTypes.VerificationLevel, + defaultMessageNotifications: guildTypes.DefaultMessageNotification, + explicitContentFilter: guildTypes.ExplicitContentFilterLevel, + afkChannelId: apiTypes.Snowflake?, + afkTimeout: number?, + icon: string?, + ownerId: apiTypes.Snowflake?, + splash: string?, + discoverySplash: string?, + banner: string?, + systemChannelId: apiTypes.Snowflake?, + systemChannelFlags: number?, + rulesChannelId: apiTypes.Snowflake?, + publicUpdatesChannelId: apiTypes.Snowflake?, + preferredLocale: apiTypes.LanguageLocales?, + features: { apiTypes.GuildFeature }, + description: string?, + premiumProgressBarEnabled: boolean?, + safetyAlertsChannelId: apiTypes.Snowflake?, +} + +export type JSON = typeof(Guild.Prototype.build(nil :: any)) + +return Guild.Interface diff --git a/packages/builders/src/guild/overwrite.luau b/packages/builders/src/guild/overwrite.luau new file mode 100644 index 0000000..5cc4dcf --- /dev/null +++ b/packages/builders/src/guild/overwrite.luau @@ -0,0 +1,125 @@ +--[[ + Implementation of discords Permission Overwrite object as a Luau builder. + + See permissions for more information about the allow and deny fields. + + https://discord.com/developers/docs/topics/permissions#permissions + https://discord.com/developers/docs/resources/channel#overwrite-object +]] + +local apiTypes = require("@api-types/apiTypes") +local permissionTypes = require("@api-types/permission") + +local bit = require("@vendor/bit") + +local Overwrite = {} + +Overwrite.Prototype = {} +Overwrite.Interface = {} + +--[[ + Responsible for allowing specific permissionTypes to be set on either a Role of a Member. +]] +function Overwrite.Prototype.allowPermissions(self: Overwrite, ...: permissionTypes.Permissions): Overwrite + for _, permissionTypes in { ... } do + table.insert(self.allowedPermissionFlags, permissionTypes) + end + + return self +end + +--[[ + Responsible for denying specific permissionTypes to be set on either a Role of a Member. +]] +function Overwrite.Prototype.denyPermissions(self: Overwrite, ...: permissionTypes.Permissions): Overwrite + for _, permissionType in { ... } do + table.insert(self.deniedPermissionFlags, permissionType) + end + + return self +end + +--[[ + Responsible for building the OverwriteObject JSON that can be parsed by the Discord API. +]] +function Overwrite.Prototype.build(self: Overwrite): JSON + local allowPermissionBits = {} + local denyPermissionBits = {} + + for index, permissionEnum in self.allowedPermissionFlags do + local permissionBit = permissionTypes.Permissions[permissionEnum] + + allowPermissionBits[index] = permissionBit + end + + for index, permissionEnum in self.deniedPermissionFlags do + local permissionBit = permissionTypes.Permissions[permissionEnum] + + denyPermissionBits[index] = permissionBit + end + + return { + type = self.type == "role" and 0 or 1, + + allow = tostring(bit.bor(table.unpack(allowPermissionBits))), + deny = tostring(bit.bor(table.unpack(denyPermissionBits))), + } +end + +--[[ + Responsible for creating a new Overwrite. + + ```lua + + ``` +]] +function Overwrite.Interface.new(resource: { + permissionType: PermissionOverwriteType, + allowFlags: { permissionTypes.Permissions }?, + denyFlags: { permissionTypes.Permissions }?, +}): Overwrite + return setmetatable( + { + allowedPermissionFlags = resource.allowFlags or {}, + deniedPermissionFlags = resource.denyFlags or {}, + + type = resource.permissionType, + } :: Overwrite, + { + __index = Overwrite.Prototype, + } + ) +end + +--[[ + Responsible for creating a new Overwrite from an existing ID/Object. + + Supports properties that the .new constructor doesn't support. (id) +]] +function Overwrite.Interface.fromId(resource: { + permissionId: apiTypes.Snowflake, + permissionType: PermissionOverwriteType, + allowFlags: { permissionTypes.Permissions }?, + denyFlags: { permissionTypes.Permissions }?, +}): Overwrite + local self = Overwrite.Interface.new(resource) + + self.id = resource.permissionId + + return self +end + +export type Overwrite = typeof(Overwrite.Prototype) & { + allowedPermissionFlags: { permissionTypes.Permissions }, + deniedPermissionFlags: { permissionTypes.Permissions }, + + type: PermissionOverwriteType, + id: apiTypes.Snowflake, +} + +export type JSON = typeof(Overwrite.Prototype.build(nil :: any)) + +export type PermissionOverwriteType = "role" | "member" +export type PermissionOverwriteObject = apiTypes.OverwriteObject + +return Overwrite.Interface diff --git a/packages/builders/src/guild/role.luau b/packages/builders/src/guild/role.luau new file mode 100644 index 0000000..5083a9c --- /dev/null +++ b/packages/builders/src/guild/role.luau @@ -0,0 +1,147 @@ +--[[ + Implementation of a Discord Guild Role as a Luau builder. + + https://discord.com/developers/docs/resources/guild#create-guild-role-json-params +]] + +local apiTypes = require("@api-types/apiTypes") +local permissionTypes = require("@api-types/permission") + +local bit = require("@vendor/bit") + +local validateKebabCase = require("@utils/validateKebabCase") + +local Role = {} + +Role.Prototype = {} +Role.Interface = {} + +--[[ + Sets the name of the role, optionally this could be defined when creating the Role Builder as well. +]] +function Role.Prototype.setName(self: Role, roleName: string): Role + assert(#roleName <= 100, `Role name must be less than 100 characters.`) + assert(#roleName > 1, `Role name must be more than 1 characters.`) + + assert(validateKebabCase(roleName), `Tag name must be kebab-case.`) + + self.name = roleName + + return self +end + +--[[ + Sets the permissions of the role, optionally this could be defined when creating the Role Builder as well. +]] +function Role.Prototype.setPermissions(self: Role, ...: permissionTypes.Permissions): Role + for _, permissionType in { ... } do + table.insert(self.permissionFlags, permissionType) + end + + return self +end + +--[[ + Sets the color of the role. Colors are presennted as a hexadecimal number. +]] +function Role.Prototype.setColor(self: Role, color: number): Role + self.color = color + + return self +end + +--[[ + Sets the role to be hoisted. A hoisted role is displayed in the user listing, vs's an unhoisted role which is not displayed at all. +]] +function Role.Prototype.setHoisted(self: Role, isHoisted: boolean): Role + self.isHoisted = isHoisted + + return self +end + +--[[ + Sets the role to be mentionable through the @ mention. +]] +function Role.Prototype.setMentionable(self: Role, isMentionable: boolean): Role + self.isMentionable = isMentionable + + return self +end + +--[[ + Responsible for buillding the role object that the Discord API can understand. +]] +function Role.Prototype.build(self: Role): JSON + local permissionBits = {} + + for index, permissionEnum in self.permissionFlags do + local permissionBit = permissionTypes.Permissions[permissionEnum] + + permissionBits[index] = permissionBit + end + + return { + name = self.name, + permissions = tostring(bit.bor(table.unpack(permissionBits))), + color = self.color, + hoist = self.isHoisted, + icon = self.icon, + unicode_emoji = self.unicodeEmoji, + mentionable = self.isMentionable, + } :: apiTypes.GuildRoleObject +end + +--[[ + Constructs a new Role Builder. + + ```lua + local role = Role.new("Role Name", { permissions.Permission.SendMessages }) + :setColor(0xFFFFFF) + :setHoisted(true) + :setMentionable(true) + :build() + ``` +]] +function Role.Interface.new(resource: { + roleName: string?, + rolePermissions: { permissionTypes.Permissions }?, +}): Role + local self = setmetatable( + { + name = "", + permissionFlags = {}, + color = 0xFFFFFF, + isHoisted = false, + isMentionable = true, + + -- icon, + -- unicodeEmoji, + } :: Role, + { __index = Role.Prototype } + ) + + if resource.roleName then + self:setName(resource.roleName) + end + + if resource.rolePermissions then + -- HACK: Typecasting here so that all varargs are typed as a Permission. + self:setPermissions(table.unpack(resource.rolePermissions) :: permissionTypes.Permissions) + end + + return self +end + +export type Role = typeof(Role.Prototype) & { + name: string, + permissionFlags: { permissionTypes.Permissions }, + color: number, + isHoisted: boolean, + icon: string?, + unicodeEmoji: string?, + isMentionable: boolean, +} + +export type JSON = typeof(Role.Prototype.build(nil :: any)) + +return Role.Interface diff --git a/packages/builders/src/intents.luau b/packages/builders/src/intents.luau new file mode 100644 index 0000000..57e8808 --- /dev/null +++ b/packages/builders/src/intents.luau @@ -0,0 +1,70 @@ +--[[ + Implementation of discord intents as a luau builder. + + https://discord.com/developers/docs/topics/gateway#list-of-intents +]] + +local intents = require("@api-types/gateway/intents") + +local bit = require("@vendor/bit") + +local Intents = {} + +Intents.Prototype = {} +Intents.Interface = {} + +--[[ + Sets either the id of a guilds custom emoji, or a unicode character of an emoji. +]] +function Intents.Prototype.addIntent(self: Intents, intent: intents.Intent): Intents + assert(intents[intent], `Invalid intent: {intent}`) + assert(table.find(self.intents, intent) == nil, `Intent {intent} already set`) + + table.insert(self.intents, intent) + + return self +end + +--[[ + Responsible for building all intent permissions into an integer that the Discord API can parse +]] +function Intents.Prototype.build(self: Intents): number + local intentsValue = 0 + + for _, intentEnum in self.intents do + local intentDisposition = intents[intentEnum] + + assert(intentDisposition, `Unexpected intent '{intentEnum}'`) + + intentsValue += bit.lshift(1, intentDisposition) :: number + end + + return intentsValue +end + +--[[ + Constructs a new intents builder. + + ```lua + Intents.new() + :addIntent("GuildMembers") + :addIntent("GuildMessages") + :build() + ``` +]] +function Intents.Interface.new(intents: {}?): Intents + local self = setmetatable( + { + intents = intents or {}, + } :: Intents, + { __index = Intents.Prototype } + ) + + return self +end + +export type Intents = typeof(Intents.Prototype) & { + intents: { intents.Intent }, +} + +return Intents.Interface diff --git a/packages/builders/src/interaction/choice.luau b/packages/builders/src/interaction/choice.luau new file mode 100644 index 0000000..8f5cdd8 --- /dev/null +++ b/packages/builders/src/interaction/choice.luau @@ -0,0 +1,126 @@ +--[[ + Implementation of the Application Command Option Choice Structure in Luau. + + https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-choice-structure +]] + +local validateKebabCase = require("@utils/validateKebabCase") + +local apiTypes = require("@api-types/apiTypes") + +local Choice = {} + +Choice.Prototype = {} +Choice.Interface = {} + +--[[ + Responsible for setting the name of the choice. Name must be kebab case, with 1-100 characters. +]] +function Choice.Prototype.setName(self: Choice, name: string): Choice + assert(#name <= 100, "name must be less than 100 characters") + assert(#name >= 1, "name must be at least 1 character") + + validateKebabCase(name) + + self.name = name + + return self +end + +--[[ + Adding localization to the Name of the choice, enabling developers to create milti-language support. +]] +function Choice.Prototype.setNameLocalization( + self: Choice, + localization: apiTypes.LanguageLocales, + name: string +): Choice + assert(#name <= 100, "name must be less than 100 characters") + assert(#name >= 1, "name must be at least 1 character") + + validateKebabCase(name) + + self.nameLocalizations[localization] = name + + return self +end + +--[[ + Set the value of the choice, value can only be either a string or number. +]] +function Choice.Prototype.setValue(self: Choice, value: string | number): Choice + local typeofValue = type(value) + + assert(typeofValue == "string" or typeofValue == "number", "value must be a string or number") + + if typeofValue == "string" then + assert(#(value :: string) <= 100, "value must be less than 100 characters") + end + + self.value = value + + return self +end + +--[[ + Responsible for buillding the default reaction object that the Discord API can understand. +]] +function Choice.Prototype.build(self: Choice): JSON + assert(self.name, `Missing name for choice: {self.name}`) + assert(self.value, `Missing value for choice: {self.name}`) + + return { + name = self.name, + name_localizations = self.nameLocalizations, + value = self.value, + } +end + +--[[ + Constructor for the Discord Default Reaction Builder. + + ```lua + local defaultReaction = Choice.new({ + name = "example-choice", + value = "example-value", + }):build() + ``` +]] +function Choice.Interface.new(resource: { + name: string?, + nameLocalizations: { [apiTypes.LanguageLocales]: string }?, + value: string | number?, +}?): Choice + local self = setmetatable( + { + nameLocalizations = {}, + } :: Choice, + { __index = Choice.Prototype } + ) + + if resource and resource.name then + self:setName(resource.name) + end + + if resource and resource.nameLocalizations then + for localization: apiTypes.LanguageLocales, value in resource.nameLocalizations do + self:setNameLocalization(localization, value) + end + end + + if resource and resource.value then + self:setValue(resource.value) + end + + return self +end + +export type Choice = typeof(Choice.Prototype) & { + name: string, + nameLocalizations: { [apiTypes.LanguageLocales]: string }, + value: string | number, +} + +export type JSON = typeof(Choice.Prototype.build(nil :: any)) + +return Choice.Interface diff --git a/packages/builders/src/interaction/interaction.luau b/packages/builders/src/interaction/interaction.luau new file mode 100644 index 0000000..b636611 --- /dev/null +++ b/packages/builders/src/interaction/interaction.luau @@ -0,0 +1,273 @@ +--[[ + Implementation of a discord Interaction object as a luau builder. + + https://discord.com/developers/docs/interactions/application-commands#edit-global-application-command-json-params +]] + +local apiTypes = require("@api-types/apiTypes") +local interactionTypes = require("@api-types/interaction") +local applicationTypes = require("@api-types/application") + +local interactionOption = require("@builders/interaction/option") + +local validateKebabCase = require("@utils/validateKebabCase") + +local Interaction = {} + +Interaction.Prototype = {} +Interaction.Interface = {} + +--[[ + Name of command, 1-32 characters +]] +function Interaction.Prototype.setName(self: Interaction, name: string): Interaction + assert(#name <= 32, "name must be less than 32 characters") + assert(#name >= 1, "name must be at least 1 character") + + self.name = name + + return self +end + +--[[ + Set the Type of command +]] +function Interaction.Prototype.setType( + self: Interaction, + commandType: interactionTypes.ApplicationCommandType +): Interaction + self.type = commandType + + return self +end + +--[[ + Localization dictionary for the name field. Values follow the same restrictions as name +]] +function Interaction.Prototype.setNameLocalization( + self: Interaction, + localization: apiTypes.LanguageLocales, + name: string +): Interaction + assert(#name <= 32, "name must be less than 32 characters") + assert(#name >= 1, "name must be at least 1 character") + + validateKebabCase(name) + + self.nameLocalizations[localization] = name + + return self +end + +--[[ + 1-100 character description +]] +function Interaction.Prototype.setDescription(self: Interaction, description: string): Interaction + assert(#description <= 100, "description must be less than 100 characters") + assert(#description >= 1, "description must be at least 1 character") + + self.description = description + + return self +end + +--[[ + Localization dictionary for the description field. Values follow the same restrictions as description +]] +function Interaction.Prototype.setDescriptionLocalization( + self: Interaction, + localization: apiTypes.LanguageLocales, + description: string +): Interaction + assert(#description <= 100, "description must be less than 100 characters") + assert(#description >= 1, "description must be at least 1 character") + + self.descriptionLocalizations[localization] = description + + return self +end + +--[[ + the parameters for the command +]] +function Interaction.Prototype.addOption(self: Interaction, option: interactionOption.JSON): Interaction + table.insert(self.options, option) + + return self +end + +--[[ + Set of permissions represented as a bit set, recommended that you use the permission builder. +]] +function Interaction.Prototype.setDefaultMemberPermissions(self: Interaction, permissions: string): Interaction + self.defaultMemberPermissions = permissions + + return self +end + +--[[ + Installation context(s) where the command is available +]] +function Interaction.Prototype.addIntegrationType( + self: Interaction, + type: applicationTypes.IntegrationTypesConfig +): Interaction + table.insert(self.integrationTypes, type) + + return self +end + +--[[ + Interaction context(s) where the command can be used +]] +function Interaction.Prototype.addContext( + self: Interaction, + context: interactionTypes.InteractionContextType +): Interaction + table.insert(self.contexts, context) + + return self +end + +--[[ + Indicates whether the command is age-restricted +]] +function Interaction.Prototype.setNsfw(self: Interaction, isNsfw: boolean): Interaction + self.nsfw = isNsfw + + return self +end + +--[[ + Responsible for buillding the default reaction object that the Discord API can understand. +]] +function Interaction.Prototype.build(self: Interaction): JSON + assert(self.name, "name must be set") + assert(#self.integrationTypes > 0, `must have one or more integration types set`) + assert(#self.contexts > 0, `must have one or more contexts set`) + + local contexts: { number } = {} + local integrationTypeArray: { number } = {} + + for _, context in self.contexts do + table.insert(contexts, interactionTypes.InteractionContextType[context]) + end + + for _, interactionType in self.integrationTypes do + table.insert(integrationTypeArray, applicationTypes.IntegrationTypesConfig[interactionType]) + end + + return { + id = nil :: any, + application_id = nil :: any, + version = nil :: any, + guild_id = nil :: any, + name = self.name, + name_localizations = self.nameLocalizations, + description = self.description, + description_localizations = self.descriptionLocalizations, + options = self.options, + default_member_permissions = self.defaultMemberPermissions, + dm_permission = nil :: any, + default_permission = nil :: any, + integration_types = integrationTypeArray, + contexts = contexts, + type = interactionTypes.ApplicationCommandType[self.type] or nil, + nsfw = self.nsfw, + } +end + +--[[ + Constructor for the Discord Default Reaction Builder. + + ```lua + local defaultReaction = Interaction.new("000000000000000000", "secret-emoji"):build() + ``` +]] +function Interaction.Interface.new(resource: { + name: string?, + nameLocalizations: { [apiTypes.LanguageLocales]: string }?, + description: string?, + descriptionLocalizations: { [apiTypes.LanguageLocales]: string }?, + options: { interactionOption.JSON }?, + defaultMemberPermissions: string?, + integrationTypes: { applicationTypes.IntegrationTypesConfig }?, + contexts: { interactionTypes.InteractionContextType }?, + nsfw: boolean?, +}?): Interaction + local self = setmetatable( + { + nameLocalizations = {}, + descriptionLocalizations = {}, + options = {}, + integrationTypes = {}, + contexts = {}, + } :: Interaction, + { __index = Interaction.Prototype } + ) + + if resource and resource.name then + self:setName(resource.name) + end + + if resource and resource.nameLocalizations then + for localization: apiTypes.LanguageLocales, name in resource.nameLocalizations do + self:setNameLocalization(localization, name) + end + end + + if resource and resource.description then + self:setDescription(resource.description) + end + + if resource and resource.descriptionLocalizations then + for localization: apiTypes.LanguageLocales, name in resource.descriptionLocalizations do + self:setDescriptionLocalization(localization, name) + end + end + + if resource and resource.options then + for _, option in resource.options do + self:addOption(option) + end + end + + if resource and resource.defaultMemberPermissions then + self:setDefaultMemberPermissions(resource.defaultMemberPermissions) + end + + if resource and resource.integrationTypes then + for _, type: applicationTypes.IntegrationTypesConfig in resource.integrationTypes do + self:addIntegrationType(type) + end + end + + if resource and resource.contexts then + for _, context: interactionTypes.InteractionContextType in resource.contexts do + self:addContext(context) + end + end + + if resource and resource.nsfw then + self:setNsfw(resource.nsfw) + end + + return self +end + +export type Interaction = typeof(Interaction.Prototype) & { + name: string, + nameLocalizations: { [apiTypes.LanguageLocales]: string }, + description: string, + descriptionLocalizations: { [apiTypes.LanguageLocales]: string }, + options: { interactionOption.JSON }, + defaultMemberPermissions: string?, + integrationTypes: { applicationTypes.IntegrationTypesConfig }, + contexts: { interactionTypes.InteractionContextType }, + type: interactionTypes.ApplicationCommandType, + nsfw: boolean?, +} + +export type JSON = typeof(Interaction.Prototype.build(nil :: any)) + +return Interaction.Interface diff --git a/packages/builders/src/interaction/option.luau b/packages/builders/src/interaction/option.luau new file mode 100644 index 0000000..c180f41 --- /dev/null +++ b/packages/builders/src/interaction/option.luau @@ -0,0 +1,297 @@ +--[[ + Implementation of the Application Command Option Structure in Luau. + + https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-structure +]] + +local apiTypes = require("@api-types/apiTypes") +local interactionTypes = require("@api-types/interaction") +local channelTypes = require("@api-types/channel") + +local interactionChoice = require("@builders/interaction/choice") + +local validateKebabCase = require("@utils/validateKebabCase") + +local Option = {} + +Option.Prototype = {} +Option.Interface = {} + +--[[ + Set the Type of option that will be built. +]] +function Option.Prototype.setType(self: Option, type: interactionTypes.ApplicationCommandOptionType): Option + self.type = type + + return self +end + +--[[ + Set the name of this option, option name needs to be kebab case. +]] +function Option.Prototype.setName(self: Option, name: string): Option + assert(#name <= 32, "name must be less than 32 characters") + assert(#name >= 1, "name must be at least 1 character") + + validateKebabCase(name) + + self.name = name + + return self +end + +--[[ + add a name localization for this option. +]] +function Option.Prototype.setNameLocalization( + self: Option, + localization: apiTypes.LanguageLocales, + name: string +): Option + assert(#name <= 32, "name must be less than 32 characters") + assert(#name >= 1, "name must be at least 1 character") + + validateKebabCase(name) + + self.nameLocalizations[localization] = name + + return self +end + +--[[ + set the description of this option. +]] +function Option.Prototype.setDescription(self: Option, description: string): Option + assert(#description <= 100, "description must be less than 100 characters") + assert(#description >= 1, "description must be at least 1 character") + + self.description = description + + return self +end + +--[[ + add a description localization for this option. +]] +function Option.Prototype.setDescriptionLocalization( + self: Option, + localization: apiTypes.LanguageLocales, + description: string +): Option + assert(#description <= 100, "description must be less than 100 characters") + assert(#description >= 1, "description must be at least 1 character") + + self.descriptionLocalizations[localization] = description + + return self +end + +--[[ + set if the option is required, will not work on SubCommand or SubCommandGroup options. +]] +function Option.Prototype.setRequired(self: Option, isRequired: boolean): Option + assert( + self.type == "SubCommand" or self.type == "SubCommandGroup", + `Required cannot be set on SubCommands, or SubCommandGroups` + ) + + self.required = isRequired + + return self +end + +--[[ + add a choice to the option, will only work on String, Integer, or Number options. +]] +function Option.Prototype.addChoice(self: Option, choice: interactionChoice.JSON): Option + assert( + self.type == "String" or self.type == "Integer" or self.type == "Number", + "Choices can only be added to String, Integer, or Number options" + ) + + table.insert(self.choices, choice) + + return self +end + +--[[ + add an option to the option, will only work on SubCommand or SubCommandGroup options. +]] +function Option.Prototype.addOption(self: Option, option: JSON): Option + assert( + self.type == "SubCommand" or self.type == "SubCommandGroup", + `Options can only be added to SubCommands, or SubCommandGroups` + ) + + table.insert(self.options, option) + + return self +end + +--[[ + add supported channel types, will only work on Channel options. +]] +function Option.Prototype.addChannelType(self: Option, channelType: channelTypes.ChannelType): Option + assert(self.type == "Channel", `Channel types can only be added to Channel options`) + + table.insert(self.channelTypes, channelType) + + return self +end + +--[[ + set the min value of this option, will only work on Integer or Number options. +]] +function Option.Prototype.setMinValue(self: Option, value: number): Option + assert(self.type == "Integer" or self.type == "Number", "Min values can only be set on Integer or Number options") + + self.minValue = value + + return self +end + +--[[ + set the max value of this option, will only work on Integer or Number options. +]] +function Option.Prototype.setMaxValue(self: Option, value: number): Option + assert(self.type == "Integer" or self.type == "Number", "Min values can only be set on Integer or Number options") + + self.maxValue = value + + return self +end + +--[[ + set the min length of this option, will only work on String options. +]] +function Option.Prototype.setMinLength(self: Option, value: number): Option + assert(self.type == "String", "Min values can only be set on String options") + + self.minLength = value + + return self +end + +--[[ + set the max length of this option, will only work on String options. +]] +function Option.Prototype.setMaxLength(self: Option, value: number): Option + assert(self.type == "String", "Max values can only be set on String options") + + self.maxLength = value + + return self +end + +--[[ + If autocomplete interactions are enabled for this option, will only work on String, Integer, or Number options. +]] +function Option.Prototype.setAutocompleteEnabled(self: Option, autocompleteEnabled: boolean): Option + assert( + self.type == "String" or self.type == "Integer" or self.type == "Number", + `Autocomplete can only be set on String, Integer, or Number options` + ) + + self.autocomplete = autocompleteEnabled + + return self +end + +--[[ + Responsible for buillding the default reaction object that the Discord API can understand. +]] +function Option.Prototype.build(self: Option): JSON + local channelTypeArray = {} + + for _, channelType in self.channelTypes do + table.insert(channelTypeArray, channelTypes.ChannelTypes[channelType]) + end + + assert(self.name, `Missing name for option: {self.name}`) + assert(self.description, `Missing description for option: {self.name}`) + + return { + type = interactionTypes.InteractionType[self.type], + channel_types = channelTypeArray, + + name = self.name, + name_localizations = self.nameLocalizations, + description = self.description, + description_localizations = self.descriptionLocalizations, + required = self.required, + choices = self.choices, + options = self.options, + min_value = self.minValue, + max_value = self.maxValue, + min_length = self.minLength, + max_length = self.maxLength, + autocomplete = self.autocomplete, + } +end + +--[[ + Constructor for the Discord Default Reaction Builder. + + ```lua + local defaultReaction = Option.new("000000000000000000", "secret-emoji"):build() + ``` +]] +function Option.Interface.new(resource: { + type: interactionTypes.ApplicationCommandOptionType?, + name: string?, + nameLocalizations: { [apiTypes.LanguageLocales]: string }?, + description: string?, + descriptionLocalizations: { [apiTypes.LanguageLocales]: string }?, + required: boolean?, + choices: { apiTypes.ApplicationCommandOptionChoiceObject }?, + options: { JSON }?, + channelTypes: { apiTypes.ChannelType }?, + minValue: number?, + maxValue: number?, + minLength: number?, + maxLength: number?, + autocomplete: boolean?, +}?): Option + local self = setmetatable( + { + type = resource and resource.type, + name = resource and resource.name, + nameLocalizations = resource and resource.nameLocalizations or {}, + description = resource and resource.description, + descriptionLocalizations = resource and resource.descriptionLocalizations or {}, + required = resource and resource.required, + choices = resource and resource.choices or {}, + options = resource and resource.options or {}, + channelTypes = resource and resource.channelTypes or {}, + minValue = resource and resource.minValue, + maxValue = resource and resource.maxValue, + minLength = resource and resource.minLength, + maxLength = resource and resource.maxLength, + autocomplete = resource and resource.autocomplete, + } :: Option, + { __index = Option.Prototype } + ) + + return self +end + +export type Option = typeof(Option.Prototype) & { + type: interactionTypes.ApplicationCommandOptionType?, + name: string?, + nameLocalizations: { [apiTypes.LanguageLocales]: string }, + description: string?, + descriptionLocalizations: { [apiTypes.LanguageLocales]: string }, + required: boolean?, + choices: { interactionChoice.JSON }, + options: { JSON }, + channelTypes: { channelTypes.ChannelType }, + minValue: number?, + maxValue: number?, + minLength: number?, + maxLength: number?, + autocomplete: boolean?, +} + +export type JSON = typeof(Option.Prototype.build(nil :: any)) + +return Option.Interface diff --git a/packages/builders/src/message/allowedMention.luau b/packages/builders/src/message/allowedMention.luau new file mode 100644 index 0000000..9c75d8d --- /dev/null +++ b/packages/builders/src/message/allowedMention.luau @@ -0,0 +1,138 @@ +--[[ + Implementation of discords AllowedMention object as a Luau builder. + + https://discord.com/developers/docs/resources/message#allowed-mentions-object +]] + +local apiTypes = require("@api-types/apiTypes") + +local AllowedMention = {} + +AllowedMention.Prototype = {} +AllowedMention.Interface = {} + +--[[ + Add a user mention to the allowed mentions for this message. +]] +function AllowedMention.Prototype.addUserMention(self: AllowedMention, userId: string): AllowedMention + if not table.find(self.parse, "users") then + table.insert(self.parse, "users") + end + + table.insert(self.users, userId) + + return self +end + +--[[ + Add a role mention to the allowed mentions for this message. +]] +function AllowedMention.Prototype.addRoleMention(self: AllowedMention, roleId: string): AllowedMention + if not table.find(self.parse, "roles") then + table.insert(self.parse, "roles") + end + + table.insert(self.roles, roleId) + + return self +end + +--[[ + For replies, whether to mention the author of the message being replied to (default false) +]] +function AllowedMention.Prototype.setRepliedUser(self: AllowedMention, repliedUser: boolean): AllowedMention + self.repliedUser = repliedUser + + return self +end + +--[[ + Controls @everyone and @here mentions +]] +function AllowedMention.Prototype.setMentionsEveryone(self: AllowedMention, mentionsEveryone: boolean): AllowedMention + if mentionsEveryone then + if not table.find(self.parse, "everyone") then + table.insert(self.parse, "everyone") + end + else + local index = table.find(self.parse, "everyone") + + if index then + table.remove(self.parse, index) + end + end + + return self +end + +--[[ + Responsible for building the AllowedMention JSON that can be parsed by the Discord API. +]] +function AllowedMention.Prototype.build(self: AllowedMention): JSON + return { + parse = self.parse, + roles = self.roles, + users = self.users, + replied_user = self.repliedUser, + } +end + +--[[ + Responsible for creating a new AllowedMention. + + ```lua + + ``` +]] +function AllowedMention.Interface.new(resource: { + roles: { apiTypes.Snowflake }, + users: { apiTypes.Snowflake }, + repliedUser: boolean?, + mentionsEveryone: boolean?, +}?): AllowedMention + local self = setmetatable( + { + parse = {}, + roles = {}, + users = {}, + } :: AllowedMention, + { + __index = AllowedMention.Prototype, + } + ) + + if resource then + if resource.roles then + for _, roleId in resource.roles do + self:addRoleMention(roleId) + end + end + + if resource.users then + for _, userId in resource.users do + self:addUserMention(userId) + end + end + + if resource.repliedUser then + self:setRepliedUser(resource.repliedUser) + end + + if resource.mentionsEveryone then + self:setMentionsEveryone(resource.mentionsEveryone) + end + end + + return self +end + +export type AllowedMention = typeof(AllowedMention.Prototype) & { + parse: { apiTypes.AllowedMentionTypes }, + roles: { apiTypes.Snowflake }, + users: { apiTypes.Snowflake }, + repliedUser: boolean?, +} + +export type JSON = typeof(AllowedMention.Prototype.build(nil :: any)) + +return AllowedMention.Interface diff --git a/packages/builders/src/message/components/actionRow.luau b/packages/builders/src/message/components/actionRow.luau new file mode 100644 index 0000000..3a80875 --- /dev/null +++ b/packages/builders/src/message/components/actionRow.luau @@ -0,0 +1,74 @@ +--[[ + Implementation of discords ActionRow object as a Luau builder. + + https://discord.com/developers/docs/interactions/message-components#action-rows +]] + +local button = require("@builders/message/components/button") +local textInput = require("@builders/message/components/textInput") +local selectMenu = require("@builders/message/components/selectMenu/selectMenu") + +local ActionRow = {} + +ActionRow.Prototype = {} +ActionRow.Interface = {} + +--[[ + +]] +function ActionRow.Prototype.addComponent( + self: ActionRow, + component: button.JSON | textInput.JSON | selectMenu.JSON | JSON +): ActionRow + table.insert(self.components, component) + + return self +end + +--[[ + Responsible for building the ActionRow JSON that can be parsed by the Discord API. +]] +function ActionRow.Prototype.build(self: ActionRow): JSON + return { + type = 1, + components = self.components, + } +end + +--[[ + Responsible for creating a new ActionRow. + + ```lua + + ``` +]] +function ActionRow.Interface.new(resource: { + components: { button.JSON | textInput.JSON | selectMenu.JSON | JSON }, +}?): ActionRow + local self = setmetatable( + { + components = {}, + } :: ActionRow, + { + __index = ActionRow.Prototype, + } + ) + + if resource then + if resource.components then + for _, component in resource.components do + self:addComponent(component) + end + end + end + + return self +end + +export type ActionRow = typeof(ActionRow.Prototype) & { + components: { button.JSON | textInput.JSON | selectMenu.JSON | JSON }, +} + +export type JSON = typeof(ActionRow.Prototype.build(nil :: any)) + +return ActionRow.Interface diff --git a/packages/builders/src/message/components/button.luau b/packages/builders/src/message/components/button.luau new file mode 100644 index 0000000..41f4b4b --- /dev/null +++ b/packages/builders/src/message/components/button.luau @@ -0,0 +1,163 @@ +--[[ + Implementation of discords Button object as a Luau builder. + + https://discord.com/developers/docs/interactions/message-components#buttons +]] + +local messageTypes = require("@api-types/message") + +local emoji = require("@builders/emoji") + +local Button = {} + +Button.Prototype = {} +Button.Interface = {} + +--[[ + +]] +function Button.Prototype.setStyle(self: Button, buttonStyle: messageTypes.ButtonStyle): Button + self.style = buttonStyle + + return self +end + +--[[ + +]] +function Button.Prototype.setLabel(self: Button, label: string): Button + assert(#label <= 80, `Label must be less than 80 characters.`) + assert(#label > 0, `Label must be more than 0 characters.`) + + self.label = label + + return self +end + +--[[ + +]] +function Button.Prototype.setEmoji(self: Button, emoji: emoji.JSON): Button + self.emoji = emoji + + return self +end + +--[[ + +]] +function Button.Prototype.setCustomId(self: Button, customId: string): Button + self.customId = customId + + return self +end + +--[[ + +]] +function Button.Prototype.setSkuId(self: Button, skuId: string): Button + self.skuId = skuId + + return self +end + +--[[ + +]] +function Button.Prototype.setUrl(self: Button, url: string): Button + self.url = url + + return self +end + +--[[ + +]] +function Button.Prototype.setDisabled(self: Button, disabled: boolean): Button + self.disabled = disabled + + return self +end + +--[[ + Responsible for building the Button JSON that can be parsed by the Discord API. +]] +function Button.Prototype.build(self: Button): JSON + return { + type = 2, + style = messageTypes.ButtonStyle[self.style], + label = self.label, + emoji = self.emoji, + custom_id = self.customId, + sku_id = self.skuId, + url = self.url, + disabled = self.disabled, + } +end + +--[[ + Responsible for creating a new Button. + + ```lua + + ``` +]] +function Button.Interface.new(resource: { + style: messageTypes.ButtonStyle, + label: string?, + emoji: emoji.JSON?, + customId: string?, + skuId: string?, + url: string?, + disabled: boolean?, +}?): Button + local self = setmetatable({} :: Button, { + __index = Button.Prototype, + }) + + if resource then + if resource.style then + self:setStyle(resource.style) + end + + if resource.label then + self:setLabel(resource.label) + end + + if resource.emoji then + self:setEmoji(resource.emoji) + end + + if resource.customId then + self:setCustomId(resource.customId) + end + + if resource.skuId then + self:setSkuId(resource.skuId) + end + + if resource.url then + self:setUrl(resource.url) + end + + if resource.disabled ~= nil then + self:setDisabled(resource.disabled) + end + end + + return self +end + +export type Button = typeof(Button.Prototype) & { + style: messageTypes.ButtonStyle, + label: string?, + emoji: emoji.JSON?, + customId: string?, + skuId: string?, + url: string?, + disabled: boolean?, +} + +export type JSON = typeof(Button.Prototype.build(nil :: any)) + +return Button.Interface diff --git a/packages/builders/src/message/components/selectMenu/defaultValue.luau b/packages/builders/src/message/components/selectMenu/defaultValue.luau new file mode 100644 index 0000000..4c158fc --- /dev/null +++ b/packages/builders/src/message/components/selectMenu/defaultValue.luau @@ -0,0 +1,77 @@ +--[[ + Implementation of discords DefaultValue object as a Luau builder. + + https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-default-value-structure +]] + +local messageTypes = require("@api-types/message") + +local DefaultValue = {} + +DefaultValue.Prototype = {} +DefaultValue.Interface = {} + +--[[ + +]] +function DefaultValue.Prototype.setId(self: DefaultValue, id: string): DefaultValue + self.id = id + + return self +end + +--[[ + +]] +function DefaultValue.Prototype.setType(self: DefaultValue, type: messageTypes.SelectDefaultValueType): DefaultValue + self.type = type + + return self +end + +--[[ + Responsible for building the DefaultValue JSON that can be parsed by the Discord API. +]] +function DefaultValue.Prototype.build(self: DefaultValue): JSON + return { + id = self.id, + type = self.type, + } +end + +--[[ + Responsible for creating a new DefaultValue. + + ```lua + + ``` +]] +function DefaultValue.Interface.new(resource: { + id: string, + type: messageTypes.SelectDefaultValueType, +}?): DefaultValue + local self = setmetatable({} :: DefaultValue, { + __index = DefaultValue.Prototype, + }) + + if resource then + if resource.id then + self:setId(resource.id) + end + + if resource.type then + self:setType(resource.type) + end + end + + return self +end + +export type DefaultValue = typeof(DefaultValue.Prototype) & { + id: string, + type: messageTypes.SelectDefaultValueType, +} + +export type JSON = typeof(DefaultValue.Prototype.build(nil :: any)) + +return DefaultValue.Interface diff --git a/packages/builders/src/message/components/selectMenu/option.luau b/packages/builders/src/message/components/selectMenu/option.luau new file mode 100644 index 0000000..104c4ae --- /dev/null +++ b/packages/builders/src/message/components/selectMenu/option.luau @@ -0,0 +1,134 @@ +--[[ + Implementation of discords SelectMenu object as a Luau builder. + + https://discord.com/developers/docs/interactions/message-components#SelectMenus +]] + +local emoji = require("@builders/emoji") + +local SelectMenu = {} + +SelectMenu.Prototype = {} +SelectMenu.Interface = {} + +--[[ + +]] +function SelectMenu.Prototype.setLabel(self: SelectMenu, label: string): SelectMenu + assert(#label <= 100, `Label must be less than 80 characters.`) + assert(#label > 0, `Label must be more than 0 characters.`) + + self.label = label + + return self +end + +--[[ + +]] +function SelectMenu.Prototype.setValue(self: SelectMenu, value: string): SelectMenu + assert(#value <= 100, `Value must be less than 80 characters.`) + assert(#value > 0, `Value must be more than 0 characters.`) + + self.value = value + + return self +end + +--[[ + +]] +function SelectMenu.Prototype.setDescription(self: SelectMenu, description: string): SelectMenu + assert(#description <= 100, `Description( must be less than 80 characters.`) + assert(#description > 0, `Description( must be more than 0 characters.`) + + self.description = description + + return self +end + +--[[ + +]] +function SelectMenu.Prototype.setEmoji(self: SelectMenu, emoji: emoji.JSON): SelectMenu + self.emoji = emoji + + return self +end + +--[[ + +]] +function SelectMenu.Prototype.setDefault(self: SelectMenu, default: boolean): SelectMenu + self.default = default + + return self +end + +--[[ + Responsible for building the SelectMenu JSON that can be parsed by the Discord API. +]] +function SelectMenu.Prototype.build(self: SelectMenu): JSON + return { + label = self.label, + value = self.value, + description = self.description, + emoji = self.emoji, + default = self.default, + } +end + +--[[ + Responsible for creating a new SelectMenu. + + ```lua + + ``` +]] +function SelectMenu.Interface.new(resource: { + label: string?, + value: string?, + description: string?, + emoji: emoji.JSON?, + default: boolean?, +}): SelectMenu + local self = setmetatable({} :: SelectMenu, { + __index = SelectMenu.Prototype, + }) + + if resource then + if resource.label then + self:setLabel(resource.label) + end + + if resource.value then + self:setValue(resource.value) + end + + if resource.description then + self:setDescription(resource.description) + end + + if resource.emoji then + self:setEmoji(resource.emoji) + end + + if resource.default ~= nil then + self:setDefault(resource.default) + end + end + + return self +end + +export type SelectMenu = typeof(SelectMenu.Prototype) & { + label: string?, + value: string?, + description: string?, + emoji: emoji.JSON?, + default: boolean?, +} + +export type JSON = typeof(SelectMenu.Prototype.build(nil :: any)) + +return SelectMenu.Interface diff --git a/packages/builders/src/message/components/selectMenu/selectMenu.luau b/packages/builders/src/message/components/selectMenu/selectMenu.luau new file mode 100644 index 0000000..408ef6e --- /dev/null +++ b/packages/builders/src/message/components/selectMenu/selectMenu.luau @@ -0,0 +1,189 @@ +--[[ + Implementation of discords SelectMenu object as a Luau builder. + + https://discord.com/developers/docs/interactions/message-components#SelectMenus +]] + +local channelTypes = require("@api-types/channel") + +local defaultValue = require("@builders/message/components/selectMenu/defaultValue") +local option = require("@builders/message/components/selectMenu/option") + +local SelectMenu = {} + +SelectMenu.Prototype = {} +SelectMenu.Interface = {} + +--[[ + +]] +function SelectMenu.Prototype.setCustomId(self: SelectMenu, customId: string): SelectMenu + self.customId = customId + + return self +end + +--[[ + +]] +function SelectMenu.Prototype.setPlaceholder(self: SelectMenu, placeholder: string): SelectMenu + self.placeholder = placeholder + + return self +end + +--[[ + +]] +function SelectMenu.Prototype.setMinValues(self: SelectMenu, minValueCount: number): SelectMenu + self.minValues = minValueCount + + return self +end + +--[[ + +]] +function SelectMenu.Prototype.setMaxValues(self: SelectMenu, maxValueCount: number): SelectMenu + self.maxValues = maxValueCount + + return self +end + +--[[ + +]] +function SelectMenu.Prototype.setDisabled(self: SelectMenu, disabled: boolean): SelectMenu + self.disabled = disabled + + return self +end + +--[[ + +]] +function SelectMenu.Prototype.addDefaultValue(self: SelectMenu, defaultValue: defaultValue.JSON): SelectMenu + table.insert(self.defaultValues, defaultValue) + + return self +end + +--[[ + +]] +function SelectMenu.Prototype.addChannelType(self: SelectMenu, channelType: channelTypes.ChannelType): SelectMenu + table.insert(self.channelTypes, channelType) + + return self +end + +--[[ + +]] +function SelectMenu.Prototype.addOption(self: SelectMenu, option: option.JSON): SelectMenu + table.insert(self.options, option) + + return self +end + +--[[ + Responsible for building the SelectMenu JSON that can be parsed by the Discord API. +]] +function SelectMenu.Prototype.build(self: SelectMenu): JSON + return { + custom_id = self.customId, + options = self.options, + channel_types = self.channelTypes, + placeholder = self.placeholder, + default_values = self.defaultValues, + min_values = self.minValues, + max_values = self.maxValues, + disabled = self.disabled, + } +end + +--[[ + Responsible for creating a new SelectMenu. + + ```lua + + ``` +]] +function SelectMenu.Interface.new(resource: { + customId: string?, + options: { option.JSON }, + channelTypes: { channelTypes.ChannelType }, + placeholder: string?, + defaultValues: { defaultValue.JSON }, + minValues: number?, + maxValues: number?, + disabled: boolean?, +}?): SelectMenu + local self = setmetatable( + { + defaultValues = {}, + options = {}, + channelTypes = {}, + } :: SelectMenu, + { + __index = SelectMenu.Prototype, + } + ) + + if resource then + if resource.customId then + self:setCustomId(resource.customId) + end + + if resource.placeholder then + self:setPlaceholder(resource.placeholder) + end + + if resource.minValues then + self:setMinValues(resource.minValues) + end + + if resource.maxValues then + self:setMaxValues(resource.maxValues) + end + + if resource.disabled ~= nil then + self:setDisabled(resource.disabled) + end + + if resource.options then + for _, option in resource.options do + self:addOption(option) + end + end + + if resource.channelTypes then + for _, channelType: channelTypes.ChannelType in resource.channelTypes do + self:addChannelType(channelType) + end + end + + if resource.defaultValues then + for _, defaultValue in resource.defaultValues do + self:addDefaultValue(defaultValue) + end + end + end + + return self +end + +export type SelectMenu = typeof(SelectMenu.Prototype) & { + customId: string?, + options: { option.JSON }, + channelTypes: { channelTypes.ChannelType }, + placeholder: string?, + defaultValues: { defaultValue.JSON }, + minValues: number?, + maxValues: number?, + disabled: boolean?, +} + +export type JSON = typeof(SelectMenu.Prototype.build(nil :: any)) + +return SelectMenu.Interface diff --git a/packages/builders/src/message/components/textInput.luau b/packages/builders/src/message/components/textInput.luau new file mode 100644 index 0000000..6acd10d --- /dev/null +++ b/packages/builders/src/message/components/textInput.luau @@ -0,0 +1,174 @@ +--[[ + Implementation of discords TextInput object as a Luau builder. + + https://discord.com/developers/docs/interactions/message-components#text-input-object-text-input-structure +]] + +local messageTypes = require("@api-types/message") + +local TextInput = {} + +TextInput.Prototype = {} +TextInput.Interface = {} + +--[[ + +]] +function TextInput.Prototype.setCustomId(self: TextInput, customId: string): TextInput + self.customId = customId + + return self +end + +--[[ + +]] +function TextInput.Prototype.setStyle(self: TextInput, style: messageTypes.TextInputStyle): TextInput + self.style = style + + return self +end + +--[[ + +]] +function TextInput.Prototype.setLabel(self: TextInput, label: string): TextInput + self.label = label + + return self +end + +--[[ + +]] +function TextInput.Prototype.setMinLength(self: TextInput, minLength: number): TextInput + self.minLength = minLength + + return self +end + +--[[ + +]] +function TextInput.Prototype.setMaxLength(self: TextInput, maxLength: number): TextInput + self.maxLength = maxLength + + return self +end + +--[[ + +]] +function TextInput.Prototype.setIsRequired(self: TextInput, isRequired: boolean): TextInput + self.required = isRequired + + return self +end + +--[[ + +]] +function TextInput.Prototype.setValue(self: TextInput, value: string): TextInput + self.value = value + + return self +end + +--[[ + +]] +function TextInput.Prototype.setPlaceholder(self: TextInput, placeholder: string): TextInput + self.placeholder = placeholder + + return self +end + +--[[ + Responsible for building the TextInput JSON that can be parsed by the Discord API. +]] +function TextInput.Prototype.build(self: TextInput): JSON + return { + type = 4, + custom_id = self.customId, + style = messageTypes.MessageType[self.style], + label = self.label, + min_length = self.minLength, + max_length = self.maxLength, + required = self.required, + value = self.value, + placeholder = self.placeholder, + } +end + +--[[ + Responsible for creating a new TextInput. + + ```lua + + ``` +]] +function TextInput.Interface.new(resource: { + customId: string?, + style: messageTypes.TextInputStyle?, + label: string?, + minLength: number?, + maxLength: number?, + required: boolean?, + value: string?, + placeholder: string?, +}?): TextInput + local self = setmetatable({} :: TextInput, { + __index = TextInput.Prototype, + }) + + if resource then + if resource.customId then + self:setCustomId(resource.customId) + end + + if resource.style then + self:setStyle(resource.style) + end + + if resource.label then + self:setLabel(resource.label) + end + + if resource.minLength then + self:setMinLength(resource.minLength) + end + + if resource.maxLength then + self:setMaxLength(resource.maxLength) + end + + if resource.required ~= nil then + self:setIsRequired(resource.required) + end + + if resource.value then + self:setValue(resource.value) + end + + if resource.placeholder then + self:setPlaceholder(resource.placeholder) + end + end + + return self +end + +export type TextInput = typeof(TextInput.Prototype) & { + customId: string?, + style: messageTypes.TextInputStyle?, + label: string?, + minLength: number?, + maxLength: number?, + required: boolean?, + value: string?, + placeholder: string?, +} + +export type JSON = typeof(TextInput.Prototype.build(nil :: any)) + +return TextInput.Interface diff --git a/packages/builders/src/message/message.luau b/packages/builders/src/message/message.luau new file mode 100644 index 0000000..8364831 --- /dev/null +++ b/packages/builders/src/message/message.luau @@ -0,0 +1,275 @@ +--[[ + Implementation of discords Message object as a Luau builder. + + https://discord.com/developers/docs/resources/message#message-object +]] + +local embed = require("@builders/embed/embed") +local allowedMention = require("@builders/message/allowedMention") +local reference = require("@builders/message/reference") + +local poll = require("@builders/message/poll/poll") + +local button = require("@builders/message/components/button") +local textInput = require("@builders/message/components/textInput") +local selectMenu = require("@builders/message/components/selectMenu/selectMenu") +local actionRow = require("@builders/message/components/actionRow") + +local attachment = require("@builders/attachment") + +local Message = {} + +Message.Prototype = {} +Message.Interface = {} + +--[[ + Sets the content of the message. +]] +function Message.Prototype.setContent(self: Message, content: string): Message + assert(#content < 2000, `Message content must be less than 2000 characters.`) + + self.content = content + + return self +end + +--[[ + Sets the nonce of the message. +]] +function Message.Prototype.setNonce(self: Message, nonce: string): Message + assert(#nonce < 25, `Message nonse must be less than 25 characters.`) + + self.nonce = nonce + + return self +end + +--[[ + Sets whether the message is text-to-speech. +]] +function Message.Prototype.setTTS(self: Message, tts: boolean): Message + self.tts = tts + + return self +end + +--[[ + Adds an embed to the message. +]] +function Message.Prototype.addEmbed(self: Message, embedData: embed.JSON): Message + table.insert(self.embeds, embedData) + + return self +end + +--[[ + Sets the allowed mentions for the message. +]] +function Message.Prototype.setAllowedMentions(self: Message, mentions: allowedMention.JSON): Message + self.allowedMentions = mentions + + return self +end + +--[[ + Sets the message reference. +]] +function Message.Prototype.setMessageReference(self: Message, ref: reference.JSON): Message + self.messageReference = ref + + return self +end + +--[[ + Adds a component to the message. +]] +function Message.Prototype.addComponent( + self: Message, + component: button.JSON | textInput.JSON | selectMenu.JSON | actionRow.JSON +): Message + table.insert(self.components, component) + + return self +end + +--[[ + Adds a sticker ID to the message. +]] +function Message.Prototype.addStickerId(self: Message, stickerId: string): Message + table.insert(self.stickerIds, stickerId) + + return self +end + +--[[ + Adds an attachment to the message. +]] +function Message.Prototype.addAttachment(self: Message, attachmentData: attachment.JSON): Message + table.insert(self.attachments, attachmentData) + + return self +end + +--[[ + Sets the flags for the message. +]] +function Message.Prototype.setFlags(self: Message, flags: number): Message + self.flags = flags + + return self +end + +--[[ + If true and nonce is present, it will be checked for uniqueness in the past few minutes. If another message + was created by the same author with the same nonce, that message will be returned and no new message will + be created. +]] +function Message.Prototype.setEnforceNonce(self: Message, enforceNonce: boolean): Message + self.enforceNonce = enforceNonce + + return self +end + +--[[ + Sets the poll for the message. +]] +function Message.Prototype.setPoll(self: Message, poll: poll.JSON): Message + self.poll = poll + + return self +end + +--[[ + Responsible for building the Message JSON that can be parsed by the Discord API. +]] +function Message.Prototype.build(self: Message): JSON + return { + content = self.content, + nonce = self.nonce, + tts = self.tts, + embeds = self.embeds, + allowed_mentions = self.allowedMentions, + message_reference = self.messageReference, + components = self.components, + sticker_ids = self.stickerIds, + attachments = self.attachments, + flags = self.flags, + enforce_nonce = self.enforceNonce, + poll = self.poll, + } +end + +--[[ + Responsible for creating a new Message. + + ```lua + + ``` +]] +function Message.Interface.new(resource: { + content: string?, + nonce: string?, + tts: boolean?, + embeds: { embed.JSON }?, + allowedMentions: allowedMention.JSON?, + messageReference: reference.JSON?, + components: { button.JSON | textInput.JSON | selectMenu.JSON | actionRow.JSON }?, + stickerIds: { string }?, + attachments: { attachment.JSON }?, + flags: number?, + enforceNonce: boolean?, + poll: poll.JSON?, +}?): Message + local self = setmetatable( + { + embeds = {}, + components = {}, + stickerIds = {}, + attachments = {}, + } :: Message, + { + __index = Message.Prototype, + } + ) + + if resource then + if resource.content then + self:setContent(resource.content) + end + + if resource.nonce then + self:setNonce(resource.nonce) + end + + if resource.tts then + self:setTTS(resource.tts) + end + + if resource.allowedMentions then + self:setAllowedMentions(resource.allowedMentions) + end + + if resource.messageReference then + self:setMessageReference(resource.messageReference) + end + + if resource.flags then + self:setFlags(resource.flags) + end + + if resource.enforceNonce then + self:setEnforceNonce(resource.enforceNonce) + end + + if resource.poll then + self:setPoll(resource.poll) + end + + if resource.embeds then + for _, embed in resource.embeds do + self:addEmbed(embed) + end + end + + if resource.components then + for _, component in resource.components do + self:addComponent(component) + end + end + + if resource.stickerIds then + for _, stickerId in resource.stickerIds do + self:addStickerId(stickerId) + end + end + + if resource.attachments then + for _, attachment in resource.attachments do + self:addAttachment(attachment) + end + end + end + + return self +end + +export type Message = typeof(Message.Prototype) & { + content: string?, + nonce: string?, + tts: boolean?, + embeds: { embed.JSON }, + allowedMentions: allowedMention.JSON, + messageReference: reference.JSON, + components: { button.JSON | textInput.JSON | selectMenu.JSON | actionRow.JSON }, + stickerIds: { string }, + -- files[n] + -- payloadJson + attachments: { attachment.JSON }, + flags: number, + enforceNonce: boolean, + poll: poll.JSON?, +} + +export type JSON = typeof(Message.Prototype.build(nil :: any)) + +return Message.Interface diff --git a/packages/builders/src/message/poll/answer.luau b/packages/builders/src/message/poll/answer.luau new file mode 100644 index 0000000..081a560 --- /dev/null +++ b/packages/builders/src/message/poll/answer.luau @@ -0,0 +1,77 @@ +--[[ + Implementation of discords PollAnswer object as a Luau builder. + + https://discord.com/developers/docs/resources/poll#poll-answer-object-poll-answer-object-structure +]] + +local mediaBuilder = require("@builders/message/poll/media") + +local PollAnswer = {} + +PollAnswer.Prototype = {} +PollAnswer.Interface = {} + +--[[ + Sets the ID for the poll answer. +]] +function PollAnswer.Prototype.setId(self: PollAnswer, id: number): PollAnswer + self.answerId = id + + return self +end + +--[[ + Sets the media for the poll answer. +]] +function PollAnswer.Prototype.setMedia(self: PollAnswer, media: mediaBuilder.JSON): PollAnswer + self.pollMedia = media + + return self +end + +--[[ + Responsible for building the PollAnswer JSON that can be parsed by the Discord API. +]] +function PollAnswer.Prototype.build(self: PollAnswer): JSON + return { + answer_id = self.answerId, + poll_media = self.pollMedia, + } +end + +--[[ + Responsible for creating a new PollAnswer. + + ```lua + + ``` +]] +function PollAnswer.Interface.new(resource: { + answerId: number?, + pollMedia: mediaBuilder.JSON?, +}?): PollAnswer + local self = setmetatable({} :: PollAnswer, { + __index = PollAnswer.Prototype, + }) + + if resource then + if resource.answerId then + self:setId(resource.answerId) + end + + if resource.pollMedia then + self:setMedia(resource.pollMedia) + end + end + + return self +end + +export type PollAnswer = typeof(PollAnswer.Prototype) & { + answerId: number?, + pollMedia: mediaBuilder.JSON?, +} + +export type JSON = typeof(PollAnswer.Prototype.build(nil :: any)) + +return PollAnswer.Interface diff --git a/packages/builders/src/message/poll/media.luau b/packages/builders/src/message/poll/media.luau new file mode 100644 index 0000000..faf3355 --- /dev/null +++ b/packages/builders/src/message/poll/media.luau @@ -0,0 +1,77 @@ +--[[ + Implementation of discords PollMedia object as a Luau builder. + + https://discord.com/developers/docs/resources/poll#poll-media-object-poll-media-object-structure +]] + +local emojiBuilder = require("@builders/emoji") + +local PollMedia = {} + +PollMedia.Prototype = {} +PollMedia.Interface = {} + +--[[ + Sets the text for the PollMedia. +]] +function PollMedia.Prototype.setText(self: PollMedia, text: string): PollMedia + self.text = text + + return self +end + +--[[ + Responsible for setting the emoji for the PollMedia. +]] +function PollMedia.Prototype.setEmoji(self: PollMedia, emoji: emojiBuilder.JSON): PollMedia + self.emoji = emoji + + return self +end + +--[[ + Responsible for building the PollMedia JSON that can be parsed by the Discord API. +]] +function PollMedia.Prototype.build(self: PollMedia): JSON + return { + text = self.text, + emoji = self.emoji, + } +end + +--[[ + Responsible for creating a new PollMedia. + + ```lua + + ``` +]] +function PollMedia.Interface.new(resource: { + text: string?, + emoji: emojiBuilder.JSON?, +}?): PollMedia + local self = setmetatable({} :: PollMedia, { + __index = PollMedia.Prototype, + }) + + if resource then + if resource.text then + self:setText(resource.text) + end + + if resource.emoji then + self:setEmoji(resource.emoji) + end + end + + return self +end + +export type PollMedia = typeof(PollMedia.Prototype) & { + text: string?, + emoji: emojiBuilder.JSON?, +} + +export type JSON = typeof(PollMedia.Prototype.build(nil :: any)) + +return PollMedia.Interface diff --git a/packages/builders/src/message/poll/poll.luau b/packages/builders/src/message/poll/poll.luau new file mode 100644 index 0000000..9f9b1e1 --- /dev/null +++ b/packages/builders/src/message/poll/poll.luau @@ -0,0 +1,143 @@ +--[[ + Implementation of discords Poll object as a Luau builder. + + https://discord.com/developers/docs/resources/poll#poll-object +]] + +local datetime = require("@std-polyfills/datetime") + +local messageTypes = require("@api-types/message") + +local mediaBuilder = require("@builders/message/poll/media") +local answerBuilder = require("@builders/message/poll/answer") + +local Poll = {} + +Poll.Prototype = {} +Poll.Interface = {} + +--[[ + Adds an answer to the poll. +]] +function Poll.Prototype.addAnswer(self: Poll, answer: answerBuilder.JSON): Poll + table.insert(self.answers, answer) + + return self +end + +--[[ + Sets the question for the poll. +]] +function Poll.Prototype.setQuestion(self: Poll, question: mediaBuilder.JSON): Poll + self.question = question + + return self +end + +--[[ + Sets the expiry time for the poll. +]] +function Poll.Prototype.setExpiry(self: Poll, expiry: number | datetime.DateTime): Poll + if typeof(expiry) == "DateTime" then + local object = expiry :: datetime.DateTime + + self.expiry = object.unixTimestampMillis + else + self.expiry = expiry :: number + end + + return self +end + +--[[ + Sets whether the poll allows multiple selections. +]] +function Poll.Prototype.setMultiselect(self: Poll, multiselect: boolean): Poll + self.allowMultiselect = multiselect + + return self +end + +--[[ + Sets the layout type for the poll. +]] +function Poll.Prototype.setLayoutType(self: Poll, layoutType: messageTypes.PollLayoutType): Poll + self.layoutType = layoutType + + return self +end + +--[[ + Builds and returns the Poll JSON that can be parsed by the Discord API. +]] +function Poll.Prototype.build(self: Poll): JSON + return { + question = self.question, + answers = self.answers, + expiry = self.expiry, + allow_multiselect = self.allowMultiselect, + layout_type = messageTypes.PollLayoutType[self.layoutType], + } +end + +--[[ + Responsible for creating a new Poll. + + ```lua + + ``` +]] +function Poll.Interface.new(resource: { + question: mediaBuilder.JSON?, + answers: { answerBuilder.JSON }?, + expiry: number?, + allowMultiselect: boolean?, + layoutType: messageTypes.PollLayoutType?, +}?): Poll + local self = setmetatable( + { + answers = {}, + } :: Poll, + { + __index = Poll.Prototype, + } + ) + + if resource then + if resource.question then + self:setQuestion(resource.question) + end + + if resource.expiry then + self:setExpiry(resource.expiry) + end + + if resource.allowMultiselect then + self:setMultiselect(resource.allowMultiselect) + end + + if resource.layoutType then + self:setLayoutType(resource.layoutType) + end + + if resource.answers then + for _, answer in resource.answers do + self:addAnswer(answer) + end + end + end + + return self +end + +export type Poll = typeof(Poll.Prototype) & { + question: mediaBuilder.JSON, + answers: { answerBuilder.JSON }, + expiry: number, + allowMultiselect: boolean, + layoutType: messageTypes.PollLayoutType, +} + +export type JSON = typeof(Poll.Prototype.build(nil :: any)) + +return Poll.Interface diff --git a/packages/builders/src/message/reference.luau b/packages/builders/src/message/reference.luau new file mode 100644 index 0000000..e3b1634 --- /dev/null +++ b/packages/builders/src/message/reference.luau @@ -0,0 +1,126 @@ +--[[ + Implementation of discords Reference object as a Luau builder. + + https://discord.com/developers/docs/resources/message#message-reference-structure +]] + +local apiTypes = require("@api-types/apiTypes") +local messageTypes = require("@api-types/message") + +local Reference = {} + +Reference.Prototype = {} +Reference.Interface = {} + +--[[ + type of reference. +]] +function Reference.Prototype.setType(self: Reference, type: messageTypes.MessageReferenceType): Reference + self.type = type + + return self +end + +--[[ + id of the originating message +]] +function Reference.Prototype.setMessageId(self: Reference, messageId: string): Reference + self.messageId = messageId + + return self +end + +--[[ + id of the originating message's channel +]] +function Reference.Prototype.setChannelId(self: Reference, channelId: string): Reference + self.channelId = channelId + + return self +end + +--[[ + id of the originating message's guild +]] +function Reference.Prototype.setGuildId(self: Reference, guildId: string): Reference + self.guildId = guildId + + return self +end + +--[[ + when sending, whether to error if the referenced message doesn't exist instead of sending as a normal (non-reply) message, default true +]] +function Reference.Prototype.setFailIfNotExists(self: Reference, failIfNotExists: boolean): Reference + self.failIfNotExists = failIfNotExists + + return self +end + +--[[ + Responsible for building the Reference JSON that can be parsed by the Discord API. +]] +function Reference.Prototype.build(self: Reference): JSON + return { + type = messageTypes.MessageType[self.type], + message_id = self.messageId, + channel_id = self.channelId, + guild_id = self.guildId, + fail_if_not_exists = self.failIfNotExists, + } +end + +--[[ + Responsible for creating a new Reference. + + ```lua + + ``` +]] +function Reference.Interface.new(resource: { + type: messageTypes.MessageReferenceType?, + messageId: string?, + channelId: string?, + guildId: string?, + failIfNotExists: boolean?, +}?): Reference + local self = setmetatable({} :: Reference, { + __index = Reference.Prototype, + }) + + if resource then + if resource.type then + self:setType(resource.type) + end + + if resource.messageId then + self:setMessageId(resource.messageId) + end + + if resource.channelId then + self:setChannelId(resource.channelId) + end + + if resource.guildId then + self:setGuildId(resource.guildId) + end + + if resource.failIfNotExists ~= nil then + self:setFailIfNotExists(resource.failIfNotExists) + end + end + + return self +end + +export type Reference = typeof(Reference.Prototype) & { + type: messageTypes.MessageReferenceType?, + messageId: apiTypes.Snowflake?, + channelId: apiTypes.Snowflake?, + guildId: apiTypes.Snowflake?, + failIfNotExists: boolean?, +} + +export type JSON = typeof(Reference.Prototype.build(nil :: any)) + +return Reference.Interface diff --git a/packages/classes/README.md b/packages/classes/README.md new file mode 100644 index 0000000..5b7a087 --- /dev/null +++ b/packages/classes/README.md @@ -0,0 +1,9 @@ +
+

+ discord-luau +

+
+ +## DiscordLuau - Classes + +This package provides developers with a set of classes that can be used to interact with the Discord API. \ No newline at end of file diff --git a/packages/classes/src/activity/activity.luau b/packages/classes/src/activity/activity.luau new file mode 100644 index 0000000..0fb44eb --- /dev/null +++ b/packages/classes/src/activity/activity.luau @@ -0,0 +1,103 @@ +--[[ + Implementation of the Discord Activity class in Luau + + https://discord.com/developers/docs/topics/gateway-events#activity-object +]] + +local apiTypes = require("@api-types/apiTypes") + +local activityAssets = require("@classes/activity/activityAssets") +local activityButton = require("@classes/activity/activityButton") +local activitySecrets = require("@classes/activity/activitySecrets") +local activityEmoji = require("@classes/activity/activityEmoji") +local activityParty = require("@classes/activity/activityParty") +local activityTimestamp = require("@classes/activity/activityTimestamp") + +local Activity = {} + +Activity.Interface = {} +Activity.Prototype = {} + +function Activity.Prototype.sync(self: Activity, activityData: apiTypes.ActivityObject) + local activityButtons = {} + local activityType: ActivityType = "Playing" + + if activityData.buttons then + for _, button in activityData.buttons do + table.insert(activityButtons, activityButton.new(button.label, button.url)) + end + end + + self.party = activityData.party + and activityParty.new(activityData.party.id, activityData.party.size[1], activityData.party.size[2]) + + self.secrets = activityData.secrets + and activitySecrets.new(activityData.secrets.join, activityData.secrets.spectate, activityData.secrets.match) + + self.timestamps = activityData.timestamps + and activityTimestamp.new(activityData.timestamps.start, activityData.timestamps["end"]) + + self.assets = activityData.assets + and activityAssets.new( + activityData.assets.large_image, + activityData.assets.large_text, + activityData.assets.small_image, + activityData.assets.small_text + ) + + self.buttons = activityButtons + + if activityData.type == 0 then + activityType = "Playing" + elseif activityData.type == 1 then + activityType = "Streaming" + elseif activityData.type == 2 then + activityType = "Listening" + elseif activityData.type == 3 then + activityType = "Watching" + elseif activityData.type == 4 then + activityType = "Custom" + elseif activityData.type == 5 then + activityType = "Competing" + end + + self.name = activityData.name + self.type = activityType + self.url = activityData.url + self.createdAt = activityData.created_at + self.applicationId = activityData.application_id + self.details = activityData.details + self.state = activityData.state + self.emoji = activityData.emoji and activityEmoji.new(activityData.emoji.id, activityData.emoji) + self.instance = activityData.instance + self.flags = activityData.flags +end + +function Activity.Interface.new(data: apiTypes.ActivityObject): Activity + local self = setmetatable({} :: Activity, { __index = Activity.Prototype }) + + self:sync(data) + + return self +end + +export type ActivityType = "Playing" | "Streaming" | "Listening" | "Watching" | "Custom" | "Competing" +export type Activity = typeof(Activity.Prototype) & { + name: string, + type: ActivityType, + url: string?, + createdAt: number, + timestamps: activityTimestamp.ActivityTimestamp, + applicationId: apiTypes.Snowflake, + details: string?, + state: string?, + emoji: activityEmoji.ActivityEmoji?, + party: activityParty.ActivityParty?, + assets: activityAssets.ActivityAssets?, + secrets: activitySecrets.ActivitySecrets?, + instance: boolean?, + flags: number?, + buttons: { activityButton.ActivityButton }, +} + +return Activity.Interface diff --git a/packages/classes/src/activity/activityAssets.luau b/packages/classes/src/activity/activityAssets.luau new file mode 100644 index 0000000..aa22215 --- /dev/null +++ b/packages/classes/src/activity/activityAssets.luau @@ -0,0 +1,38 @@ +--[[ + Implementation of the Discord ActivityAssets class in Luau + + https://discord.com/developers/docs/topics/gateway-events#activity-object-activity-assets +]] + +local ActivityAssets = {} + +ActivityAssets.Interface = {} +ActivityAssets.Prototype = {} + +function ActivityAssets.Interface.new( + largeImage: string, + largeText: string, + smallImage: string, + smallText: string +): ActivityAssets + local self = setmetatable( + { + largeImage = largeImage, + largeText = largeText, + smallImage = smallImage, + smallText = smallText, + } :: ActivityAssets, + { __index = ActivityAssets.Prototype } + ) + + return self +end + +export type ActivityAssets = typeof(ActivityAssets.Prototype) & { + largeImage: string, + largeText: string, + smallImage: string, + smallText: string, +} + +return ActivityAssets.Interface diff --git a/packages/classes/src/activity/activityButton.luau b/packages/classes/src/activity/activityButton.luau new file mode 100644 index 0000000..8f09d3b --- /dev/null +++ b/packages/classes/src/activity/activityButton.luau @@ -0,0 +1,29 @@ +--[[ + Implementation of the Discord ActivityButton class in Luau + + https://discord.com/developers/docs/topics/gateway-events#activity-object-activity-buttons +]] + +local ActivityButton = {} + +ActivityButton.Interface = {} +ActivityButton.Prototype = {} + +function ActivityButton.Interface.new(label: string, url: string): ActivityButton + local self = setmetatable( + { + label = label, + url = url, + } :: ActivityButton, + { __index = ActivityButton.Prototype } + ) + + return self +end + +export type ActivityButton = typeof(ActivityButton.Prototype) & { + label: string, + url: string, +} + +return ActivityButton.Interface diff --git a/packages/classes/src/activity/activityEmoji.luau b/packages/classes/src/activity/activityEmoji.luau new file mode 100644 index 0000000..995fc64 --- /dev/null +++ b/packages/classes/src/activity/activityEmoji.luau @@ -0,0 +1,40 @@ +--[[ + Implementation of the Discord ActivityEmoji class in Luau + + https://discord.com/developers/docs/topics/gateway-events#activity-object-activity-emoji +]] + +local apiTypes = require("@api-types/apiTypes") + +local ActivityEmoji = {} + +ActivityEmoji.Interface = {} +ActivityEmoji.Prototype = {} + +function ActivityEmoji.Prototype.sync(self: ActivityEmoji, emojiData: apiTypes.ActivityEmojiObject) + self.animated = emojiData.animated + self.name = emojiData.name +end + +function ActivityEmoji.Interface.new(id: apiTypes.Snowflake, emojiData: apiTypes.ActivityEmojiObject?): ActivityEmoji + local self = setmetatable( + { + id = id, + } :: ActivityEmoji, + { __index = ActivityEmoji.Prototype } + ) + + if emojiData then + self:sync(emojiData) + end + + return self +end + +export type ActivityEmoji = typeof(ActivityEmoji.Prototype) & { + id: apiTypes.Snowflake, + name: string, + animated: boolean?, +} + +return ActivityEmoji.Interface diff --git a/packages/classes/src/activity/activityParty.luau b/packages/classes/src/activity/activityParty.luau new file mode 100644 index 0000000..2cd289a --- /dev/null +++ b/packages/classes/src/activity/activityParty.luau @@ -0,0 +1,35 @@ +--[[ + Implementation of the Discord ActivityParty class in Luau + + https://discord.com/developers/docs/topics/gateway-events#activity-object-activity-party +]] + +local apiTypes = require("@api-types/apiTypes") + +local ActivityParty = {} + +ActivityParty.Interface = {} +ActivityParty.Prototype = {} + +function ActivityParty.Interface.new(id: apiTypes.Snowflake, currentSize: number, maxSize: number): ActivityParty + local self = setmetatable( + { + id = id, + + currentSize = currentSize, + maxSize = maxSize, + } :: ActivityParty, + { __index = ActivityParty.Prototype } + ) + + return self +end + +export type ActivityParty = typeof(ActivityParty.Prototype) & { + id: apiTypes.Snowflake, + + currentSize: number, + maxSize: number, +} + +return ActivityParty.Interface diff --git a/packages/classes/src/activity/activitySecrets.luau b/packages/classes/src/activity/activitySecrets.luau new file mode 100644 index 0000000..77ba981 --- /dev/null +++ b/packages/classes/src/activity/activitySecrets.luau @@ -0,0 +1,31 @@ +--[[ + Implementation of the Discord ActivitySecrets class in Luau + + https://discord.com/developers/docs/topics/gateway-events#activity-object-activity-secrets +]] + +local ActivitySecrets = {} + +ActivitySecrets.Interface = {} +ActivitySecrets.Prototype = {} + +function ActivitySecrets.Interface.new(join: string?, spectate: string?, match: string?): ActivitySecrets + local self = setmetatable( + { + join = join, + spectate = spectate, + match = match, + } :: ActivitySecrets, + { __index = ActivitySecrets.Prototype } + ) + + return self +end + +export type ActivitySecrets = typeof(ActivitySecrets.Prototype) & { + join: string?, + spectate: string?, + match: string?, +} + +return ActivitySecrets.Interface diff --git a/packages/classes/src/activity/activityTimestamp.luau b/packages/classes/src/activity/activityTimestamp.luau new file mode 100644 index 0000000..df205a4 --- /dev/null +++ b/packages/classes/src/activity/activityTimestamp.luau @@ -0,0 +1,31 @@ +--[[ + Implementation of the Discord ActivityTimestamps class in Luau + + https://discord.com/developers/docs/topics/gateway-events#activity-object-activity-timestamps +]] + +local datetime = require("@std-polyfills/datetime") + +local ActivityTimestamp = {} + +ActivityTimestamp.Interface = {} +ActivityTimestamp.Prototype = {} + +function ActivityTimestamp.Interface.new(startTime: number, endTime: number): ActivityTimestamp + local self = setmetatable( + { + startTime = datetime.fromUnixTimestamp(startTime), + endTime = datetime.fromUnixTimestamp(endTime), + } :: ActivityTimestamp, + { __index = ActivityTimestamp.Prototype } + ) + + return self +end + +export type ActivityTimestamp = typeof(ActivityTimestamp.Prototype) & { + startTime: datetime.DateTime, + endTime: datetime.DateTime, +} + +return ActivityTimestamp.Interface diff --git a/packages/classes/src/application/application.luau b/packages/classes/src/application/application.luau new file mode 100644 index 0000000..cee234c --- /dev/null +++ b/packages/classes/src/application/application.luau @@ -0,0 +1,226 @@ +--[[ + Implementation of the Discord Application class in Luau + + https://discord.com/developers/docs/resources/application#application-object +]] + +local future = require("@vendor/future") + +local apiTypes = require("@api-types/apiTypes") +local applicationTypes = require("@api-types/application") + +local user = require("@classes/user") +local unavailableGuild = require("@classes/guild/unavailableGuild") +local team = require("@classes/application/team/team") +local state = require("@classes/state") +local installParams = require("@classes/application/installParams") + +local commandsRest = require("@rest/commands") +local interactionBuilder = require("@builders/interaction/interaction") + +local commandObject = require("@classes/application/command/command") + +local applicationBitflags = require("@classes/bitflags/application") + +local Application = {} + +Application.Interface = {} +Application.Prototype = {} + +--[[ + Creates a global Slash Command for the Application, slash commands can take up to 5 minutes to sync, and will require + developers to reload their discord client. + + For testing/building commands, it's advised you use guild commands, which do not suffer the same constraints. +]] +function Application.Prototype.createSlashCommandAsync( + self: Application, + slashCommand: interactionBuilder.JSON +): future.Future + return future.new(function() + local request = self.state.rest:newRequest() + + local status, response = + commandsRest.createGlobalApplicationCommandAsync(request, self.id, slashCommand):await() + + assert(status == "Fulfilled", tostring(response)) + + return commandObject.new(response) + end) +end + +--[[ + Delete an existing global application command. +]] +function Application.Prototype.deleteSlashCommandAsync( + self: Application, + slashCommandId: apiTypes.Snowflake +): future.Future + return future.new(function() + local request = self.state.rest:newRequest() + + local status, response = + commandsRest.deleteGlobalApplicationCommandAsync(request, self.id, slashCommandId):await() + + assert(status == "Fulfilled", tostring(response)) + end) +end + +--[[ + Edit an existing global application command. +]] +function Application.Prototype.editSlashCommandAsync( + self: Application, + slashCommandId: apiTypes.Snowflake, + slashCommand: interactionBuilder.JSON +): future.Future + return future.new(function() + local request = self.state.rest:newRequest() + + local status, response = + commandsRest.editGlobalApplicationCommandAsync(request, self.id, slashCommandId, slashCommand):await() + + assert(status == "Fulfilled", tostring(response)) + + return commandObject.new(response) + end) +end + +--[[ + Get a list of global application commands. +]] +function Application.Prototype.getSlashCommandsAsync( + self: Application, + withLocalizations: boolean? +): future.Future<{ commandObject.Command }> + return future.new(function() + local request = self.state.rest:newRequest() + + local status, response = commandsRest + .getGlobalApplicationCommandsAsync(request, self.id, { + withLocalizations = withLocalizations, + }) + :await() + + assert(status == "Fulfilled", tostring(response)) + + local commands = {} + + for _, commandData in response do + table.insert(commands, commandObject.new(commandData)) + end + + return commands + end) +end + +--[[ + Overwrite all global application commands. This will skip over commands that are the same as the current commands, and will not remove any existing commands. +]] +function Application.Prototype.overwriteSlashCommandsAsync( + self: Application, + slashCommands: { interactionBuilder.JSON } +): future.Future<{ commandObject.Command }> + return future.new(function() + local request = self.state.rest:newRequest() + + local status, response = + commandsRest.bulkOverwriteGlobalApplicationCommandsAsync(request, self.id, slashCommands):await() + + assert(status == "Fulfilled", tostring(response)) + + local commands = {} + + for _, commandData in response do + table.insert(commands, commandObject.new(commandData)) + end + + return commands + end) +end + +function Application.Prototype.sync(self: Application, applicationData: apiTypes.ApplicationObject) + local integrationTypesConfig = {} + + for integrationType, available in next, applicationData.integration_types_config or {} do + -- todo: are these actually numbers or strings? + + if integrationType == 0 then + integrationTypesConfig.GuildInstall = available + elseif integrationType == 1 then + integrationTypesConfig.UserInstall = available + end + end + + self.id = applicationData.id + self.name = applicationData.name + self.icon = applicationData.icon + self.description = applicationData.description + self.rpcOrigins = applicationData.rpc_origins + self.botPublic = applicationData.bot_public + self.botRequireCodeGrant = applicationData.bot_require_code_grant + self.bot = applicationData.bot and user.new(applicationData.bot) + self.termsOfServiceUrl = applicationData.terms_of_service_url + self.privacyPolicyUrl = applicationData.privacy_policy_url + self.owner = applicationData.owner and user.new(applicationData.owner) + self.summary = applicationData.summary + self.verifyKey = applicationData.verify_key + self.team = applicationData.team and team.new(applicationData.team) + self.guildId = applicationData.guild_id + self.guild = applicationData.guild + and applicationData.guild.id + and unavailableGuild.new(self.state, applicationData.guild.id) + self.primarySkuId = applicationData.primary_sku_id + self.slug = applicationData.slug + self.coverImage = applicationData.cover_image + self.flags = applicationData.flags and applicationBitflags.new(applicationData.flags) + self.installParams = applicationData.install_params and installParams.new(applicationData.install_params) + self.integrationTypesConfig = integrationTypesConfig + self.customInstallUrl = applicationData.custom_install_url +end + +function Application.Interface.new(state: state.State, applicationData: apiTypes.ApplicationObject): Application + local self = setmetatable( + { + state = state, + } :: Application, + { __index = Application.Prototype } + ) + + self:sync(applicationData) + + return self +end + +export type Application = typeof(Application.Prototype) & { + state: state.State, + + id: apiTypes.Snowflake, + name: string, + icon: string?, + description: string, + rpcOrigins: { string }?, + botPublic: boolean, + botRequireCodeGrant: boolean, + bot: user.User?, + termsOfServiceUrl: string?, + privacyPolicyUrl: string?, + owner: user.User?, + summary: string?, + verifyKey: string?, + team: team.Team?, + guildId: apiTypes.Snowflake?, + guild: unavailableGuild.UnavailableGuild?, + primarySkuId: apiTypes.Snowflake?, + slug: string?, + coverImage: string?, + flags: applicationBitflags.ApplicationBitflag?, + approximateGuildCount: number?, + approximateUserInstallCount: number?, + redirectUris: { string }?, + installParams: installParams.InstallParams?, + integrationTypesConfig: { [applicationTypes.IntegrationTypesConfig]: boolean }?, + customInstallUrl: string?, +} + +return Application.Interface diff --git a/packages/classes/src/application/command/choice.luau b/packages/classes/src/application/command/choice.luau new file mode 100644 index 0000000..ebf5fe5 --- /dev/null +++ b/packages/classes/src/application/command/choice.luau @@ -0,0 +1,34 @@ +--[[ + Implementation of the Discord Choice class in Luau + + https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-choice-structure +]] + +local apiTypes = require("@api-types/apiTypes") + +local Choice = {} + +Choice.Interface = {} +Choice.Prototype = {} + +function Choice.Prototype.sync(self: Choice, choiceData: apiTypes.ApplicationCommandOptionChoiceObject) + self.name = choiceData.name + self.nameLocalizations = choiceData.name_localizations + self.value = choiceData.value +end + +function Choice.Interface.new(choiceData: apiTypes.ApplicationCommandOptionChoiceObject): Choice + local self = setmetatable({} :: Choice, { __index = Choice.Prototype }) + + self:sync(choiceData) + + return self +end + +export type Choice = typeof(Choice.Prototype) & { + name: string, + nameLocalizations: { [apiTypes.LanguageLocales]: string }?, + value: string | number, +} + +return Choice.Interface diff --git a/packages/classes/src/application/command/command.luau b/packages/classes/src/application/command/command.luau new file mode 100644 index 0000000..fa894ae --- /dev/null +++ b/packages/classes/src/application/command/command.luau @@ -0,0 +1,85 @@ +--[[ + Implementation of the Discord Command class in Luau + + https://discord.com/developers/docs/interactions/application-commands#application-command-object +]] + +local apiTypes = require("@api-types/apiTypes") +local applicationTypes = require("@api-types/application") +local interactionTypes = require("@api-types/interaction") + +local permission = require("@classes/permission") + +local option = require("@classes/application/command/option") + +local Command = {} + +Command.Interface = {} +Command.Prototype = {} + +function Command.Prototype.sync(self: Command, commandData: apiTypes.ApplicationCommandObject) + local optionArray = {} + local integrationTypeArray = {} + local contextArray = {} + + -- fixme: need to use `next` iterator here to avoid LSP errors. + for _, optionData in next, commandData.options or {} do + table.insert(optionArray, option.new(optionData)) + end + + -- fixme: need to use `next` iterator here to avoid LSP errors. + for _, integrationType in next, commandData.integration_types or {} do + table.insert(integrationTypeArray, applicationTypes.IntegrationTypesConfig[integrationType]) + end + + -- fixme: need to use `next` iterator here to avoid LSP errors. + for _, context in next, commandData.contexts or {} do + table.insert(integrationTypeArray, interactionTypes.InteractionContextType[context]) + end + + self.id = commandData.id or "-1" + self.type = interactionTypes.ApplicationCommandType[commandData.type] + self.applicationId = commandData.application_id or "-1" + self.guildId = commandData.guild_id + self.name = commandData.name + self.nameLocalizations = commandData.name_localizations + self.description = commandData.description + self.descriptionLocalizations = commandData.description_localizations + self.options = optionArray + self.defaultMemberPermissions = commandData.default_member_permissions + and permission.new(commandData.default_member_permissions) + self.nsfw = commandData.nsfw + self.integrationTypes = integrationTypeArray + self.contexts = contextArray + self.version = commandData.version or "-1" +end + +function Command.Interface.new(commandData: apiTypes.ApplicationCommandObject): Command + local self = setmetatable({} :: Command, { __index = Command.Prototype }) + + self:sync(commandData) + + return self +end + +export type Command = typeof(Command.Prototype) & { + id: apiTypes.Snowflake, + type: apiTypes.ApplicationCommandType?, + applicationId: apiTypes.Snowflake, + guildId: apiTypes.Snowflake?, + name: string, + nameLocalizations: { [apiTypes.LanguageLocales]: string }?, + description: string, + descriptionLocalizations: { [apiTypes.LanguageLocales]: string }?, + options: { option.Option }, + defaultMemberPermissions: permission.Permission?, + -- dmPermission: boolean?, + -- defaultPermissions: boolean? + nsfw: boolean?, + integrationTypes: { applicationTypes.IntegrationTypesConfig }, + contexts: { interactionTypes.InteractionContextType }, + version: apiTypes.Snowflake, + -- handler: -- todo: implement handler type +} + +return Command.Interface diff --git a/packages/classes/src/application/command/option.luau b/packages/classes/src/application/command/option.luau new file mode 100644 index 0000000..cf5986e --- /dev/null +++ b/packages/classes/src/application/command/option.luau @@ -0,0 +1,80 @@ +--[[ + Implementation of the Discord Option class in Luau + + https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-choice-structure +]] + +local apiTypes = require("@api-types/apiTypes") +local channelTypes = require("@api-types/channel") +local interactionTypes = require("@api-types/interaction") + +local choice = require("@classes/application/command/choice") + +local Option = {} + +Option.Interface = {} +Option.Prototype = {} + +function Option.Prototype.sync(self: Option, optionData: apiTypes.ApplicationCommandOptionObject) + local choiceArray = {} + local optionArray = {} + + local channelTypeArray = {} + + -- fixme: need to use `next` iterator here to avoid LSP errors. + for _, choiceData in next, optionData.choices or {} do + table.insert(choiceArray, choice.new(choiceData)) + end + + -- fixme: need to use `next` iterator here to avoid LSP errors. + for _, optionData in next, optionData.options or {} do + table.insert(optionArray, Option.Interface.new(optionData)) + end + + -- fixme: need to use `next` iterator here to avoid LSP errors. + for _, channelType in next, optionData.channel_types or {} do + table.insert(channelTypeArray, channelTypes.ChannelTypes[channelType]) + end + + self.type = interactionTypes.ApplicationCommandOptionType[optionData.type] + self.name = optionData.name + self.nameLocalizations = optionData.name_localizations + self.description = optionData.description + self.descriptionLocalizations = optionData.description_localizations + self.required = optionData.required + self.choices = choiceArray + self.options = optionArray + self.channelTypes = channelTypeArray + self.minValue = optionData.min_value + self.maxValue = optionData.max_value + self.minLength = optionData.min_length + self.maxLength = optionData.max_length + self.autocomplete = optionData.autocomplete +end + +function Option.Interface.new(optionData: apiTypes.ApplicationCommandOptionObject): Option + local self = setmetatable({} :: Option, { __index = Option.Prototype }) + + self:sync(optionData) + + return self +end + +export type Option = typeof(Option.Prototype) & { + type: apiTypes.ApplicationCommandOptionType, + name: string, + nameLocalizations: { [apiTypes.LanguageLocales]: string }?, + description: string, + descriptionLocalizations: { [apiTypes.LanguageLocales]: string }?, + required: boolean?, + choices: { choice.Choice }?, + options: { Option }?, + channelTypes: { channelTypes.ChannelType }, + minValue: number?, + maxValue: number?, + minLength: number?, + maxLength: number?, + autocomplete: boolean?, +} + +return Option.Interface diff --git a/packages/classes/src/application/installParams.luau b/packages/classes/src/application/installParams.luau new file mode 100644 index 0000000..d7872e4 --- /dev/null +++ b/packages/classes/src/application/installParams.luau @@ -0,0 +1,35 @@ +--[[ + Implementation of the Discord InstallParams class in Luau + + https://discord.com/developers/docs/resources/application#install-params-object +]] + +local apiTypes = require("@api-types/apiTypes") +local applicationTypes = require("@api-types/application") + +local permission = require("@classes/permission") + +local InstallParams = {} + +InstallParams.Interface = {} +InstallParams.Prototype = {} + +function InstallParams.Prototype.sync(self: InstallParams, installParamsData: apiTypes.InstallParamsObject) + self.scopes = installParamsData.scopes + self.permissions = permission.new(installParamsData.permissions) +end + +function InstallParams.Interface.new(installParamsData: apiTypes.InstallParamsObject): InstallParams + local self = setmetatable({} :: InstallParams, { __index = InstallParams.Prototype }) + + self:sync(installParamsData) + + return self +end + +export type InstallParams = typeof(InstallParams.Prototype) & { + scopes: { applicationTypes.OAuth2Scopes }, + permissions: permission.Permission, +} + +return InstallParams.Interface diff --git a/packages/classes/src/application/team/member.luau b/packages/classes/src/application/team/member.luau new file mode 100644 index 0000000..fca612e --- /dev/null +++ b/packages/classes/src/application/team/member.luau @@ -0,0 +1,45 @@ +--[[ + Implementation of the Discord Member class in Luau + + https://discord.com/developers/docs/topics/teams#data-models-team-object +]] + +local apiTypes = require("@api-types/apiTypes") +local applicationTypes = require("@api-types/application") + +local user = require("@classes/user") + +local Member = {} + +Member.Interface = {} +Member.Prototype = {} + +function Member.Prototype.sync(self: Member, memberData: apiTypes.TeamMemberObject) + self.teamId = memberData.team_id + self.user = user.new(memberData.user) + + self.membershipState = applicationTypes.MembershipState[memberData.membership_state] + + self.role = ( + memberData.role == "Admin" and "Admin" + or memberData.role == "Developer" and "Developer" + or memberData.role == "Read-only" and "ReadOnly" + ) :: applicationTypes.TeamRole +end + +function Member.Interface.new(memberData: apiTypes.TeamMemberObject): Member + local self = setmetatable({} :: Member, { __index = Member.Prototype }) + + self:sync(memberData) + + return self +end + +export type Member = typeof(Member.Prototype) & { + membershipState: applicationTypes.MembershipState, + teamId: apiTypes.Snowflake, + user: user.User, + role: applicationTypes.TeamRole, +} + +return Member.Interface diff --git a/packages/classes/src/application/team/team.luau b/packages/classes/src/application/team/team.luau new file mode 100644 index 0000000..e024cc6 --- /dev/null +++ b/packages/classes/src/application/team/team.luau @@ -0,0 +1,46 @@ +--[[ + Implementation of the Discord Team class in Luau + + https://discord.com/developers/docs/topics/teams#data-models-team-object +]] + +local apiTypes = require("@api-types/apiTypes") + +local member = require("@classes/application/team/member") + +local Team = {} + +Team.Interface = {} +Team.Prototype = {} + +function Team.Prototype.sync(self: Team, teamData: apiTypes.TeamObject) + local memberArray = {} + + for _, memberData in teamData.members do + table.insert(memberArray, member.new(memberData)) + end + + self.icon = teamData.icon + self.id = teamData.id + self.members = memberArray + self.name = teamData.name + self.ownerUserId = teamData.owner_user_id +end + +function Team.Interface.new(teamData: apiTypes.TeamObject): Team + local self = setmetatable({} :: Team, { __index = Team.Prototype }) + + self:sync(teamData) + + return self +end + +export type Team = typeof(Team.Prototype) & { + icon: string?, + id: apiTypes.Snowflake, + members: { member.Member }, + name: string, + ownerUserId: apiTypes.Snowflake, +} + +return Team.Interface diff --git a/packages/classes/src/attachment.luau b/packages/classes/src/attachment.luau new file mode 100644 index 0000000..a51c48e --- /dev/null +++ b/packages/classes/src/attachment.luau @@ -0,0 +1,58 @@ +--[[ + Implementation of the Discord Attachment class in Luau + + https://discord.com/developers/docs/resources/message#attachment-object +]] + +local apiTypes = require("@api-types/apiTypes") + +local attachmentBitflag = require("@classes/bitflags/attachment") + +local Attachment = {} + +Attachment.Interface = {} +Attachment.Prototype = {} + +function Attachment.Prototype.sync(self: Attachment, attachmentData: apiTypes.AttachmentObject) + self.id = attachmentData.id + self.filename = attachmentData.filename + self.title = attachmentData.title + self.description = attachmentData.description + self.contentType = attachmentData.content_type + self.size = attachmentData.size + self.url = attachmentData.url + self.proxyUrl = attachmentData.proxy_url + self.height = attachmentData.height + self.width = attachmentData.width + self.ephemeral = attachmentData.ephemeral + self.durationSeconds = attachmentData.duration_secs + self.waveform = attachmentData.waveform + self.flags = attachmentData.flags and attachmentBitflag.new(attachmentData.flags) +end + +function Attachment.Interface.new(attachmentData: apiTypes.AttachmentObject): Attachment + local self = setmetatable({} :: Attachment, { __index = Attachment.Prototype }) + + self:sync(attachmentData) + + return self +end + +export type Attachment = typeof(Attachment.Prototype) & { + id: apiTypes.Snowflake, + filename: string, + title: string?, + description: string?, + contentType: string?, + size: number, + url: string, + proxyUrl: string, + height: number?, + width: number?, + ephemeral: boolean?, + durationSeconds: number?, + waveform: string?, + flags: attachmentBitflag.AttachmentBitflag?, +} + +return Attachment.Interface diff --git a/packages/classes/src/avatarDecoration.luau b/packages/classes/src/avatarDecoration.luau new file mode 100644 index 0000000..6807579 --- /dev/null +++ b/packages/classes/src/avatarDecoration.luau @@ -0,0 +1,31 @@ +--[[ + Implementation of the Discord Presence class in Luau + + https://discord.com/developers/docs/resources/user#avatar-decoration-data-object +]] + +local apiTypes = require("@api-types/apiTypes") + +local AvatarDecoration = {} + +AvatarDecoration.Interface = {} +AvatarDecoration.Prototype = {} + +function AvatarDecoration.Interface.new(asset: string, skuId: apiTypes.Snowflake): AvatarDecoration + local self = setmetatable( + { + asset = asset, + skuId = skuId, + } :: AvatarDecoration, + { __index = AvatarDecoration.Prototype } + ) + + return self +end + +export type AvatarDecoration = typeof(AvatarDecoration.Prototype) & { + asset: string, + skuId: apiTypes.Snowflake, +} + +return AvatarDecoration.Interface diff --git a/packages/classes/src/bitflags/application.luau b/packages/classes/src/bitflags/application.luau new file mode 100644 index 0000000..98b0e74 --- /dev/null +++ b/packages/classes/src/bitflags/application.luau @@ -0,0 +1,125 @@ +--[[ + Implementation of the Discord Application Bitflags in Luau + + https://discord.com/developers/docs/resources/application#application-object-application-flags +]] + +local bit = require("@vendor/bit") + +local ApplicationBitflag = {} + +ApplicationBitflag.Interface = {} +ApplicationBitflag.Prototype = {} + +--[[ + Indicates if an app uses the Auto Moderation API +]] +function ApplicationBitflag.Prototype.applicationAutoModerationRoleCreateBadge(self: ApplicationBitflag) + local targetBit = bit.lshift(1, 6) + + return bit.band(self.flag, targetBit) == targetBit +end + +--[[ + Intent required for bots in 100 or more servers to receive presence_update events +]] +function ApplicationBitflag.Prototype.gatewayPresence(self: ApplicationBitflag) + local targetBit = bit.lshift(1, 12) + + return bit.band(self.flag, targetBit) == targetBit +end + +--[[ + Intent required for bots in under 100 servers to receive presence_update events, + found on the Bot page in your app's settings +]] +function ApplicationBitflag.Prototype.gatewayPresenceLimited(self: ApplicationBitflag) + local targetBit = bit.lshift(1, 13) + + return bit.band(self.flag, targetBit) == targetBit +end + +--[[ + Intent required for bots in 100 or more servers to receive member-related events like + guild_member_add. See the list of member-related events under GUILD_MEMBERS +]] +function ApplicationBitflag.Prototype.gatewayGuildMembers(self: ApplicationBitflag) + local targetBit = bit.lshift(1, 14) + + return bit.band(self.flag, targetBit) == targetBit +end + +--[[ + Intent required for bots in under 100 servers to receive member-related events + like guild_member_add, found on the Bot page in your app's settings. + + See the list of member-related events under GUILD_MEMBERS +]] +function ApplicationBitflag.Prototype.gatewayGuildMembersLimited(self: ApplicationBitflag) + local targetBit = bit.lshift(1, 15) + + return bit.band(self.flag, targetBit) == targetBit +end + +--[[ + Indicates unusual growth of an app that prevents verification +]] +function ApplicationBitflag.Prototype.verificationPendingGuildLimit(self: ApplicationBitflag) + local targetBit = bit.lshift(1, 16) + + return bit.band(self.flag, targetBit) == targetBit +end + +--[[ + Indicates if an app is embedded within the Discord client (currently unavailable publicly) +]] +function ApplicationBitflag.Prototype.embedded(self: ApplicationBitflag) + local targetBit = bit.lshift(1, 17) + + return bit.band(self.flag, targetBit) == targetBit +end + +--[[ + Intent required for bots in 100 or more servers to receive message content +]] +function ApplicationBitflag.Prototype.gatewayMessageContent(self: ApplicationBitflag) + local targetBit = bit.lshift(1, 18) + + return bit.band(self.flag, targetBit) == targetBit +end + +--[[ + Intent required for bots in under 100 servers to receive message content, + found on the Bot page in your app's settings +]] +function ApplicationBitflag.Prototype.gatewayMessageContentLimited(self: ApplicationBitflag) + local targetBit = bit.lshift(1, 19) + + return bit.band(self.flag, targetBit) == targetBit +end + +--[[ + Indicates if an app has registered global application commands +]] +function ApplicationBitflag.Prototype.applicationCommandBadge(self: ApplicationBitflag) + local targetBit = bit.lshift(1, 23) + + return bit.band(self.flag, targetBit) == targetBit +end + +function ApplicationBitflag.Interface.new(flag: number): ApplicationBitflag + local self = setmetatable( + { + flag = flag, + } :: ApplicationBitflag, + { __index = ApplicationBitflag.Prototype } + ) + + return self +end + +export type ApplicationBitflag = typeof(ApplicationBitflag.Prototype) & { + flag: number, +} + +return ApplicationBitflag.Interface diff --git a/packages/classes/src/bitflags/attachment.luau b/packages/classes/src/bitflags/attachment.luau new file mode 100644 index 0000000..15d2f3a --- /dev/null +++ b/packages/classes/src/bitflags/attachment.luau @@ -0,0 +1,39 @@ +--[[ + Implementation of the Discord Role Bitflags in Luau + + https://discord.com/developers/docs/resources/message#attachment-object-attachment-flags +]] + +local bit = require("@vendor/bit") + +local AttachmentBitflag = {} + +AttachmentBitflag.Interface = {} +AttachmentBitflag.Prototype = {} + +--[[ + this attachment has been edited using the remix feature on mobile + +]] +function AttachmentBitflag.Prototype.isRemix(self: AttachmentBitflag) + local targetBit = bit.lshift(1, 2) + + return bit.band(self.flag, targetBit) == targetBit +end + +function AttachmentBitflag.Interface.new(flag: number): AttachmentBitflag + local self = setmetatable( + { + flag = flag, + } :: AttachmentBitflag, + { __index = AttachmentBitflag.Prototype } + ) + + return self +end + +export type AttachmentBitflag = typeof(AttachmentBitflag.Prototype) & { + flag: number, +} + +return AttachmentBitflag.Interface diff --git a/packages/classes/src/bitflags/channel.luau b/packages/classes/src/bitflags/channel.luau new file mode 100644 index 0000000..515db88 --- /dev/null +++ b/packages/classes/src/bitflags/channel.luau @@ -0,0 +1,57 @@ +--[[ + Implementation of the Discord Role Bitflags in Luau + + https://discord.com/developers/docs/resources/channel#channel-object-channel-flags +]] + +local bit = require("@vendor/bit") + +local ChannelBitflag = {} + +ChannelBitflag.Interface = {} +ChannelBitflag.Prototype = {} + +--[[ + if this thread is pinned to the top of its parent GUILD_FORUM or GUILD_MEDIA channel +]] +function ChannelBitflag.Prototype.isPinned(self: ChannelBitflag) + local targetBit = bit.lshift(1, 1) + + return bit.band(self.flag, targetBit) == targetBit +end + +--[[ + whether a tag is required to be specified when creating a thread in a + GUILD_FORUM or a GUILD_MEDIA channel. Tags are specified in the applied_tags field. +]] +function ChannelBitflag.Prototype.requiresTag(self: ChannelBitflag) + local targetBit = bit.lshift(1, 4) + + return bit.band(self.flag, targetBit) == targetBit +end + +--[[ + When set hides the embedded media download options. Available only for media channels +]] +function ChannelBitflag.Prototype.hideMediaDownloadOptions(self: ChannelBitflag) + local targetBit = bit.lshift(1, 15) + + return bit.band(self.flag, targetBit) == targetBit +end + +function ChannelBitflag.Interface.new(flag: number): ChannelBitflag + local self = setmetatable( + { + flag = flag, + } :: ChannelBitflag, + { __index = ChannelBitflag.Prototype } + ) + + return self +end + +export type ChannelBitflag = typeof(ChannelBitflag.Prototype) & { + flag: number, +} + +return ChannelBitflag.Interface diff --git a/packages/classes/src/bitflags/guildMember.luau b/packages/classes/src/bitflags/guildMember.luau new file mode 100644 index 0000000..e519702 --- /dev/null +++ b/packages/classes/src/bitflags/guildMember.luau @@ -0,0 +1,65 @@ +--[[ + Implementation of the Discord Role Bitflags in Luau + + https://discord.com/developers/docs/resources/guild#guild-member-object-guild-member-flags +]] + +local bit = require("@vendor/bit") + +local GuildMemberBitflag = {} + +GuildMemberBitflag.Interface = {} +GuildMemberBitflag.Prototype = {} + +--[[ + Member has left and rejoined the guild +]] +function GuildMemberBitflag.Prototype.didRejoin(self: GuildMemberBitflag) + local targetBit = bit.lshift(1, 0) + + return bit.band(self.flag, targetBit) == targetBit +end + +--[[ + Member has completed onboarding +]] +function GuildMemberBitflag.Prototype.completedOnboarding(self: GuildMemberBitflag) + local targetBit = bit.lshift(1, 1) + + return bit.band(self.flag, targetBit) == targetBit +end + +--[[ + Member is exempt from guild verification requirements +]] +function GuildMemberBitflag.Prototype.bypassesVerification(self: GuildMemberBitflag) + local targetBit = bit.lshift(1, 2) + + return bit.band(self.flag, targetBit) == targetBit +end + +--[[ + Member has started onboarding +]] +function GuildMemberBitflag.Prototype.startedOnboarding(self: GuildMemberBitflag) + local targetBit = bit.lshift(1, 3) + + return bit.band(self.flag, targetBit) == targetBit +end + +function GuildMemberBitflag.Interface.new(flag: number): GuildMemberBitflag + local self = setmetatable( + { + flag = flag, + } :: GuildMemberBitflag, + { __index = GuildMemberBitflag.Prototype } + ) + + return self +end + +export type GuildMemberBitflag = typeof(GuildMemberBitflag.Prototype) & { + flag: number, +} + +return GuildMemberBitflag.Interface diff --git a/packages/classes/src/bitflags/message.luau b/packages/classes/src/bitflags/message.luau new file mode 100644 index 0000000..04d0bd1 --- /dev/null +++ b/packages/classes/src/bitflags/message.luau @@ -0,0 +1,128 @@ +--[[ + Implementation of the Discord Application Bitflags in Luau + + https://discord.com/developers/docs/resources/message#message-object-message-flags +]] + +local bit = require("@vendor/bit") + +local MessageBitflag = {} + +MessageBitflag.Interface = {} +MessageBitflag.Prototype = {} + +--[[ + this message has been published to subscribed channels (via Channel Following) +]] +function MessageBitflag.Prototype.crossposted(self: MessageBitflag) + local targetBit = bit.lshift(1, 0) + + return bit.band(self.flag, targetBit) == targetBit +end + +--[[ + this message originated from a message in another channel (via Channel Following) +]] +function MessageBitflag.Prototype.isCrossposted(self: MessageBitflag) + local targetBit = bit.lshift(1, 1) + + return bit.band(self.flag, targetBit) == targetBit +end + +--[[ + do not include any embeds when serializing this message +]] +function MessageBitflag.Prototype.suppressEmbeds(self: MessageBitflag) + local targetBit = bit.lshift(1, 2) + + return bit.band(self.flag, targetBit) == targetBit +end + +--[[ + the source message for this crosspost has been deleted (via Channel Following) +]] +function MessageBitflag.Prototype.sourceMessageDeleted(self: MessageBitflag) + local targetBit = bit.lshift(1, 3) + + return bit.band(self.flag, targetBit) == targetBit +end + +--[[ + this message came from the urgent message system +]] +function MessageBitflag.Prototype.urgent(self: MessageBitflag) + local targetBit = bit.lshift(1, 4) + + return bit.band(self.flag, targetBit) == targetBit +end + +--[[ + this message has an associated thread, with the same id as the message +]] +function MessageBitflag.Prototype.hasThread(self: MessageBitflag) + local targetBit = bit.lshift(1, 5) + + return bit.band(self.flag, targetBit) == targetBit +end + +--[[ + this message is only visible to the user who invoked the Interaction +]] +function MessageBitflag.Prototype.ephemeral(self: MessageBitflag) + local targetBit = bit.lshift(1, 6) + + return bit.band(self.flag, targetBit) == targetBit +end + +--[[ + this message is an Interaction Response and the bot is "thinking" +]] +function MessageBitflag.Prototype.loading(self: MessageBitflag) + local targetBit = bit.lshift(1, 7) + + return bit.band(self.flag, targetBit) == targetBit +end + +--[[ + this message failed to mention some roles and add their members to the thread +]] +function MessageBitflag.Prototype.failedToMentionSomeRolesInThread(self: MessageBitflag) + local targetBit = bit.lshift(1, 8) + + return bit.band(self.flag, targetBit) == targetBit +end + +--[[ + this message will not trigger push and desktop notifications +]] +function MessageBitflag.Prototype.suppressNotifications(self: MessageBitflag) + local targetBit = bit.lshift(1, 12) + + return bit.band(self.flag, targetBit) == targetBit +end + +--[[ + this message is a voice message +]] +function MessageBitflag.Prototype.isVouceMessage(self: MessageBitflag) + local targetBit = bit.lshift(1, 13) + + return bit.band(self.flag, targetBit) == targetBit +end + +function MessageBitflag.Interface.new(flag: number): MessageBitflag + local self = setmetatable( + { + flag = flag, + } :: MessageBitflag, + { __index = MessageBitflag.Prototype } + ) + + return self +end + +export type MessageBitflag = typeof(MessageBitflag.Prototype) & { + flag: number, +} + +return MessageBitflag.Interface diff --git a/packages/classes/src/bitflags/role.luau b/packages/classes/src/bitflags/role.luau new file mode 100644 index 0000000..70612ee --- /dev/null +++ b/packages/classes/src/bitflags/role.luau @@ -0,0 +1,38 @@ +--[[ + Implementation of the Discord Role Bitflags in Luau + + https://discord.com/developers/docs/topics/permissions#role-object-role-flags +]] + +local bit = require("@vendor/bit") + +local RoleBitflag = {} + +RoleBitflag.Interface = {} +RoleBitflag.Prototype = {} + +--[[ + Checks if the role is in an onboarding prompt. +]] +function RoleBitflag.Prototype.isInOnboardingPrompt(self: RoleBitflag) + local targetBit = bit.lshift(1, 0) + + return bit.band(self.flag, targetBit) == targetBit +end + +function RoleBitflag.Interface.new(flag: number): RoleBitflag + local self = setmetatable( + { + flag = flag, + } :: RoleBitflag, + { __index = RoleBitflag.Prototype } + ) + + return self +end + +export type RoleBitflag = typeof(RoleBitflag.Prototype) & { + flag: number, +} + +return RoleBitflag.Interface diff --git a/packages/classes/src/bitflags/systemChannel.luau b/packages/classes/src/bitflags/systemChannel.luau new file mode 100644 index 0000000..74ba56c --- /dev/null +++ b/packages/classes/src/bitflags/systemChannel.luau @@ -0,0 +1,83 @@ +--[[ + Implementation of the Discord Role Bitflags in Luau + + https://discord.com/developers/docs/resources/guild#guild-object-system-channel-flags +]] + +local bit = require("@vendor/bit") + +local SystemChannelBitflag = {} + +SystemChannelBitflag.Interface = {} +SystemChannelBitflag.Prototype = {} + +--[[ + Returns if the system channel has the suppress join notifications bitflag set. +]] +function SystemChannelBitflag.Prototype.suppressJoinNotifications(self: SystemChannelBitflag) + local targetBit = bit.lshift(1, 0) + + return bit.band(self.flag, targetBit) == targetBit +end + +--[[ + Returns if the system channel has the suppress premium subscriptions bitflag set. +]] +function SystemChannelBitflag.Prototype.suppressPremiumSubscriptions(self: SystemChannelBitflag) + local targetBit = bit.lshift(1, 1) + + return bit.band(self.flag, targetBit) == targetBit +end + +--[[ + Returns if the system channel has the suppress guild reminder notifications bitflag set. +]] +function SystemChannelBitflag.Prototype.suppressGuildReminderNotifications(self: SystemChannelBitflag) + local targetBit = bit.lshift(1, 2) + + return bit.band(self.flag, targetBit) == targetBit +end + +--[[ + Returns if the system channel has the suppress join notification replies bitflag set. +]] +function SystemChannelBitflag.Prototype.suppressJoinNotificationReplies(self: SystemChannelBitflag) + local targetBit = bit.lshift(1, 3) + + return bit.band(self.flag, targetBit) == targetBit +end + +--[[ + Returns if the system channel has the suppress role subscriptions purchase notifications bitflag set. +]] +function SystemChannelBitflag.Prototype.suppressRoleSubscriptionPurchaseNotifications(self: SystemChannelBitflag) + local targetBit = bit.lshift(1, 4) + + return bit.band(self.flag, targetBit) == targetBit +end + +--[[ + Returns if the system channel has the suppress role subscriptions purchase notification replies bitflag set. +]] +function SystemChannelBitflag.Prototype.suppressRoleSubscriptionPurchaseNotificationReplies(self: SystemChannelBitflag) + local targetBit = bit.lshift(1, 5) + + return bit.band(self.flag, targetBit) == targetBit +end + +function SystemChannelBitflag.Interface.new(flag: number): SystemChannelBitflag + local self = setmetatable( + { + flag = flag, + } :: SystemChannelBitflag, + { __index = SystemChannelBitflag.Prototype } + ) + + return self +end + +export type SystemChannelBitflag = typeof(SystemChannelBitflag.Prototype) & { + flag: number, +} + +return SystemChannelBitflag.Interface diff --git a/packages/classes/src/bitflags/threadMember.luau b/packages/classes/src/bitflags/threadMember.luau new file mode 100644 index 0000000..7a362e9 --- /dev/null +++ b/packages/classes/src/bitflags/threadMember.luau @@ -0,0 +1,29 @@ +--[[ + Implementation of the Discord Role Bitflags in Luau + + https://discord.com/developers/docs/resources/channel#thread-member-object + + ⚠️ Discord currently doesn't provide any flag documentation for this class? Wack. +]] + +local ThreadMemberBitflag = {} + +ThreadMemberBitflag.Interface = {} +ThreadMemberBitflag.Prototype = {} + +function ThreadMemberBitflag.Interface.new(flag: number): ThreadMemberBitflag + local self = setmetatable( + { + flag = flag, + } :: ThreadMemberBitflag, + { __index = ThreadMemberBitflag.Prototype } + ) + + return self +end + +export type ThreadMemberBitflag = typeof(ThreadMemberBitflag.Prototype) & { + flag: number, +} + +return ThreadMemberBitflag.Interface diff --git a/packages/classes/src/cache.luau b/packages/classes/src/cache.luau new file mode 100644 index 0000000..481ed11 --- /dev/null +++ b/packages/classes/src/cache.luau @@ -0,0 +1,114 @@ +--[[ + Cache class, responsible for storing the cache of the bot, as well as removing keys/values + from the cache when they are no longer needed. + + each key and value has a lifetime value assigned to them, this is used to determine + when a key/value should be removed from the cache. + + when a key/value has been updated or requested, the lifetime is reset to the current time + + the lifetime value. +]] + +local task = require("@std-polyfills/task") + +local Cache = {} + +Cache.Interface = {} +Cache.Prototype = {} + +--[[ + Sets a key-value pair in the cache. This will additionally call 'createThreadLoop' to ensure + that there's a thread available to remove expired keys/values from the cache. +]] +function Cache.Prototype.set(self: Cache, key: K, value: V) + self.store[key] = value +end + +--[[ + Fetches a key-value pair from the cache. +]] +function Cache.Prototype.get(self: Cache, key: K): V + return self.store[key] +end + +--[[ + Responsible for creating a thread loop that will remove expired keys/values from the cache. + + This thread will die when the cache is empty, which requires any further additions to the cache + to call this function again in order to create a new thread. +]] +function Cache.Prototype.createThreadLoop(self: Cache) + if self.thread then + return + end + + self.thread = task.spawn(function() + while true do + if #self.store == 0 then + self.thread = nil + + return + end + + task.wait(self.rate or 60) + + local now = os.clock() + + for key in self.store do + if now > self.lifetimes[key] + self.expiry then + self.store[key] = nil + self.lifetimes[key] = nil + end + end + end + end) +end + +--[[ + Constructor for the Cache class. +]] +function Cache.Interface.new(expiry: number, rate: number?): Cache + -- fixme: hack for luau's type system, apparently it doesn't want 'store' calling 'createThreadLoop' ? + local self: any + + local lifetimes = {} + local store = setmetatable({}, { + __index = function(storeSelf, key: K) + rawset(lifetimes, key, os.clock()) + + return rawget(storeSelf, key) + end, + __newindex = function(storeSelf, key: K, value: V) + rawset(lifetimes, key, os.clock()) + rawset(storeSelf, key, value) + + self:createThreadLoop() + end, + }) :: any + + self = setmetatable( + { + store = store :: { [K]: V }, + lifetimes = lifetimes, + + expiry = expiry, + rate = rate, + } :: Cache, + { + __index = Cache.Prototype, + } + ) + + return self :: Cache +end + +export type Cache = typeof(Cache.Prototype) & { + store: { [K]: V }, + lifetimes: { [K]: number }, + expiry: number, + rate: number?, + + thread: thread?, +} + +return Cache.Interface diff --git a/packages/classes/src/channels/behaviour/channel.luau b/packages/classes/src/channels/behaviour/channel.luau new file mode 100644 index 0000000..1073e06 --- /dev/null +++ b/packages/classes/src/channels/behaviour/channel.luau @@ -0,0 +1,41 @@ +--[[ + Channel Behaviour, responsible for implementing the various properties, and methods for a generic + channel object. +]] + +local apiTypes: string = require("@api-types/apiTypes") +local channelTypes = require("@api-types/channel") + +local channelBitflags = require("@classes/bitflags/channel") + +local ChannelBehaviour = {} + +ChannelBehaviour.Interface = {} +ChannelBehaviour.Prototype = {} + +function ChannelBehaviour.Interface.inheritProperties(class: any, channelData: apiTypes.ChannelObject) + class.type = channelTypes.ChannelTypes[channelData.type] + + class.id = channelData.id + class.name = channelData.name + + class.flags = channelData.flags and channelBitflags.new(channelData.flags) +end + +function ChannelBehaviour.Interface.inheritMethods(class: any) + for key, value in ChannelBehaviour.Prototype do + class[key] = value + end +end + +export type ChannelBehaviourMethods = typeof(ChannelBehaviour.Prototype) +export type ChannelBehaviourProperties = { + id: apiTypes.Snowflake, + type: channelTypes.ChannelType, + name: string?, + flags: channelBitflags.ChannelBitflag?, +} + +export type ChannelBehaviour = ChannelBehaviourMethods & ChannelBehaviourProperties + +return ChannelBehaviour.Interface diff --git a/packages/classes/src/channels/behaviour/forum.luau b/packages/classes/src/channels/behaviour/forum.luau new file mode 100644 index 0000000..748d7ca --- /dev/null +++ b/packages/classes/src/channels/behaviour/forum.luau @@ -0,0 +1,51 @@ +--[[ + Forum Behaviour, responsible for implementing the various properties, and methods for a forum thread channel. +]] + +local apiTypes = require("@api-types/apiTypes") +local channelTypes = require("@api-types/channel") + +local forumTag = require("@classes/channels/forumTag") +local defaultReaction = require("@classes/channels/defaultReaction") + +local ForumBehaviour = {} + +ForumBehaviour.Interface = {} +ForumBehaviour.Prototype = {} + +function ForumBehaviour.Interface.inheritProperties(class: any, channelData: apiTypes.ChannelObject) + local forumTagArray = {} + + -- fixme: need to use `next` iterator here to avoid LSP errors. + for _, forumTagData in next, channelData.available_tags or {} do + table.insert(forumTagArray, forumTag.new(forumTagData)) + end + + class.availableTags = forumTagArray + class.appliedTags = channelData.applied_tags + + class.defaultReaction = channelData.default_reaction_emoji + and defaultReaction.new(channelData.default_reaction_emoji) + + class.defaultSortOrder = channelTypes.ForumSortOrder[channelData.default_sort_order] + class.defaultForumLayout = channelTypes.ForumLayout[channelData.default_forum_layout] +end + +function ForumBehaviour.Interface.inheritMethods(class: any) + for key, value in ForumBehaviour.Prototype do + class[key] = value + end +end + +export type ForumBehaviourMethods = typeof(ForumBehaviour.Prototype) +export type ForumBehaviourProperties = { + availableTags: { forumTag.ForumTag }, + defaultReaction: defaultReaction.DefaultReaction?, + appliedTags: { apiTypes.Snowflake }, + defaultSortOrder: channelTypes.ForumSortOrder, + defaultForumLayout: channelTypes.ForumLayout, +} + +export type ForumBehaviour = ForumBehaviourMethods & ForumBehaviourProperties + +return ForumBehaviour.Interface diff --git a/packages/classes/src/channels/behaviour/groupDM.luau b/packages/classes/src/channels/behaviour/groupDM.luau new file mode 100644 index 0000000..8a35894 --- /dev/null +++ b/packages/classes/src/channels/behaviour/groupDM.luau @@ -0,0 +1,46 @@ +--[[ + Group DM Behaviour, responsible for implementing the various properties, and methods for a group DM. +]] + +local apiTypes = require("@api-types/apiTypes") + +local user = require("@classes/user") + +local DMBehaviour = {} + +DMBehaviour.Interface = {} +DMBehaviour.Prototype = {} + +function DMBehaviour.Interface.inheritProperties(class: any, channelData: apiTypes.ChannelObject) + local recipientArray = {} + + -- fixme: need to use `next` iterator here to avoid LSP errors. + for _, recipient in next, channelData.recipients or {} do + table.insert(recipientArray, user.new(recipient)) + end + + class.recipients = recipientArray + class.icon = channelData.icon + class.ownerId = channelData.owner_id + class.applicationId = channelData.application_id + class.managed = channelData.managed +end + +function DMBehaviour.Interface.inheritMethods(class: any) + for key, value in DMBehaviour.Prototype do + class[key] = value + end +end + +export type DMBehaviourMethods = typeof(DMBehaviour.Prototype) +export type DMBehaviourProperties = { + recipients: { user.User }, + icon: string?, + ownerId: string?, + applicationId: string?, + managed: boolean, +} + +export type DMBehaviour = DMBehaviourMethods & DMBehaviourProperties + +return DMBehaviour.Interface diff --git a/packages/classes/src/channels/behaviour/guild.luau b/packages/classes/src/channels/behaviour/guild.luau new file mode 100644 index 0000000..1ed7b24 --- /dev/null +++ b/packages/classes/src/channels/behaviour/guild.luau @@ -0,0 +1,54 @@ +--[[ + Guild Behaviour, responsible for implementing the various properties, and methods for a guild channels. +]] + +local apiTypes = require("@api-types/apiTypes") + +local overwrite = require("@classes/channels/overwrite") + +local GuildBehaviour = {} + +GuildBehaviour.Interface = {} +GuildBehaviour.Prototype = {} + +function GuildBehaviour.Interface.inheritProperties(class: any, channelData: apiTypes.ChannelObject) + local permissionOverwriteArray = {} + + for _, overwriteData in channelData.permission_overwrites do + table.insert(permissionOverwriteArray, overwrite.new(overwriteData)) + end + + class.permissionOverwrites = permissionOverwriteArray + + class.nsfw = channelData.nsfw + class.topic = channelData.topic + class.guildId = channelData.guild_id + class.position = channelData.position + class.parentId = channelData.parent_id + class.permissions = channelData.permissions + class.defaultThreadRateLimitPerUser = channelData.rate_limit_per_user + class.defaultAutoArchiveDuration = channelData.default_auto_archive_duration +end + +function GuildBehaviour.Interface.inheritMethods(class: any) + for key, value in GuildBehaviour.Prototype do + class[key] = value + end +end + +export type GuildBehaviourMethods = typeof(GuildBehaviour.Prototype) +export type GuildBehaviourProperties = { + permissionOverwrites: { overwrite.Overwrite }, + nsfw: boolean, + topic: string?, + guildId: string, + position: number, + parentId: string?, + permissions: number, + defaultThreadRateLimitPerUser: number, + defaultAutoArchiveDuration: number, +} + +export type GuildBehaviour = GuildBehaviourMethods & GuildBehaviourProperties + +return GuildBehaviour.Interface diff --git a/packages/classes/src/channels/behaviour/media.luau b/packages/classes/src/channels/behaviour/media.luau new file mode 100644 index 0000000..3491d1d --- /dev/null +++ b/packages/classes/src/channels/behaviour/media.luau @@ -0,0 +1,51 @@ +--[[ + Media Behaviour, responsible for implementing the various properties, and methods for a media thread channel. +]] + +local apiTypes = require("@api-types/apiTypes") +local channelTypes = require("@api-types/channel") + +local forumTag = require("@classes/channels/forumTag") +local defaultReaction = require("@classes/channels/defaultReaction") + +local MediaBehaviour = {} + +MediaBehaviour.Interface = {} +MediaBehaviour.Prototype = {} + +function MediaBehaviour.Interface.inheritProperties(class: any, channelData: apiTypes.ChannelObject) + local forumTagArray = {} + + -- fixme: need to use `next` iterator here to avoid LSP errors. + for _, mediaTagData in next, channelData.available_tags or {} do + table.insert(forumTagArray, forumTag.new(mediaTagData)) + end + + class.availableTags = forumTagArray + + class.defaultReaction = channelData.default_reaction_emoji + and defaultReaction.new(channelData.default_reaction_emoji) + + class.defaultForumLayout = channelData.default_forum_layout + class.appliedTags = channelData.applied_tags + + class.defaultSortOrder = channelTypes.MediaSortOrder[channelData.default_sort_order] +end + +function MediaBehaviour.Interface.inheritMethods(class: any) + for key, value in MediaBehaviour.Prototype do + class[key] = value + end +end + +export type MediaBehaviourMethods = typeof(MediaBehaviour.Prototype) +export type MediaBehaviourProperties = { + availableTags: { forumTag.ForumTag }, + defaultReaction: defaultReaction.DefaultReaction?, + appliedTags: { apiTypes.Snowflake }, + defaultForumLayout: channelTypes.MediaSortOrder, +} + +export type MediaBehaviour = MediaBehaviourMethods & MediaBehaviourProperties + +return MediaBehaviour.Interface diff --git a/packages/classes/src/channels/behaviour/text.luau b/packages/classes/src/channels/behaviour/text.luau new file mode 100644 index 0000000..ee70523 --- /dev/null +++ b/packages/classes/src/channels/behaviour/text.luau @@ -0,0 +1,35 @@ +--[[ + Text Behaviour, responsible for implementing the various properties, and methods for a text-able channels. +]] + +local datetime = require("@std-polyfills/datetime") + +local apiTypes = require("@api-types/apiTypes") + +local TextBehaviour = {} + +TextBehaviour.Interface = {} +TextBehaviour.Prototype = {} + +function TextBehaviour.Interface.inheritProperties(class: any, channelData: apiTypes.ChannelObject) + class.lastMessageId = channelData.last_message_id + class.lastPinTimestamp = channelData.last_pin_timestamp and datetime.fromIsoDate(channelData.last_pin_timestamp) + class.rateLimitPerUser = channelData.rate_limit_per_user +end + +function TextBehaviour.Interface.inheritMethods(class: any) + for key, value in TextBehaviour.Prototype do + class[key] = value + end +end + +export type TextBehaviourMethods = typeof(TextBehaviour.Prototype) +export type TextBehaviourProperties = { + lastMessageId: string?, + lastPinTimestamp: datetime.DateTime?, + rateLimitPerUser: number, +} + +export type TextBehaviour = TextBehaviourMethods & TextBehaviourProperties + +return TextBehaviour.Interface diff --git a/packages/classes/src/channels/behaviour/thread.luau b/packages/classes/src/channels/behaviour/thread.luau new file mode 100644 index 0000000..c26f012 --- /dev/null +++ b/packages/classes/src/channels/behaviour/thread.luau @@ -0,0 +1,44 @@ +--[[ + Thread Behaviour, responsible for implementing the various properties, and methods for a generic thread channels. +]] + +local apiTypes = require("@api-types/apiTypes") + +local threadMember = require("@classes/threadMember") +local threadMetadata = require("@classes/channels/threadMetadata") + +local ThreadBehaviour = {} + +ThreadBehaviour.Interface = {} +ThreadBehaviour.Prototype = {} + +function ThreadBehaviour.Interface.inheritProperties(class: any, channelData: apiTypes.ChannelObject) + class.totalMessageSent = channelData.total_message_sent + class.member = channelData.member and threadMember.new(channelData.member) + class.threadMetadata = channelData.thread_metadata and threadMetadata.new(channelData.thread_metadata) + class.memberCount = channelData.member_count + class.messageCount = channelData.message_count + class.parentId = channelData.parent_id + class.ownerId = channelData.owner_id +end + +function ThreadBehaviour.Interface.inheritMethods(class: any) + for key, value in ThreadBehaviour.Prototype do + class[key] = value + end +end + +export type ThreadBehaviourMethods = typeof(ThreadBehaviour.Prototype) +export type ThreadBehaviourProperties = { + totalMessageSent: number, + member: threadMember.ThreadMember?, + threadMetadata: threadMetadata.ThreadMetadata?, + memberCount: number, + messageCount: number, + parentId: string?, + ownerId: string, +} + +export type ThreadBehaviour = ThreadBehaviourMethods & ThreadBehaviourProperties + +return ThreadBehaviour.Interface diff --git a/packages/classes/src/channels/behaviour/voice.luau b/packages/classes/src/channels/behaviour/voice.luau new file mode 100644 index 0000000..4b32575 --- /dev/null +++ b/packages/classes/src/channels/behaviour/voice.luau @@ -0,0 +1,37 @@ +--[[ + Voice Behaviour, responsible for implementing the various properties, and methods for a voice-able channels. +]] + +local apiTypes = require("@api-types/apiTypes") +local channelTypes = require("@api-types/channel") + +local VoiceBehaviour = {} + +VoiceBehaviour.Interface = {} +VoiceBehaviour.Prototype = {} + +function VoiceBehaviour.Interface.inheritProperties(class: any, channelData: apiTypes.ChannelObject) + class.videoQualityMode = channelTypes.VideoQualityMode[channelData.video_quality_mode] + + class.bitrate = channelData.bitrate + class.userLimit = channelData.user_limit + class.rtcRegion = channelData.rtc_region +end + +function VoiceBehaviour.Interface.inheritMethods(class: any) + for key, value in VoiceBehaviour.Prototype do + class[key] = value + end +end + +export type VoiceBehaviourMethods = typeof(VoiceBehaviour.Prototype) +export type VoiceBehaviourProperties = { + bitrate: number, + userLimit: number, + rtcRegion: string?, + videoQualityMode: channelTypes.VideoQualityMode, +} + +export type VoiceBehaviour = VoiceBehaviourMethods & VoiceBehaviourProperties + +return VoiceBehaviour.Interface diff --git a/packages/classes/src/channels/defaultReaction.luau b/packages/classes/src/channels/defaultReaction.luau new file mode 100644 index 0000000..9f003cd --- /dev/null +++ b/packages/classes/src/channels/defaultReaction.luau @@ -0,0 +1,32 @@ +--[[ + Implementation of the Discord DefaultReaction class in Luau + + https://discord.com/developers/docs/resources/channel#default-reaction-object +]] + +local apiTypes = require("@api-types/apiTypes") + +local DefaultReaction = {} + +DefaultReaction.Interface = {} +DefaultReaction.Prototype = {} + +function DefaultReaction.Prototype.sync(self: DefaultReaction, DefaultReactionData: apiTypes.DefaultReactionObject) + self.emojiId = DefaultReactionData.emoji_id + self.emojiName = DefaultReactionData.emoji_name +end + +function DefaultReaction.Interface.new(DefaultReactionData: apiTypes.DefaultReactionObject): DefaultReaction + local self = setmetatable({} :: DefaultReaction, { __index = DefaultReaction.Prototype }) + + self:sync(DefaultReactionData) + + return self +end + +export type DefaultReaction = typeof(DefaultReaction.Prototype) & { + emojiId: string?, + emojiName: string?, +} + +return DefaultReaction.Interface diff --git a/packages/classes/src/channels/forumTag.luau b/packages/classes/src/channels/forumTag.luau new file mode 100644 index 0000000..41695ea --- /dev/null +++ b/packages/classes/src/channels/forumTag.luau @@ -0,0 +1,39 @@ +--[[ + Implementation of the Discord ForumTag class in Luau + + https://discord.com/developers/docs/resources/channel#forum-tag-object +]] + +local apiTypes = require("@api-types/apiTypes") + +local ForumTag = {} + +ForumTag.Interface = {} +ForumTag.Prototype = {} + +function ForumTag.Prototype.sync(self: ForumTag, forumTagData: apiTypes.ForumTagObject) + self.id = forumTagData.id + self.name = forumTagData.name + self.moderated = forumTagData.moderated + self.emojiId = forumTagData.emoji_id + self.emojiName = forumTagData.emoji_name +end + +function ForumTag.Interface.new(forumTagData: apiTypes.ForumTagObject): ForumTag + local self = setmetatable({} :: ForumTag, { __index = ForumTag.Prototype }) + + self:sync(forumTagData) + + return self +end + +export type ForumTagType = "role" | "member" +export type ForumTag = typeof(ForumTag.Prototype) & { + id: apiTypes.Snowflake, + name: string, + moderated: boolean, + emojiId: string?, + emojiName: string?, +} + +return ForumTag.Interface diff --git a/packages/classes/src/channels/mention.luau b/packages/classes/src/channels/mention.luau new file mode 100644 index 0000000..789fcc6 --- /dev/null +++ b/packages/classes/src/channels/mention.luau @@ -0,0 +1,38 @@ +--[[ + Implementation of the Discord ChannelMention class in Luau + + https://discord.com/developers/docs/resources/message#channel-mention-object +]] + +local apiTypes = require("@api-types/apiTypes") +local channelTypes = require("@api-types/channel") + +local ChannelMention = {} + +ChannelMention.Interface = {} +ChannelMention.Prototype = {} + +function ChannelMention.Prototype.sync(self: ChannelMention, mentionData: apiTypes.ChannelMentionObject) + self.type = channelTypes.ChannelTypes[mentionData.type] + + self.id = mentionData.id + self.name = mentionData.name + self.guildId = mentionData.guild_id +end + +function ChannelMention.Interface.new(mentionData: apiTypes.ChannelMentionObject): ChannelMention + local self = setmetatable({} :: ChannelMention, { __index = ChannelMention.Prototype }) + + self:sync(mentionData) + + return self +end + +export type ChannelMention = typeof(ChannelMention.Prototype) & { + id: apiTypes.Snowflake, + guildId: apiTypes.Snowflake, + type: channelTypes.ChannelType, + name: string, +} + +return ChannelMention.Interface diff --git a/packages/classes/src/channels/overwrite.luau b/packages/classes/src/channels/overwrite.luau new file mode 100644 index 0000000..f66a42a --- /dev/null +++ b/packages/classes/src/channels/overwrite.luau @@ -0,0 +1,38 @@ +--[[ + Implementation of the Discord Overwrite class in Luau + + https://discord.com/developers/docs/resources/channel#overwrite-object +]] + +local apiTypes = require("@api-types/apiTypes") + +local Overwrite = {} + +Overwrite.Interface = {} +Overwrite.Prototype = {} + +function Overwrite.Prototype.sync(self: Overwrite, overwriteData: apiTypes.OverwriteObject) + self.type = (overwriteData.type == 0 and "role" or "member") :: OverwriteType + + self.id = overwriteData.id + self.allow = overwriteData.allow + self.deny = overwriteData.deny +end + +function Overwrite.Interface.new(overwriteData: apiTypes.OverwriteObject): Overwrite + local self = setmetatable({} :: Overwrite, { __index = Overwrite.Prototype }) + + self:sync(overwriteData) + + return self +end + +export type OverwriteType = "role" | "member" +export type Overwrite = typeof(Overwrite.Prototype) & { + id: apiTypes.Snowflake, + type: OverwriteType, + allow: string, + deny: string, +} + +return Overwrite.Interface diff --git a/packages/classes/src/channels/threadMetadata.luau b/packages/classes/src/channels/threadMetadata.luau new file mode 100644 index 0000000..6063b26 --- /dev/null +++ b/packages/classes/src/channels/threadMetadata.luau @@ -0,0 +1,43 @@ +--[[ + Implementation of the Discord ThreadMetadata class in Luau + + https://discord.com/developers/docs/resources/channel#thread-metadata-object +]] + +local datetime = require("@std-polyfills/datetime") + +local apiTypes = require("@api-types/apiTypes") + +local ThreadMetadata = {} + +ThreadMetadata.Interface = {} +ThreadMetadata.Prototype = {} + +function ThreadMetadata.Prototype.sync(self: ThreadMetadata, threadMetadataData: apiTypes.ThreadMetadataObject) + self.archiveTimestamp = datetime.fromIsoDate(threadMetadataData.archive_timestamp) + self.createTimestamp = threadMetadataData.create_timestamp + and datetime.fromIsoDate(threadMetadataData.create_timestamp) + + self.archived = threadMetadataData.archived + self.autoArchiveDuration = threadMetadataData.auto_archive_duration + self.locked = threadMetadataData.locked + self.invitable = threadMetadataData.invitable +end + +function ThreadMetadata.Interface.new(threadMetadataData: apiTypes.ThreadMetadataObject): ThreadMetadata + local self = setmetatable({} :: ThreadMetadata, { __index = ThreadMetadata.Prototype }) + + self:sync(threadMetadataData) + + return self +end +export type ThreadMetadata = typeof(ThreadMetadata.Prototype) & { + archived: boolean, + autoArchiveDuration: number, + archiveTimestamp: datetime.DateTime, + locked: boolean, + invitable: boolean?, + createTimestamp: datetime.DateTime?, +} + +return ThreadMetadata.Interface diff --git a/packages/classes/src/channels/threads/announcement.luau b/packages/classes/src/channels/threads/announcement.luau new file mode 100644 index 0000000..bab822d --- /dev/null +++ b/packages/classes/src/channels/threads/announcement.luau @@ -0,0 +1,48 @@ +--[[ + Implementation of the Discord Announcement class in Luau + + https://discord.com/developers/docs/resources/channel#channel-object +]] + +local apiTypes = require("@api-types/apiTypes") + +local channelBehaviour = require("@classes/channels/behaviour/channel") +local guildBehaviour = require("@classes/channels/behaviour/guild") +local textBehaviour = require("@classes/channels/behaviour/text") +local threadBehaviour = require("@classes/channels/behaviour/thread") + +local Announcement = {} + +Announcement.Interface = {} +Announcement.Prototype = {} +Announcement.Behaviours = { + channelBehaviour, + guildBehaviour, + threadBehaviour, + textBehaviour, +} + +function Announcement.Prototype.sync(self: Announcement, channelData: apiTypes.ChannelObject) + for _, behaviour in Announcement.Behaviours do + behaviour.inheritMethods(self) + behaviour.inheritProperties(self, channelData) + end +end + +function Announcement.Interface.new(channelData: apiTypes.ChannelObject): Announcement + local self = setmetatable({} :: Announcement, { __index = Announcement.Prototype }) + + self:sync(channelData) + + return self +end + +export type Announcement = + typeof(Announcement.Prototype) + & {} + & channelBehaviour.ChannelBehaviour + & textBehaviour.TextBehaviour + & guildBehaviour.GuildBehaviour + & threadBehaviour.ThreadBehaviour + +return Announcement.Interface diff --git a/packages/classes/src/channels/threads/forum.luau b/packages/classes/src/channels/threads/forum.luau new file mode 100644 index 0000000..1b913f0 --- /dev/null +++ b/packages/classes/src/channels/threads/forum.luau @@ -0,0 +1,51 @@ +--[[ + Implementation of the Discord Forum class in Luau + + https://discord.com/developers/docs/resources/channel#channel-object +]] + +local apiTypes = require("@api-types/apiTypes") + +local channelBehaviour = require("@classes/channels/behaviour/channel") +local guildBehaviour = require("@classes/channels/behaviour/guild") +local textBehaviour = require("@classes/channels/behaviour/text") +local threadBehaviour = require("@classes/channels/behaviour/thread") +local forumBehaviour = require("@classes/channels/behaviour/forum") + +local Forum = {} + +Forum.Interface = {} +Forum.Prototype = {} +Forum.Behaviours = { + channelBehaviour, + guildBehaviour, + threadBehaviour, + forumBehaviour, + textBehaviour, +} + +function Forum.Prototype.sync(self: Forum, channelData: apiTypes.ChannelObject) + for _, behaviour in Forum.Behaviours do + behaviour.inheritMethods(self) + behaviour.inheritProperties(self, channelData) + end +end + +function Forum.Interface.new(channelData: apiTypes.ChannelObject): Forum + local self = setmetatable({} :: Forum, { __index = Forum.Prototype }) + + self:sync(channelData) + + return self +end + +export type Forum = + typeof(Forum.Prototype) + & {} + & channelBehaviour.ChannelBehaviour + & textBehaviour.TextBehaviour + & guildBehaviour.GuildBehaviour + & threadBehaviour.ThreadBehaviour + & forumBehaviour.ForumBehaviour + +return Forum.Interface diff --git a/packages/classes/src/channels/threads/media.luau b/packages/classes/src/channels/threads/media.luau new file mode 100644 index 0000000..5ffe363 --- /dev/null +++ b/packages/classes/src/channels/threads/media.luau @@ -0,0 +1,51 @@ +--[[ + Implementation of the Discord Media class in Luau + + https://discord.com/developers/docs/resources/channel#channel-object +]] + +local apiTypes = require("@api-types/apiTypes") + +local channelBehaviour = require("@classes/channels/behaviour/channel") +local guildBehaviour = require("@classes/channels/behaviour/guild") +local textBehaviour = require("@classes/channels/behaviour/text") +local threadBehaviour = require("@classes/channels/behaviour/thread") +local mediaBehaviour = require("@classes/channels/behaviour/media") + +local Media = {} + +Media.Interface = {} +Media.Prototype = {} +Media.Behaviours = { + channelBehaviour, + guildBehaviour, + threadBehaviour, + mediaBehaviour, + textBehaviour, +} + +function Media.Prototype.sync(self: Media, channelData: apiTypes.ChannelObject) + for _, behaviour in Media.Behaviours do + behaviour.inheritMethods(self) + behaviour.inheritProperties(self, channelData) + end +end + +function Media.Interface.new(channelData: apiTypes.ChannelObject): Media + local self = setmetatable({} :: Media, { __index = Media.Prototype }) + + self:sync(channelData) + + return self +end + +export type Media = + typeof(Media.Prototype) + & {} + & channelBehaviour.ChannelBehaviour + & textBehaviour.TextBehaviour + & guildBehaviour.GuildBehaviour + & threadBehaviour.ThreadBehaviour + & mediaBehaviour.MediaBehaviour + +return Media.Interface diff --git a/packages/classes/src/channels/threads/private.luau b/packages/classes/src/channels/threads/private.luau new file mode 100644 index 0000000..3ae252f --- /dev/null +++ b/packages/classes/src/channels/threads/private.luau @@ -0,0 +1,48 @@ +--[[ + Implementation of the Discord Private class in Luau + + https://discord.com/developers/docs/resources/channel#channel-object +]] + +local apiTypes = require("@api-types/apiTypes") + +local channelBehaviour = require("@classes/channels/behaviour/channel") +local guildBehaviour = require("@classes/channels/behaviour/guild") +local textBehaviour = require("@classes/channels/behaviour/text") +local threadBehaviour = require("@classes/channels/behaviour/thread") + +local Private = {} + +Private.Interface = {} +Private.Prototype = {} +Private.Behaviours = { + channelBehaviour, + guildBehaviour, + threadBehaviour, + textBehaviour, +} + +function Private.Prototype.sync(self: Private, channelData: apiTypes.ChannelObject) + for _, behaviour in Private.Behaviours do + behaviour.inheritMethods(self) + behaviour.inheritProperties(self, channelData) + end +end + +function Private.Interface.new(channelData: apiTypes.ChannelObject): Private + local self = setmetatable({} :: Private, { __index = Private.Prototype }) + + self:sync(channelData) + + return self +end + +export type Private = + typeof(Private.Prototype) + & {} + & channelBehaviour.ChannelBehaviour + & textBehaviour.TextBehaviour + & guildBehaviour.GuildBehaviour + & threadBehaviour.ThreadBehaviour + +return Private.Interface diff --git a/packages/classes/src/channels/threads/public.luau b/packages/classes/src/channels/threads/public.luau new file mode 100644 index 0000000..5e7c85c --- /dev/null +++ b/packages/classes/src/channels/threads/public.luau @@ -0,0 +1,48 @@ +--[[ + Implementation of the Discord Public class in Luau + + https://discord.com/developers/docs/resources/channel#channel-object +]] + +local apiTypes = require("@api-types/apiTypes") + +local channelBehaviour = require("@classes/channels/behaviour/channel") +local guildBehaviour = require("@classes/channels/behaviour/guild") +local textBehaviour = require("@classes/channels/behaviour/text") +local threadBehaviour = require("@classes/channels/behaviour/thread") + +local Public = {} + +Public.Interface = {} +Public.Prototype = {} +Public.Behaviours = { + channelBehaviour, + guildBehaviour, + threadBehaviour, + textBehaviour, +} + +function Public.Prototype.sync(self: Public, channelData: apiTypes.ChannelObject) + for _, behaviour in Public.Behaviours do + behaviour.inheritMethods(self) + behaviour.inheritProperties(self, channelData) + end +end + +function Public.Interface.new(channelData: apiTypes.ChannelObject): Public + local self = setmetatable({} :: Public, { __index = Public.Prototype }) + + self:sync(channelData) + + return self +end + +export type Public = + typeof(Public.Prototype) + & {} + & channelBehaviour.ChannelBehaviour + & textBehaviour.TextBehaviour + & guildBehaviour.GuildBehaviour + & threadBehaviour.ThreadBehaviour + +return Public.Interface diff --git a/packages/classes/src/channels/types/dm.luau b/packages/classes/src/channels/types/dm.luau new file mode 100644 index 0000000..92d2854 --- /dev/null +++ b/packages/classes/src/channels/types/dm.luau @@ -0,0 +1,38 @@ +--[[ + Implementation of the Discord DM class in Luau + + https://discord.com/developers/docs/resources/channel#channel-object +]] + +local apiTypes = require("@api-types/apiTypes") + +local channelBehaviour = require("@classes/channels/behaviour/channel") +local textBehaviour = require("@classes/channels/behaviour/text") + +local DM = {} + +DM.Interface = {} +DM.Prototype = {} +DM.Behaviours = { + channelBehaviour, + textBehaviour, +} + +function DM.Prototype.sync(self: DM, channelData: apiTypes.ChannelObject) + for _, behaviour in DM.Behaviours do + behaviour.inheritMethods(self) + behaviour.inheritProperties(self, channelData) + end +end + +function DM.Interface.new(channelData: apiTypes.ChannelObject): DM + local self = setmetatable({} :: DM, { __index = DM.Prototype }) + + self:sync(channelData) + + return self +end + +export type DM = typeof(DM.Prototype) & {} & channelBehaviour.ChannelBehaviour & textBehaviour.TextBehaviour + +return DM.Interface diff --git a/packages/classes/src/channels/types/groupDm.luau b/packages/classes/src/channels/types/groupDm.luau new file mode 100644 index 0000000..fc6a454 --- /dev/null +++ b/packages/classes/src/channels/types/groupDm.luau @@ -0,0 +1,45 @@ +--[[ + Implementation of the Discord GroupDM class in Luau + + https://discord.com/developers/docs/resources/channel#channel-object +]] + +local apiTypes = require("@api-types/apiTypes") + +local channelBehaviour = require("@classes/channels/behaviour/channel") +local groupDMBehaviours = require("@classes/channels/behaviour/groupDM") +local textBehaviour = require("@classes/channels/behaviour/text") + +local GroupDM = {} + +GroupDM.Interface = {} +GroupDM.Prototype = {} +GroupDM.Behaviours = { + channelBehaviour, + groupDMBehaviours, + textBehaviour, +} + +function GroupDM.Prototype.sync(self: GroupDM, channelData: apiTypes.ChannelObject) + for _, behaviour in GroupDM.Behaviours do + behaviour.inheritMethods(self) + behaviour.inheritProperties(self, channelData) + end +end + +function GroupDM.Interface.new(channelData: apiTypes.ChannelObject): GroupDM + local self = setmetatable({} :: GroupDM, { __index = GroupDM.Prototype }) + + self:sync(channelData) + + return self +end + +export type GroupDM = + typeof(GroupDM.Prototype) + & {} + & channelBehaviour.ChannelBehaviour + & groupDMBehaviours.DMBehaviour + & textBehaviour.TextBehaviour + +return GroupDM.Interface diff --git a/packages/classes/src/channels/types/guildAnnouncement.luau b/packages/classes/src/channels/types/guildAnnouncement.luau new file mode 100644 index 0000000..804d6e6 --- /dev/null +++ b/packages/classes/src/channels/types/guildAnnouncement.luau @@ -0,0 +1,45 @@ +--[[ + Implementation of the Discord GuildAnnouncement class in Luau + + https://discord.com/developers/docs/resources/channel#channel-object +]] + +local apiTypes = require("@api-types/apiTypes") + +local channelBehaviour = require("@classes/channels/behaviour/channel") +local guildBehaviour = require("@classes/channels/behaviour/guild") +local textBehaviour = require("@classes/channels/behaviour/text") + +local GuildAnnouncement = {} + +GuildAnnouncement.Interface = {} +GuildAnnouncement.Prototype = {} +GuildAnnouncement.Behaviours = { + channelBehaviour, + guildBehaviour, + textBehaviour, +} + +function GuildAnnouncement.Prototype.sync(self: GuildAnnouncement, channelData: apiTypes.ChannelObject) + for _, behaviour in GuildAnnouncement.Behaviours do + behaviour.inheritMethods(self) + behaviour.inheritProperties(self, channelData) + end +end + +function GuildAnnouncement.Interface.new(channelData: apiTypes.ChannelObject): GuildAnnouncement + local self = setmetatable({} :: GuildAnnouncement, { __index = GuildAnnouncement.Prototype }) + + self:sync(channelData) + + return self +end + +export type GuildAnnouncement = + typeof(GuildAnnouncement.Prototype) + & {} + & channelBehaviour.ChannelBehaviour + & textBehaviour.TextBehaviour + & guildBehaviour.GuildBehaviour + +return GuildAnnouncement.Interface diff --git a/packages/classes/src/channels/types/guildCategory.luau b/packages/classes/src/channels/types/guildCategory.luau new file mode 100644 index 0000000..b7f2d14 --- /dev/null +++ b/packages/classes/src/channels/types/guildCategory.luau @@ -0,0 +1,42 @@ +--[[ + Implementation of the Discord GuildCategory class in Luau + + https://discord.com/developers/docs/resources/channel#channel-object +]] + +local apiTypes = require("@api-types/apiTypes") + +local channelBehaviour = require("@classes/channels/behaviour/channel") +local guildBehaviour = require("@classes/channels/behaviour/guild") + +local GuildCategory = {} + +GuildCategory.Interface = {} +GuildCategory.Prototype = {} +GuildCategory.Behaviours = { + channelBehaviour, + guildBehaviour, +} + +function GuildCategory.Prototype.sync(self: GuildCategory, channelData: apiTypes.ChannelObject) + for _, behaviour in GuildCategory.Behaviours do + behaviour.inheritMethods(self) + behaviour.inheritProperties(self, channelData) + end +end + +function GuildCategory.Interface.new(channelData: apiTypes.ChannelObject): GuildCategory + local self = setmetatable({} :: GuildCategory, { __index = GuildCategory.Prototype }) + + self:sync(channelData) + + return self +end + +export type GuildCategory = + typeof(GuildCategory.Prototype) + & {} + & channelBehaviour.ChannelBehaviour + & guildBehaviour.GuildBehaviour + +return GuildCategory.Interface diff --git a/packages/classes/src/channels/types/guildDirectory.luau b/packages/classes/src/channels/types/guildDirectory.luau new file mode 100644 index 0000000..f756f72 --- /dev/null +++ b/packages/classes/src/channels/types/guildDirectory.luau @@ -0,0 +1,42 @@ +--[[ + Implementation of the Discord GuildDirectory class in Luau + + https://discord.com/developers/docs/resources/channel#channel-object +]] + +local apiTypes = require("@api-types/apiTypes") + +local channelBehaviour = require("@classes/channels/behaviour/channel") +local guildBehaviour = require("@classes/channels/behaviour/guild") + +local GuildDirectory = {} + +GuildDirectory.Interface = {} +GuildDirectory.Prototype = {} +GuildDirectory.Behaviours = { + channelBehaviour, + guildBehaviour, +} + +function GuildDirectory.Prototype.sync(self: GuildDirectory, channelData: apiTypes.ChannelObject) + for _, behaviour in GuildDirectory.Behaviours do + behaviour.inheritMethods(self) + behaviour.inheritProperties(self, channelData) + end +end + +function GuildDirectory.Interface.new(channelData: apiTypes.ChannelObject): GuildDirectory + local self = setmetatable({} :: GuildDirectory, { __index = GuildDirectory.Prototype }) + + self:sync(channelData) + + return self +end + +export type GuildDirectory = + typeof(GuildDirectory.Prototype) + & {} + & channelBehaviour.ChannelBehaviour + & guildBehaviour.GuildBehaviour + +return GuildDirectory.Interface diff --git a/packages/classes/src/channels/types/guildStageVoice.luau b/packages/classes/src/channels/types/guildStageVoice.luau new file mode 100644 index 0000000..b928a37 --- /dev/null +++ b/packages/classes/src/channels/types/guildStageVoice.luau @@ -0,0 +1,45 @@ +--[[ + Implementation of the Discord GuildStageVoice class in Luau + + https://discord.com/developers/docs/resources/channel#channel-object +]] + +local apiTypes = require("@api-types/apiTypes") + +local channelBehaviour = require("@classes/channels/behaviour/channel") +local guildBehaviour = require("@classes/channels/behaviour/guild") +local voiceBehaviour = require("@classes/channels/behaviour/voice") + +local GuildStageVoice = {} + +GuildStageVoice.Interface = {} +GuildStageVoice.Prototype = {} +GuildStageVoice.Behaviours = { + channelBehaviour, + guildBehaviour, + voiceBehaviour, +} + +function GuildStageVoice.Prototype.sync(self: GuildStageVoice, channelData: apiTypes.ChannelObject) + for _, behaviour in GuildStageVoice.Behaviours do + behaviour.inheritMethods(self) + behaviour.inheritProperties(self, channelData) + end +end + +function GuildStageVoice.Interface.new(ChannelData: apiTypes.ChannelObject): GuildStageVoice + local self = setmetatable({} :: GuildStageVoice, { __index = GuildStageVoice.Prototype }) + + self:sync(ChannelData) + + return self +end + +export type GuildStageVoice = + typeof(GuildStageVoice.Prototype) + & {} + & channelBehaviour.ChannelBehaviour + & voiceBehaviour.VoiceBehaviour + & guildBehaviour.GuildBehaviour + +return GuildStageVoice.Interface diff --git a/packages/classes/src/channels/types/guildText.luau b/packages/classes/src/channels/types/guildText.luau new file mode 100644 index 0000000..d8ed4e4 --- /dev/null +++ b/packages/classes/src/channels/types/guildText.luau @@ -0,0 +1,45 @@ +--[[ + Implementation of the Discord GuildText class in Luau + + https://discord.com/developers/docs/resources/channel#channel-object +]] + +local apiTypes = require("@api-types/apiTypes") + +local channelBehaviour = require("@classes/channels/behaviour/channel") +local guildBehaviour = require("@classes/channels/behaviour/guild") +local textBehaviour = require("@classes/channels/behaviour/text") + +local GuildText = {} + +GuildText.Interface = {} +GuildText.Prototype = {} +GuildText.Behaviours = { + channelBehaviour, + guildBehaviour, + textBehaviour, +} + +function GuildText.Prototype.sync(self: GuildText, channelData: apiTypes.ChannelObject) + for _, behaviour in GuildText.Behaviours do + behaviour.inheritMethods(self) + behaviour.inheritProperties(self, channelData) + end +end + +function GuildText.Interface.new(channelData: apiTypes.ChannelObject): GuildText + local self = setmetatable({} :: GuildText, { __index = GuildText.Prototype }) + + self:sync(channelData) + + return self +end + +export type GuildText = + typeof(GuildText.Prototype) + & {} + & channelBehaviour.ChannelBehaviour + & textBehaviour.TextBehaviour + & guildBehaviour.GuildBehaviour + +return GuildText.Interface diff --git a/packages/classes/src/channels/types/guildVoice.luau b/packages/classes/src/channels/types/guildVoice.luau new file mode 100644 index 0000000..f66e083 --- /dev/null +++ b/packages/classes/src/channels/types/guildVoice.luau @@ -0,0 +1,45 @@ +--[[ + Implementation of the Discord GuildVoice class in Luau + + https://discord.com/developers/docs/resources/channel#channel-object +]] + +local apiTypes = require("@api-types/apiTypes") + +local channelBehaviour = require("@classes/channels/behaviour/channel") +local guildBehaviour = require("@classes/channels/behaviour/guild") +local voiceBehaviour = require("@classes/channels/behaviour/voice") + +local GuildVoice = {} + +GuildVoice.Interface = {} +GuildVoice.Prototype = {} +GuildVoice.Behaviours = { + channelBehaviour, + guildBehaviour, + voiceBehaviour, +} + +function GuildVoice.Prototype.sync(self: GuildVoice, channelData: apiTypes.ChannelObject) + for _, behaviour in GuildVoice.Behaviours do + behaviour.inheritMethods(self) + behaviour.inheritProperties(self, channelData) + end +end + +function GuildVoice.Interface.new(channelData: apiTypes.ChannelObject): GuildVoice + local self = setmetatable({} :: GuildVoice, { __index = GuildVoice.Prototype }) + + self:sync(channelData) + + return self +end + +export type GuildVoice = + typeof(GuildVoice.Prototype) + & {} + & channelBehaviour.ChannelBehaviour + & voiceBehaviour.VoiceBehaviour + & guildBehaviour.GuildBehaviour + +return GuildVoice.Interface diff --git a/packages/classes/src/embed/author.luau b/packages/classes/src/embed/author.luau new file mode 100644 index 0000000..fccc6a7 --- /dev/null +++ b/packages/classes/src/embed/author.luau @@ -0,0 +1,36 @@ +--[[ + Implementation of the Discord Author class in Luau + + https://discord.com/developers/docs/resources/message#embed-object-embed-author-structure +]] + +local apiTypes = require("@api-types/apiTypes") + +local Author = {} + +Author.Interface = {} +Author.Prototype = {} + +function Author.Prototype.sync(self: Author, authorData: apiTypes.EmbedAuthorObject) + self.name = authorData.name + self.url = authorData.url + self.iconUrl = authorData.icon_url + self.proxyIconUrl = authorData.proxy_icon_url +end + +function Author.Interface.new(authorData: apiTypes.EmbedAuthorObject): Author + local self = setmetatable({} :: Author, { __index = Author.Prototype }) + + self:sync(authorData) + + return self +end + +export type Author = typeof(Author.Prototype) & { + name: string, + url: string?, + iconUrl: string?, + proxyIconUrl: string?, +} + +return Author.Interface diff --git a/packages/classes/src/embed/embed.luau b/packages/classes/src/embed/embed.luau new file mode 100644 index 0000000..6f0fb8d --- /dev/null +++ b/packages/classes/src/embed/embed.luau @@ -0,0 +1,72 @@ +--[[ + Implementation of the Discord Embed class in Luau + + https://discord.com/developers/docs/resources/message#attachment-object +]] + +local datetime = require("@std-polyfills/datetime") + +local apiTypes = require("@api-types/apiTypes") +local embedTypes = require("@api-types/embed") + +local author = require("@classes/embed/author") +local footer = require("@classes/embed/footer") +local image = require("@classes/embed/image") +local thumbnail = require("@classes/embed/thumbnail") +local video = require("@classes/embed/video") +local provider = require("@classes/embed/provider") +local field = require("@classes/embed/field") + +local Embed = {} + +Embed.Interface = {} +Embed.Prototype = {} + +function Embed.Prototype.sync(self: Embed, embedData: apiTypes.EmbedObject) + local fieldArray = {} + + for _, fieldData in next, embedData.fields or {} do + table.insert(fieldArray, field.new(fieldData)) + end + + self.fields = fieldArray + + self.title = embedData.title + self.type = embedData.type :: any + self.description = embedData.description + self.url = embedData.url + self.timestamp = embedData.timestamp and datetime.fromIsoDate(embedData.timestamp) + self.color = embedData.color + self.footer = embedData.footer and footer.new(embedData.footer) + self.image = embedData.image and image.new(embedData.image) + self.thumbnail = embedData.thumbnail and thumbnail.new(embedData.thumbnail) + self.video = embedData.video and video.new(embedData.video) + self.provider = embedData.provider and provider.new(embedData.provider) + self.author = embedData.author and author.new(embedData.author) +end + +function Embed.Interface.new(embedData: apiTypes.EmbedObject): Embed + local self = setmetatable({} :: Embed, { __index = Embed.Prototype }) + + self:sync(embedData) + + return self +end + +export type Embed = typeof(Embed.Prototype) & { + title: string?, + type: embedTypes.EmbedType?, + description: string?, + url: string?, + timestamp: datetime.DateTime?, + color: number?, + footer: footer.Footer?, + image: image.Image?, + thumbnail: thumbnail.Thumbnail?, + video: video.Video?, + provider: provider.Provider?, + author: author.Author?, + fields: { field.Field }?, +} + +return Embed.Interface diff --git a/packages/classes/src/embed/field.luau b/packages/classes/src/embed/field.luau new file mode 100644 index 0000000..5089568 --- /dev/null +++ b/packages/classes/src/embed/field.luau @@ -0,0 +1,34 @@ +--[[ + Implementation of the Discord Field class in Luau + + https://discord.com/developers/docs/resources/message#embed-object-embed-field-structure +]] + +local apiTypes = require("@api-types/apiTypes") + +local Field = {} + +Field.Interface = {} +Field.Prototype = {} + +function Field.Prototype.sync(self: Field, fieldData: apiTypes.EmbedFieldObject) + self.name = fieldData.name + self.value = fieldData.value + self.inline = fieldData.inline +end + +function Field.Interface.new(fieldData: apiTypes.EmbedFieldObject): Field + local self = setmetatable({} :: Field, { __index = Field.Prototype }) + + self:sync(fieldData) + + return self +end + +export type Field = typeof(Field.Prototype) & { + name: string, + value: string, + inline: boolean?, +} + +return Field.Interface diff --git a/packages/classes/src/embed/footer.luau b/packages/classes/src/embed/footer.luau new file mode 100644 index 0000000..47ac116 --- /dev/null +++ b/packages/classes/src/embed/footer.luau @@ -0,0 +1,34 @@ +--[[ + Implementation of the Discord Footer class in Luau + + https://discord.com/developers/docs/resources/message#embed-object-embed-footer-structure +]] + +local apiTypes = require("@api-types/apiTypes") + +local Footer = {} + +Footer.Interface = {} +Footer.Prototype = {} + +function Footer.Prototype.sync(self: Footer, footerData: apiTypes.EmbedFooterObject) + self.text = footerData.text + self.iconUrl = footerData.icon_url + self.proxyIconUrl = footerData.proxy_icon_url +end + +function Footer.Interface.new(footerData: apiTypes.EmbedFooterObject): Footer + local self = setmetatable({} :: Footer, { __index = Footer.Prototype }) + + self:sync(footerData) + + return self +end + +export type Footer = typeof(Footer.Prototype) & { + text: string, + iconUrl: string?, + proxyIconUrl: string?, +} + +return Footer.Interface diff --git a/packages/classes/src/embed/image.luau b/packages/classes/src/embed/image.luau new file mode 100644 index 0000000..a988859 --- /dev/null +++ b/packages/classes/src/embed/image.luau @@ -0,0 +1,36 @@ +--[[ + Implementation of the Discord Image class in Luau + + https://discord.com/developers/docs/resources/message#embed-object-embed-image-structure +]] + +local apiTypes = require("@api-types/apiTypes") + +local Image = {} + +Image.Interface = {} +Image.Prototype = {} + +function Image.Prototype.sync(self: Image, imageData: apiTypes.EmbedImageObject) + self.url = imageData.url + self.proxyUrl = imageData.proxy_url + self.height = imageData.height + self.width = imageData.width +end + +function Image.Interface.new(imageData: apiTypes.EmbedImageObject): Image + local self = setmetatable({} :: Image, { __index = Image.Prototype }) + + self:sync(imageData) + + return self +end + +export type Image = typeof(Image.Prototype) & { + url: string, + proxyUrl: string?, + height: number?, + width: number?, +} + +return Image.Interface diff --git a/packages/classes/src/embed/provider.luau b/packages/classes/src/embed/provider.luau new file mode 100644 index 0000000..4a7dc84 --- /dev/null +++ b/packages/classes/src/embed/provider.luau @@ -0,0 +1,32 @@ +--[[ + Implementation of the Discord Provider class in Luau + + https://discord.com/developers/docs/resources/message#embed-object-embed-provider-structure +]] + +local apiTypes = require("@api-types/apiTypes") + +local Provider = {} + +Provider.Interface = {} +Provider.Prototype = {} + +function Provider.Prototype.sync(self: Provider, providerData: apiTypes.EmbedProviderObject) + self.name = providerData.name + self.url = providerData.url +end + +function Provider.Interface.new(providerData: apiTypes.EmbedProviderObject): Provider + local self = setmetatable({} :: Provider, { __index = Provider.Prototype }) + + self:sync(providerData) + + return self +end + +export type Provider = typeof(Provider.Prototype) & { + name: string?, + url: string?, +} + +return Provider.Interface diff --git a/packages/classes/src/embed/thumbnail.luau b/packages/classes/src/embed/thumbnail.luau new file mode 100644 index 0000000..c907d73 --- /dev/null +++ b/packages/classes/src/embed/thumbnail.luau @@ -0,0 +1,36 @@ +--[[ + Implementation of the Discord Thumbnail class in Luau + + https://discord.com/developers/docs/resources/message#embed-object-embed-thumbnail-structure +]] + +local apiTypes = require("@api-types/apiTypes") + +local Thumbnail = {} + +Thumbnail.Interface = {} +Thumbnail.Prototype = {} + +function Thumbnail.Prototype.sync(self: Thumbnail, thumbnailData: apiTypes.EmbedThumbnailObject) + self.url = thumbnailData.url + self.proxyUrl = thumbnailData.proxy_url + self.height = thumbnailData.height + self.width = thumbnailData.width +end + +function Thumbnail.Interface.new(thumbnailData: apiTypes.EmbedThumbnailObject): Thumbnail + local self = setmetatable({} :: Thumbnail, { __index = Thumbnail.Prototype }) + + self:sync(thumbnailData) + + return self +end + +export type Thumbnail = typeof(Thumbnail.Prototype) & { + url: string, + proxyUrl: string?, + height: number?, + width: number?, +} + +return Thumbnail.Interface diff --git a/packages/classes/src/embed/video.luau b/packages/classes/src/embed/video.luau new file mode 100644 index 0000000..0815873 --- /dev/null +++ b/packages/classes/src/embed/video.luau @@ -0,0 +1,36 @@ +--[[ + Implementation of the Discord Video class in Luau + + https://discord.com/developers/docs/resources/message#embed-object-embed-video-structure +]] + +local apiTypes = require("@api-types/apiTypes") + +local Video = {} + +Video.Interface = {} +Video.Prototype = {} + +function Video.Prototype.sync(self: Video, videoData: apiTypes.EmbedVideoObject) + self.url = videoData.url + self.proxyUrl = videoData.proxy_url + self.height = videoData.height + self.width = videoData.width +end + +function Video.Interface.new(videoData: apiTypes.EmbedVideoObject): Video + local self = setmetatable({} :: Video, { __index = Video.Prototype }) + + self:sync(videoData) + + return self +end + +export type Video = typeof(Video.Prototype) & { + url: string, + proxyUrl: string?, + height: number?, + width: number?, +} + +return Video.Interface diff --git a/packages/classes/src/emoji.luau b/packages/classes/src/emoji.luau new file mode 100644 index 0000000..0b80d16 --- /dev/null +++ b/packages/classes/src/emoji.luau @@ -0,0 +1,47 @@ +--[[ + Implementation of the Discord Emoji class in Luau + + https://discord.com/developers/docs/resources/emoji#emoji-resource +]] + +local apiTypes = require("@api-types/apiTypes") + +local user = require("@classes/user") + +local Emoji = {} + +Emoji.Interface = {} +Emoji.Prototype = {} + +function Emoji.Prototype.sync(self: Emoji, emojiData: apiTypes.EmojiObject) + self.roles = emojiData.roles + + self.id = emojiData.id + self.name = emojiData.name + self.user = emojiData.user and user.new(emojiData.user) + self.requireColons = emojiData.require_colons + self.managed = emojiData.managed + self.animated = emojiData.animated + self.available = emojiData.available +end + +function Emoji.Interface.new(emojiData: apiTypes.EmojiObject): Emoji + local self = setmetatable({} :: Emoji, { __index = Emoji.Prototype }) + + self:sync(emojiData) + + return self +end + +export type Emoji = typeof(Emoji.Prototype) & { + id: apiTypes.Snowflake?, + roles: { apiTypes.Snowflake }?, + name: string?, + user: user.User?, + requireColons: boolean?, + managed: boolean?, + animated: boolean?, + available: boolean?, +} + +return Emoji.Interface diff --git a/packages/classes/src/entitlement.luau b/packages/classes/src/entitlement.luau new file mode 100644 index 0000000..7190c34 --- /dev/null +++ b/packages/classes/src/entitlement.luau @@ -0,0 +1,52 @@ +--[[ + Implementation of the Discord Entitlement class in Luau + + https://discord.com/developers/docs/monetization/entitlements#entitlement-object +]] + +local datetime = require("@std-polyfills/datetime") + +local apiTypes = require("@api-types/apiTypes") +local entitlementTypes = require("@api-types/entitlement") + +local Entitlement = {} + +Entitlement.Interface = {} +Entitlement.Prototype = {} + +function Entitlement.Prototype.sync(self: Entitlement, entitlementData: apiTypes.EntitlementObject) + self.type = entitlementTypes.EntitlementType[entitlementData.type] + + self.id = entitlementData.id + self.skuId = entitlementData.sku_id + self.applicationId = entitlementData.application_id + self.userId = entitlementData.user_id + self.deleted = entitlementData.deleted + self.startsAt = entitlementData.starts_at and datetime.fromIsoDate(entitlementData.starts_at) + self.endsAt = entitlementData.ends_at and datetime.fromIsoDate(entitlementData.ends_at) + self.guildId = entitlementData.guild_id + self.consumed = entitlementData.consumed +end + +function Entitlement.Interface.new(entitlementData: apiTypes.EntitlementObject): Entitlement + local self = setmetatable({} :: Entitlement, { __index = Entitlement.Prototype }) + + self:sync(entitlementData) + + return self +end + +export type Entitlement = typeof(Entitlement.Prototype) & { + id: apiTypes.Snowflake, + skuId: apiTypes.Snowflake, + applicationId: apiTypes.Snowflake, + userId: apiTypes.Snowflake?, + type: entitlementTypes.EntitlementType, + deleted: boolean, + startsAt: datetime.DateTime?, + endsAt: datetime.DateTime?, + guildId: apiTypes.Snowflake?, + consumed: boolean?, +} + +return Entitlement.Interface diff --git a/packages/classes/src/guild/guild.luau b/packages/classes/src/guild/guild.luau new file mode 100644 index 0000000..e6eed30 --- /dev/null +++ b/packages/classes/src/guild/guild.luau @@ -0,0 +1,276 @@ +--[[ + Implementation of the Discord Guild class in Luau + + https://discord.com/developers/docs/resources/guild#guild-object +]] + +local future = require("@vendor/future") + +local apiTypes = require("@api-types/apiTypes") +local guildTypes = require("@api-types/guild") + +local state = require("@classes/state") +local emoji = require("@classes/emoji") +local sticker = require("@classes/sticker") +local role = require("@classes/guild/role") +local welcomeScreen = require("@classes/guild/welcomeScreen") + +local systemChannelBitflag = require("@classes/bitflags/systemChannel") + +local commandsRest = require("@rest/commands") +local interactionBuilder = require("@builders/interaction/interaction") + +local commandObject = require("@classes/application/command/command") + +local Guild = {} + +Guild.Interface = {} +Guild.Prototype = {} + +--[[ + Creates a guild Slash command for the application +]] +function Guild.Prototype.createSlashCommandAsync( + self: Guild, + slashCommand: interactionBuilder.JSON +): future.Future + return future.new(function() + local request = self.state.rest:newRequest() + + local status, response = commandsRest + .createGuildApplicationCommandAsync(request, self.state.applicationId, self.id, slashCommand) + :await() + + assert(status == "Fulfilled", tostring(response)) + + return commandObject.new(response) + end) +end + +--[[ + Delete an existing guild application command. +]] +function Guild.Prototype.deleteSlashCommandAsync(self: Guild, slashCommandId: apiTypes.Snowflake): future.Future + return future.new(function() + local request = self.state.rest:newRequest() + + local status, response = commandsRest + .deleteGuildApplicationCommandAsync(request, self.state.applicationId, self.id, slashCommandId) + :await() + + assert(status == "Fulfilled", tostring(response)) + end) +end + +--[[ + Edit an existing guild application command. +]] +function Guild.Prototype.editSlashCommandAsync( + self: Guild, + slashCommandId: apiTypes.Snowflake, + slashCommand: interactionBuilder.JSON +): future.Future + return future.new(function() + local request = self.state.rest:newRequest() + + local status, response = commandsRest + .editGuildApplicationCommandAsync(request, self.state.applicationId, self.id, slashCommandId, slashCommand) + :await() + + assert(status == "Fulfilled", tostring(response)) + + return commandObject.new(response) + end) +end + +--[[ + Get a list of guild application commands. +]] +function Guild.Prototype.getSlashCommandsAsync( + self: Guild, + withLocalizations: boolean? +): future.Future<{ commandObject.Command }> + return future.new(function() + local request = self.state.rest:newRequest() + + local status, response = commandsRest + .getGuildApplicationCommandsAsync(request, self.state.applicationId, self.id, { + withLocalizations = withLocalizations, + }) + :await() + + assert(status == "Fulfilled", tostring(response)) + + local commands = {} + + for _, commandData in response do + table.insert(commands, commandObject.new(commandData)) + end + + return commands + end) +end + +--[[ + Overwrite all guild application commands. This will skip over commands that are the same as the current commands, and will not remove any existing commands. +]] +function Guild.Prototype.overwriteSlashCommandsAsync( + self: Guild, + slashCommands: { interactionBuilder.JSON } +): future.Future<{ commandObject.Command }> + return future.new(function() + local request = self.state.rest:newRequest() + + -- fixme: casted this as any, the command object itself has extra fields as required by the other APIs + local status, response = commandsRest + .bulkOverwriteGuildApplicationCommandsAsync(request, self.state.applicationId, self.id, slashCommands :: any) + :await() + + assert(status == "Fulfilled", tostring(response)) + + local commands = {} + + for _, commandData in response do + table.insert(commands, commandObject.new(commandData)) + end + + return commands + end) +end + +function Guild.Prototype.sync(self: Guild, guildData: apiTypes.GuildObject) + local emojiArray = {} + local rolesArray = {} + local stickerArray = {} + + for _, emojiData in next, guildData.emojis or {} do + table.insert(emojiArray, emoji.new(emojiData)) + end + + for _, roleData in next, guildData.roles or {} do + table.insert(rolesArray, role.new(roleData)) + end + + for _, stickerData in next, guildData.stickers or {} do + table.insert(stickerArray, sticker.new(stickerData)) + end + + if guildData.welcome_screen then + self.welcomeScreen = welcomeScreen.new(guildData.welcome_screen) + end + + self.stickers = stickerArray + self.roles = rolesArray + self.emojis = emojiArray + + self.verificationLevel = guildTypes.VerificationLevel[guildData.verification_level] + self.defaultMessageNotifications = guildTypes.DefaultMessageNotification[guildData.default_message_notifications] + self.explicitContentFilter = guildTypes.ExplicitContentFilterLevel[guildData.explicit_content_filter] + self.premiumTier = guildTypes.PremiumTier[guildData.premium_tier] + self.nsfwLevel = guildTypes.NSFWLevel[guildData.nsfw_level] + self.mfaLevel = guildTypes.MFALevel[guildData.mfa_level] + + self.systemChannelFlags = guildData.system_channel_flags + and systemChannelBitflag.new(guildData.system_channel_flags) + + self.icon = guildData.icon + self.iconHash = guildData.icon_hash + self.name = guildData.name + self.splash = guildData.splash + self.discoverySplash = guildData.discovery_splash + self.owner = guildData.owner + self.ownerId = guildData.owner_id + self.permissions = guildData.permissions + self.region = guildData.region + self.afkChannelId = guildData.afk_channel_id + self.afkTimeout = guildData.afk_timeout + self.widgetEnabled = guildData.widget_enabled + self.widgetChannelId = guildData.widget_channel_id + self.features = guildData.features + self.applicationId = guildData.application_id + self.systemChannelId = guildData.system_channel_id + self.rulesChannelId = guildData.rules_channel_id + self.maxPresences = guildData.max_presences + self.maxMembers = guildData.max_members + self.vanityUrlCode = guildData.vanity_url_code + self.description = guildData.description + self.banner = guildData.banner + self.premiumSubscriptionCount = guildData.premium_subscription_count + self.preferredLocale = guildData.preferred_locale + self.publicUpdatesChannelId = guildData.public_updates_channel_id + self.maxVideoChannelUsers = guildData.max_video_channel_users + self.maxStageVideoChannelUsers = guildData.max_stage_video_channel_users + self.approximateMemberCount = guildData.approximate_member_count + self.approximatePresenceCount = guildData.approximate_presence_count + self.premiumProgressBarEnabled = guildData.premium_progress_bar_enabled + self.safetyAlertsChannelId = guildData.safety_alerts_channel_id +end + +function Guild.Interface.new(state: state.State, guildData: apiTypes.GuildObject): Guild + local self = setmetatable( + { + state = state, + + id = guildData.id, + isUnavailable = false, + } :: Guild, + { __index = Guild.Prototype } + ) + + self:sync(guildData) + + return self +end + +export type Guild = typeof(Guild.Prototype) & { + state: state.State, + + id: apiTypes.Snowflake, + isUnavailable: boolean, + + stickers: { sticker.Sticker }, + emojis: { emoji.Emoji }, + roles: { role.Role }, + welcomeScreen: welcomeScreen.WelcomeScreen?, + + icon: string?, + iconHash: string?, + name: string?, + splash: string?, + discoverySplash: string?, + owner: boolean?, + ownerId: apiTypes.Snowflake?, + permissions: string?, + region: string?, + afkChannelId: apiTypes.Snowflake?, + afkTimeout: number?, + widgetEnabled: boolean?, + widgetChannelId: apiTypes.Snowflake?, + verificationLevel: guildTypes.VerificationLevel, + defaultMessageNotifications: guildTypes.DefaultMessageNotification, + explicitContentFilter: guildTypes.ExplicitContentFilterLevel, + features: { apiTypes.GuildFeature }?, + mfaLevel: guildTypes.MFALevel?, + applicationId: apiTypes.Snowflake?, + systemChannelId: apiTypes.Snowflake?, + systemChannelFlags: systemChannelBitflag.SystemChannelBitflag?, + rulesChannelId: apiTypes.Snowflake?, + maxPresences: number?, + maxMembers: number?, + vanityUrlCode: string?, + description: string?, + banner: string?, + premiumTier: guildTypes.PremiumTier, + premiumSubscriptionCount: number?, + preferredLocale: string?, + publicUpdatesChannelId: apiTypes.Snowflake?, + maxVideoChannelUsers: number?, + maxStageVideoChannelUsers: number?, + approximateMemberCount: number?, + approximatePresenceCount: number?, + nsfwLevel: guildTypes.NSFWLevel, + premiumProgressBarEnabled: boolean?, + safetyAlertsChannelId: apiTypes.Snowflake?, +} + +return Guild.Interface diff --git a/packages/classes/src/guild/member.luau b/packages/classes/src/guild/member.luau new file mode 100644 index 0000000..b136e37 --- /dev/null +++ b/packages/classes/src/guild/member.luau @@ -0,0 +1,71 @@ +--[[ + Implementation of the Discord Member class in Luau + + https://discord.com/developers/docs/resources/guild#guild-member-object +]] + +local datetime = require("@std-polyfills/datetime") + +local apiTypes = require("@api-types/apiTypes") + +local guildMemberBitflag = require("@classes/bitflags/guildMember") + +local user = require("@classes/user") +local avatarDecoration = require("@classes/avatarDecoration") + +local Member = {} + +Member.Interface = {} +Member.Prototype = {} + +function Member.Prototype.sync(self: Member, guildMemberData: apiTypes.GuildMemberObject) + self.user = guildMemberData.user and user.new(guildMemberData.user) + + self.communicationDisabledUntil = guildMemberData.communication_disabled_until + and datetime.fromIsoDate(guildMemberData.communication_disabled_until) + + self.avatarDecorationData = guildMemberData.avatar_decoration_data + and avatarDecoration.new( + guildMemberData.avatar_decoration_data.asset, + guildMemberData.avatar_decoration_data.sku_id + ) + + self.flags = guildMemberData.flags and guildMemberBitflag.new(guildMemberData.flags) + + self.joinedAt = datetime.fromIsoDate(guildMemberData.joined_at) + self.premiumSince = guildMemberData.premium_since and datetime.fromIsoDate(guildMemberData.premium_since) + + self.nick = guildMemberData.nick + self.avatar = guildMemberData.avatar + self.roles = guildMemberData.roles + self.deaf = guildMemberData.deaf + self.mute = guildMemberData.mute + self.pending = guildMemberData.pending + self.permissions = guildMemberData.permissions +end + +function Member.Interface.new(guildMemberData: apiTypes.GuildMemberObject): Member + local self = setmetatable({} :: Member, { __index = Member.Prototype }) + + self:sync(guildMemberData) + + return self +end + +export type Member = typeof(Member.Prototype) & { + user: user.User?, + nick: string?, + avatar: string?, + roles: { apiTypes.Snowflake }, + joinedAt: datetime.DateTime, + premiumSince: datetime.DateTime?, + deaf: boolean, + mute: boolean, + flags: guildMemberBitflag.GuildMemberBitflag, + pending: boolean?, + permissions: string?, + communicationDisabledUntil: datetime.DateTime?, + avatarDecorationData: avatarDecoration.AvatarDecoration?, +} + +return Member.Interface diff --git a/packages/classes/src/guild/role.luau b/packages/classes/src/guild/role.luau new file mode 100644 index 0000000..24c27d7 --- /dev/null +++ b/packages/classes/src/guild/role.luau @@ -0,0 +1,63 @@ +--[[ + Implementation of the Discord Role class in Luau + + https://discord.com/developers/docs/topics/permissions#role-object +]] + +local apiTypes = require("@api-types/apiTypes") + +local roleTags = require("@classes/guild/roleTag") +local roleBitflag = require("@classes/bitflags/role") + +local Role = {} + +Role.Interface = {} +Role.Prototype = {} + +function Role.Prototype.sync(self: Role, roleData: apiTypes.GuildRoleObject) + local tagArray = {} + + -- fixme: need to use `next` iterator here to avoid LSP errors. + for _, tagData in next, roleData.tags or {} do + table.insert(tagArray, roleTags.new(tagData)) + end + + self.flags = roleBitflag.new(roleData.flags) + + self.tags = tagArray + self.id = roleData.id + self.name = roleData.name + self.color = roleData.color + self.hoist = roleData.hoist + self.icon = roleData.icon + self.unicodeEmoji = roleData.unicode_emoji + self.position = roleData.position + self.permissions = roleData.permissions + self.managed = roleData.managed + self.mentionable = roleData.mentionable +end + +function Role.Interface.new(roleData: apiTypes.GuildRoleObject): Role + local self = setmetatable({} :: Role, { __index = Role.Prototype }) + + self:sync(roleData) + + return self +end + +export type Role = typeof(Role.Prototype) & { + id: apiTypes.Snowflake?, + name: string, + color: number, + hoist: boolean, + icon: string?, + unicodeEmoji: string?, + position: number, + permissions: string, + managed: boolean, + mentionable: boolean, + tags: { roleTags.RoleTag }?, + flags: roleBitflag.RoleBitflag, +} + +return Role.Interface diff --git a/packages/classes/src/guild/roleTag.luau b/packages/classes/src/guild/roleTag.luau new file mode 100644 index 0000000..368b992 --- /dev/null +++ b/packages/classes/src/guild/roleTag.luau @@ -0,0 +1,40 @@ +--[[ + Implementation of the Discord RoleTag class in Luau + + https://discord.com/developers/docs/topics/permissions#role-object-role-tags-structure +]] + +local apiTypes = require("@api-types/apiTypes") + +local RoleTag = {} + +RoleTag.Interface = {} +RoleTag.Prototype = {} + +function RoleTag.Prototype.sync(self: RoleTag, roleTagData: apiTypes.GuildRoleTagObject) + self.botId = roleTagData.bot_id + self.integrationId = roleTagData.integration_id + self.premiumSubscriber = roleTagData.premium_subscriber + self.subscriptionListingId = roleTagData.subscription_listing_id + self.availableForPurchase = roleTagData.available_for_purchase + self.guildConnections = roleTagData.guild_connections +end + +function RoleTag.Interface.new(roleTagData: apiTypes.GuildRoleTagObject): RoleTag + local self = setmetatable({} :: RoleTag, { __index = RoleTag.Prototype }) + + self:sync(roleTagData) + + return self +end + +export type RoleTag = typeof(RoleTag.Prototype) & { + botId: apiTypes.Snowflake?, + integrationId: apiTypes.Snowflake?, + premiumSubscriber: nil?, + subscriptionListingId: apiTypes.Snowflake?, + availableForPurchase: nil?, + guildConnections: nil?, +} + +return RoleTag.Interface diff --git a/packages/classes/src/guild/unavailableGuild.luau b/packages/classes/src/guild/unavailableGuild.luau new file mode 100644 index 0000000..02a8ba6 --- /dev/null +++ b/packages/classes/src/guild/unavailableGuild.luau @@ -0,0 +1,71 @@ +--[[ + Implementation of the Discord Unavailable Guild class in Luau + + https://discord.com/developers/docs/resources/guild#unavailable-guild-object +]] + +local future = require("@vendor/future") + +local restGuild = require("@rest/guild") +local classGuild = require("@classes/guild/guild") + +local apiTypes = require("@api-types/apiTypes") + +local state = require("@classes/state") + +local UnavailableGuild = {} + +UnavailableGuild.Interface = {} +UnavailableGuild.Prototype = {} + +--[[ + Asynchronously retrieves the full Guild object for this UnavailableGuild. +]] +function UnavailableGuild.Prototype.getAsync( + self: UnavailableGuild, + withCounts: boolean? +): future.Future + return future.new(function() + local guildData = self.state.cache.guilds:get(self.id) + + if guildData then + return classGuild.new(self.state, guildData) + end + + local request = self.state.rest:newRequest() + local status, response = restGuild + .getGuildAsync(request, self.id, { + withCounts = withCounts, + }) + :await() + + assert(status == "Fulfilled", tostring(response)) + + self.state.cache.guilds:set(self.id, response) + + return classGuild.new(self.state, response) + end) +end + +function UnavailableGuild.Interface.new(state: state.State, id: apiTypes.Snowflake): UnavailableGuild + local self = setmetatable( + { + state = state, + + id = id, + isUnavailable = true, + } :: UnavailableGuild, + { __index = UnavailableGuild.Prototype } + ) + + return self +end + +export type UnavailableGuild = typeof(UnavailableGuild.Prototype) & { + state: state.State, + + id: apiTypes.Snowflake, + isUnavailable: boolean, +} + +return UnavailableGuild.Interface diff --git a/packages/classes/src/guild/welcomeScreen.luau b/packages/classes/src/guild/welcomeScreen.luau new file mode 100644 index 0000000..10ca6ee --- /dev/null +++ b/packages/classes/src/guild/welcomeScreen.luau @@ -0,0 +1,41 @@ +--[[ + Implementation of the Discord WelcomeScreen class in Luau + + https://discord.com/developers/docs/resources/guild#welcome-screen-object +]] + +local apiTypes = require("@api-types/apiTypes") + +local welcomeScreenChannel = require("@classes/guild/welcomeScreenChannel") + +local WelcomeScreen = {} + +WelcomeScreen.Interface = {} +WelcomeScreen.Prototype = {} + +function WelcomeScreen.Prototype.sync(self: WelcomeScreen, welcomeScreenData: apiTypes.WelcomeScreenObject) + local welcomeChannelArray = {} + + for _, welcomeChannelData in welcomeScreenData.welcome_channels do + table.insert(welcomeChannelArray, welcomeScreenChannel.new(welcomeChannelData)) + end + + self.welcomeChannels = welcomeChannelArray + + self.description = welcomeScreenData.description +end + +function WelcomeScreen.Interface.new(welcomeScreenData: apiTypes.WelcomeScreenObject): WelcomeScreen + local self = setmetatable({} :: WelcomeScreen, { __index = WelcomeScreen.Prototype }) + + self:sync(welcomeScreenData) + + return self +end + +export type WelcomeScreen = typeof(WelcomeScreen.Prototype) & { + description: string?, + welcomeChannels: { welcomeScreenChannel.WelcomeScreenChannel }, +} + +return WelcomeScreen.Interface diff --git a/packages/classes/src/guild/welcomeScreenChannel.luau b/packages/classes/src/guild/welcomeScreenChannel.luau new file mode 100644 index 0000000..5d23b1c --- /dev/null +++ b/packages/classes/src/guild/welcomeScreenChannel.luau @@ -0,0 +1,39 @@ +--[[ + Implementation of the Discord WelcomeScreenChannel class in Luau + + https://discord.com/developers/docs/resources/guild#welcome-screen-object-welcome-screen-channel-structure +]] + +local apiTypes = require("@api-types/apiTypes") + +local WelcomeScreenChannel = {} + +WelcomeScreenChannel.Interface = {} +WelcomeScreenChannel.Prototype = {} + +function WelcomeScreenChannel.Prototype.sync( + self: WelcomeScreenChannel, + welcomeScreenData: apiTypes.WelcomeScreenChannelObject +) + self.channelId = welcomeScreenData.channel_id + self.description = welcomeScreenData.description + self.emojiId = welcomeScreenData.emoji_id + self.emojiName = welcomeScreenData.emoji_name +end + +function WelcomeScreenChannel.Interface.new(roleTagData: apiTypes.WelcomeScreenChannelObject): WelcomeScreenChannel + local self = setmetatable({} :: WelcomeScreenChannel, { __index = WelcomeScreenChannel.Prototype }) + + self:sync(roleTagData) + + return self +end + +export type WelcomeScreenChannel = typeof(WelcomeScreenChannel.Prototype) & { + channelId: apiTypes.Snowflake, + description: string, + emojiId: apiTypes.Snowflake?, + emojiName: string?, +} + +return WelcomeScreenChannel.Interface diff --git a/packages/classes/src/interaction/behaviour/interaction.luau b/packages/classes/src/interaction/behaviour/interaction.luau new file mode 100644 index 0000000..ab0fad2 --- /dev/null +++ b/packages/classes/src/interaction/behaviour/interaction.luau @@ -0,0 +1,98 @@ +--[[ + Voice Behaviour, responsible for implementing the various properties, and methods for a voice-able channels. +]] + +local apiTypes = require("@api-types/apiTypes") +local applicationTypes = require("@api-types/application") +local interactionTypes = require("@api-types/interaction") + +local data = require("@classes/interaction/data") +local unavailableGuild = require("@classes/guild/unavailableGuild") +local member = require("@classes/guild/member") +local user = require("@classes/user") +local message = require("@classes/message/message") +local permission = require("@classes/permission") +local entitlement = require("@classes/entitlement") +local state = require("@classes/state") + +local Interaction = {} + +Interaction.Interface = {} +Interaction.Prototype = {} + +function Interaction.Interface.inheritProperties(class: any, interactionData: apiTypes.InteractionObject) + local entitlementArray = {} + local authorizingIntegrationOwners = {} + + for _, entitlementData in next, interactionData.entitlements or {} do + table.insert(entitlementArray, entitlement.new(entitlementData)) + end + + for integrationType, owner in ipairs(interactionData.authorizing_integration_owners) do + -- todo: are these actually numbers or strings? + + if integrationType == 0 then + authorizingIntegrationOwners.GuildInstall = owner + elseif integrationType == 1 then + authorizingIntegrationOwners.UserInstall = owner + end + end + + class.type = interactionTypes.InteractionType[interactionData.type] + class.context = interactionTypes.InteractionContextType[interactionData.context] + + class.id = interactionData.id + class.applicationId = interactionData.application_id + class.data = interactionData.data and data.new(class.state, interactionData.data) + class.guild = interactionData.guild and unavailableGuild.new(class.state, interactionData.guild.id) + class.guildId = interactionData.guild_id + -- class.channel = interactionData.channel and message.new(class.state, interactionData.channel) + class.channelId = interactionData.channel_id + class.member = interactionData.member and member.new(interactionData.member) + class.user = interactionData.user and user.new(interactionData.user) + class.token = interactionData.token + class.version = interactionData.version + class.message = interactionData.message and message.new(class.state, interactionData.message) + class.appPermissions = permission.new(interactionData.app_permissions) + class.locale = interactionData.locale + class.guildLocale = interactionData.guild_locale + class.entitlements = entitlementArray + class.authorizingIntegrationOwners = authorizingIntegrationOwners +end + +function Interaction.Interface.inheritMethods(class: any) + for key, value in Interaction.Prototype do + class[key] = value + end +end + +export type InteractionMethods = typeof(Interaction.Prototype) +export type InteractionProperties = { + state: state.State, + + id: apiTypes.Snowflake, + applicationId: apiTypes.Snowflake, + type: interactionTypes.InteractionType, + data: data.Data?, + guild: unavailableGuild.UnavailableGuild?, + guildId: apiTypes.Snowflake?, + -- channel: apiTypes.Snowflake?, -- fixme: we can't type the channels since channels require this message class. + channelId: apiTypes.Snowflake?, + member: member.Member?, + user: user.User?, + token: string, + version: number, + message: message.Message?, + appPermissions: permission.Permission, + locale: apiTypes.LanguageLocales?, + guildLocale: apiTypes.LanguageLocales?, + entitlements: { entitlement.Entitlement }, + authorizingIntegrationOwners: { + [applicationTypes.IntegrationTypesConfig]: apiTypes.Snowflake, + }, + context: interactionTypes.InteractionContextType?, +} + +export type Interaction = InteractionMethods & InteractionProperties + +return Interaction.Interface diff --git a/packages/classes/src/interaction/data.luau b/packages/classes/src/interaction/data.luau new file mode 100644 index 0000000..ee1845d --- /dev/null +++ b/packages/classes/src/interaction/data.luau @@ -0,0 +1,63 @@ +--[[ + Implementation of the Discord Data class in Luau + + https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-data +]] + +local apiTypes = require("@api-types/apiTypes") +local interactionTypes = require("@api-types/interaction") + +local resolved = require("@classes/resolved") +local state = require("@classes/state") + +local dataOption = require("@classes/interaction/dataOption") + +local Data = {} + +Data.Interface = {} +Data.Prototype = {} + +function Data.Prototype.sync(self: Data, dataObject: apiTypes.InteractionDataObject) + local optionArray = {} + + for _, option in next, dataObject.options or {} do + table.insert(optionArray, dataOption.new(option)) + end + + self.type = interactionTypes.InteractionType[dataObject.type] + + self.id = dataObject.id + self.name = dataObject.name + self.options = optionArray + self.resolved = dataObject.resolved and resolved.new(self.state, dataObject.resolved) + self.guildId = dataObject.guild_id + self.targetId = dataObject.target_id +end + +function Data.Interface.new(state: state.State, dataObject: apiTypes.InteractionDataObject): Data + local self = setmetatable( + { + state = state, + } :: Data, + { __index = Data.Prototype } + ) + + self:sync(dataObject) + + return self +end + +-- ensure any cyclic dependencies are also updated! +export type Data = typeof(Data.Prototype) & { + state: state.State, + + id: apiTypes.Snowflake, + name: string, + type: interactionTypes.InteractionType, + resolved: resolved.Resolved?, + options: { dataOption.DataOption }, + guildId: apiTypes.Snowflake?, + targetId: apiTypes.Snowflake?, +} + +return Data.Interface diff --git a/packages/classes/src/interaction/dataOption.luau b/packages/classes/src/interaction/dataOption.luau new file mode 100644 index 0000000..cc69403 --- /dev/null +++ b/packages/classes/src/interaction/dataOption.luau @@ -0,0 +1,50 @@ +--[[ + Implementation of the Discord DataOption class in Luau + + https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-type +]] + +local apiTypes = require("@api-types/apiTypes") +local interactionTypes = require("@api-types/interaction") + +local DataOption = {} + +DataOption.Interface = {} +DataOption.Prototype = {} + +function DataOption.Prototype.sync( + self: DataOption, + optionData: apiTypes.ApplicationCommandInteractionDataOptionObject +) + local optionArray = {} + + self.type = interactionTypes.ApplicationCommandOptionType[optionData.type] + + for _, option in next, optionData.options or {} do + table.insert(optionArray, DataOption.Interface.new(option)) + end + + self.options = optionArray + + self.name = optionData.name + self.value = (optionData.value :: any) :: T + self.focused = optionData.focused +end + +function DataOption.Interface.new(optionData: apiTypes.ApplicationCommandInteractionDataOptionObject): DataOption + local self = setmetatable({} :: DataOption, { __index = DataOption.Prototype }) + + self:sync(optionData) + + return self +end + +export type DataOption = typeof(DataOption.Prototype) & { + name: string, + type: interactionTypes.ApplicationCommandOptionType, + value: T?, + options: { DataOption }?, + focused: boolean?, +} + +return DataOption.Interface diff --git a/packages/classes/src/interaction/types/autocomplete.luau b/packages/classes/src/interaction/types/autocomplete.luau new file mode 100644 index 0000000..78f66c5 --- /dev/null +++ b/packages/classes/src/interaction/types/autocomplete.luau @@ -0,0 +1,44 @@ +--[[ + Implementation of the Discord Autocomplete Interaction class in Luau + + https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-structure +]] + +local apiTypes = require("@api-types/apiTypes") + +local interactionBehaviour = require("@classes/interaction/behaviour/interaction") + +-- selene: allow(unused_variable) +local state = require("@classes/state") + +local Autocomplete = {} + +Autocomplete.Interface = {} +Autocomplete.Prototype = {} +Autocomplete.Behaviours = { + interactionBehaviour, +} + +function Autocomplete.Prototype.sync(self: Autocomplete, interactionData: apiTypes.InteractionObject) + for _, behaviour in Autocomplete.Behaviours do + behaviour.inheritMethods(self) + behaviour.inheritProperties(self, interactionData) + end +end + +function Autocomplete.Interface.new(state: state.State, interactionData: apiTypes.InteractionObject): Autocomplete + local self = setmetatable( + { + state = state, + } :: Autocomplete, + { __index = Autocomplete.Prototype } + ) + + self:sync(interactionData) + + return self +end + +export type Autocomplete = typeof(Autocomplete.Prototype) & interactionBehaviour.Interaction + +return Autocomplete.Interface diff --git a/packages/classes/src/interaction/types/command.luau b/packages/classes/src/interaction/types/command.luau new file mode 100644 index 0000000..d31830e --- /dev/null +++ b/packages/classes/src/interaction/types/command.luau @@ -0,0 +1,335 @@ +--[[ + Implementation of the Discord Command Interaction class in Luau + + https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-structure +]] + +local future = require("@vendor/future") + +local apiTypes = require("@api-types/apiTypes") + +local messageBuilder = require("@builders/message/message") +local interactionBehaviour = require("@classes/interaction/behaviour/interaction") +local message = require("@classes/message/message") +local user = require("@classes/user") + +local interactionRest = require("@rest/interaction") + +-- selene: allow(unused_variable) +local state = require("@classes/state") + +local Command = {} + +Command.Interface = {} +Command.Prototype = {} +Command.Behaviours = { + interactionBehaviour, +} + +--[[ + acknowledge an interaction and edit a response later, the user sees a loading state +]] +function Command.Prototype.deferAsync(self: Command): future.Future + local request = self.state.rest:newRequest() + + return future.new(function() + local status, response = interactionRest + .createInteractionResponseAsync(request, self.id, self.token, { + type = 5, + data = {}, + }, { + withResponse = false, + }) + :await() + + self.isDeferred = true + + assert(status == "Fulfilled", tostring(response)) + end) +end + +--[[ + respond to this interaction with a message +]] +function Command.Prototype.messageAsync( + self: Command, + messageJSON: messageBuilder.JSON, + responseMessage: boolean? +): future.Future + local request = self.state.rest:newRequest() + + return future.new(function() + local status, response = interactionRest + .createInteractionResponseAsync(request, self.id, self.token, { + type = self.isDeferred and 5 or 4, + data = messageJSON, + }, { + withResponse = responseMessage or false, + }) + :await() + + assert(status == "Fulfilled", tostring(response)) + + if responseMessage and response.resource.message then + return message.new(self.state, response.resource.message) + end + end) +end + +--[[ + get the response created by this interaction +]] +function Command.Prototype.getResponseAsync(self: Command, threadId: string?): future.Future + local request = self.state.rest:newRequest() + + return future.new(function() + local status, response = interactionRest + .getOriginalInteractionResponseAsync(request, self.id, self.token, { + threadId = threadId, + }) + :await() + + assert(status == "Fulfilled", tostring(response)) + + return message.new(self.state, response) + end) +end + +--[[ + edit the response made by this interaction +]] +function Command.Prototype.editResponseAsync( + self: Command, + messageJSON: messageBuilder.JSON, + threadId: string? +): future.Future + local request = self.state.rest:newRequest() + + return future.new(function() + local status, response = interactionRest + .editOriginalInteractionResponseAsync(request, self.id, self.token, messageJSON, { + threadId = threadId, + }) + :await() + + assert(status == "Fulfilled", tostring(response)) + + return message.new(self.state, response) + end) +end + +--[[ + delete the original response made by this interaction +]] +function Command.Prototype.deleteResponseAsync(self: Command): future.Future + local request = self.state.rest:newRequest() + + return future.new(function() + local status, response = + interactionRest.deleteOriginalInteractionResponseAsync(request, self.id, self.token):await() + + assert(status == "Fulfilled", tostring(response)) + end) +end + +--[[ + create a followup response to an interaction, followup responses are messages you can send after + the initial response is sent. +]] +function Command.Prototype.createFollowupResponseAsync( + self: Command, + messageJSON: messageBuilder.JSON, + threadId: string? +) + local request = self.state.rest:newRequest() + + return future.new(function() + local status, response = interactionRest + .createFollowupMessageAsync(request, self.id, self.token, messageJSON, { + wait = true, + threadId = threadId, + }) + :await() + + assert(status == "Fulfilled", tostring(response)) + + return message.new(self.state, response) + end) +end + +--[[ + get the followup response created by this interaction +]] +function Command.Prototype.getFollowupResponseAsync(self: Command, messageId: string, threadId: string?) + local request = self.state.rest:newRequest() + + return future.new(function() + local status, response = interactionRest + .getFollowupMessageAsync(request, self.id, self.token, messageId, { + threadId = threadId, + }) + :await() + + assert(status == "Fulfilled", tostring(response)) + + return message.new(self.state, response) + end) +end + +--[[ + edit the followup response created by this interaction +]] +function Command.Prototype.editFollowupResponseAsync( + self: Command, + messageId: apiTypes.Snowflake, + messageJSON: messageBuilder.JSON, + threadId: string? +) + local request = self.state.rest:newRequest() + + return future.new(function() + local status, response = interactionRest + .editFollowupMessageAsync(request, self.id, self.token, messageId, messageJSON, { + threadId = threadId, + }) + :await() + + assert(status == "Fulfilled", tostring(response)) + + return message.new(self.state, response) + end) +end + +--[[ + delete the followup response created by this interaction +]] +function Command.Prototype.deleteFollowupResponseAsync(self: Command, messageId: string): future.Future + local request = self.state.rest:newRequest() + + return future.new(function() + local status, response = + interactionRest.deleteFollowupMessageAsync(request, self.id, self.token, messageId):await() + + assert(status == "Fulfilled", tostring(response)) + end) +end + +--[[ + Returns a list of users who have been mentioned in this interaction +]] +function Command.Prototype.getUsers(self: Command): { user.User } + if self.data and self.data.resolved and self.data.resolved.users then + local userArray = {} + + for _, user in self.data.resolved.users do + table.insert(userArray, user) + end + + return userArray + else + return {} + end +end + +--[[ + Returns a list of members who have been mentioned in this interaction +]] +function Command.Prototype.getMembers(self: Command) + if self.data and self.data.resolved and self.data.resolved.members then + local memberArray = {} + + for _, user in self.data.resolved.members do + table.insert(memberArray, user) + end + + return memberArray + else + return {} + end +end + +--[[ + Returns a list of roles which have been mentioned in this interaction +]] +function Command.Prototype.getRoles(self: Command) + if self.data and self.data.resolved and self.data.resolved.roles then + local roleArray = {} + + for _, user in self.data.resolved.roles do + table.insert(roleArray, user) + end + + return roleArray + else + return {} + end +end + +--[[ + Returns a list of channels which have been mentioned in this interaction +]] +function Command.Prototype.getChannels(_self: Command) + error(`Unable to fetch channels! This is not yet implemented!`) +end + +--[[ + Returns a list of messages which have been mentioned in this interaction +]] +function Command.Prototype.getMessages(self: Command) + if self.data and self.data.resolved and self.data.resolved.messages then + local messageArray = {} + + for _, user in self.data.resolved.messages do + table.insert(messageArray, user) + end + + return messageArray + else + return {} + end +end + +--[[ + Returns a list of attachments which have been mentioned in this interaction +]] +function Command.Prototype.getAttachments(self: Command) + if self.data and self.data.resolved and self.data.resolved.attachments then + local attachmentArray = {} + + for _, user in self.data.resolved.attachments do + table.insert(attachmentArray, user) + end + + return attachmentArray + else + return {} + end +end + +function Command.Prototype.sync(self: Command, interactionData: apiTypes.InteractionObject) + for _, behaviour in Command.Behaviours do + behaviour.inheritMethods(self) + behaviour.inheritProperties(self, interactionData) + end +end + +function Command.Interface.new(state: state.State, interactionData: apiTypes.InteractionObject): Command + local self = setmetatable( + { + state = state, + + isDeferred = false, + } :: Command, + { __index = Command.Prototype } + ) + + self:sync(interactionData) + + return self +end + +export type Command = typeof(Command.Prototype) & { + isDeferred: boolean, +} & interactionBehaviour.Interaction + +return Command.Interface diff --git a/packages/classes/src/interaction/types/component.luau b/packages/classes/src/interaction/types/component.luau new file mode 100644 index 0000000..ca8eb58 --- /dev/null +++ b/packages/classes/src/interaction/types/component.luau @@ -0,0 +1,44 @@ +--[[ + Implementation of the Discord Component Interaction class in Luau + + https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-structure +]] + +local apiTypes = require("@api-types/apiTypes") + +local interactionBehaviour = require("@classes/interaction/behaviour/interaction") + +-- selene: allow(unused_variable) +local state = require("@classes/state") + +local Component = {} + +Component.Interface = {} +Component.Prototype = {} +Component.Behaviours = { + interactionBehaviour, +} + +function Component.Prototype.sync(self: Component, interactionData: apiTypes.InteractionObject) + for _, behaviour in Component.Behaviours do + behaviour.inheritMethods(self) + behaviour.inheritProperties(self, interactionData) + end +end + +function Component.Interface.new(state: state.State, interactionData: apiTypes.InteractionObject): Component + local self = setmetatable( + { + state = state, + } :: Component, + { __index = Component.Prototype } + ) + + self:sync(interactionData) + + return self +end + +export type Component = typeof(Component.Prototype) & interactionBehaviour.Interaction + +return Component.Interface diff --git a/packages/classes/src/interaction/types/modal.luau b/packages/classes/src/interaction/types/modal.luau new file mode 100644 index 0000000..380db85 --- /dev/null +++ b/packages/classes/src/interaction/types/modal.luau @@ -0,0 +1,44 @@ +--[[ + Implementation of the Discord Modal Interaction class in Luau + + https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-structure +]] + +local apiTypes = require("@api-types/apiTypes") + +local interactionBehaviour = require("@classes/interaction/behaviour/interaction") + +-- selene: allow(unused_variable) +local state = require("@classes/state") + +local Modal = {} + +Modal.Interface = {} +Modal.Prototype = {} +Modal.Behaviours = { + interactionBehaviour, +} + +function Modal.Prototype.sync(self: Modal, interactionData: apiTypes.InteractionObject) + for _, behaviour in Modal.Behaviours do + behaviour.inheritMethods(self) + behaviour.inheritProperties(self, interactionData) + end +end + +function Modal.Interface.new(state: state.State, interactionData: apiTypes.InteractionObject): Modal + local self = setmetatable( + { + state = state, + } :: Modal, + { __index = Modal.Prototype } + ) + + self:sync(interactionData) + + return self +end + +export type Modal = typeof(Modal.Prototype) & interactionBehaviour.Interaction + +return Modal.Interface diff --git a/packages/classes/src/interaction/types/ping.luau b/packages/classes/src/interaction/types/ping.luau new file mode 100644 index 0000000..b6a8718 --- /dev/null +++ b/packages/classes/src/interaction/types/ping.luau @@ -0,0 +1,68 @@ +--[[ + Implementation of the Discord Ping Interaction class in Luau + + https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-structure +]] + +local future = require("@vendor/future") + +local apiTypes = require("@api-types/apiTypes") + +local interactionRest = require("@rest/interaction") + +local interactionBehaviour = require("@classes/interaction/behaviour/interaction") + +-- selene: allow(unused_variable) +local state = require("@classes/state") + +local Ping = {} + +Ping.Interface = {} +Ping.Prototype = {} +Ping.Behaviours = { + interactionBehaviour, +} + +--[[ + acknowledge an interaction ping +]] +function Ping.Prototype.pongAsync(self: Ping): future.Future + local request = self.state.rest:newRequest() + + return future.new(function() + local status, response = interactionRest + .createInteractionResponseAsync(request, self.id, self.token, { + type = 1, + data = {}, + }, { + withResponse = false, + }) + :await() + + assert(status == "Fulfilled", tostring(response)) + end) +end + +function Ping.Prototype.sync(self: Ping, interactionData: apiTypes.InteractionObject) + for _, behaviour in Ping.Behaviours do + behaviour.inheritMethods(self) + behaviour.inheritProperties(self, interactionData) + end +end + +function Ping.Interface.new(state: state.State, interactionData: apiTypes.InteractionObject): Ping + local self = setmetatable( + { + state = state, + } :: Ping, + { __index = Ping.Prototype } + ) + + self:sync(interactionData) + + return self +end + +export type Ping = typeof(Ping.Prototype) & interactionBehaviour.Interaction + +return Ping.Interface diff --git a/packages/classes/src/message/activity.luau b/packages/classes/src/message/activity.luau new file mode 100644 index 0000000..0a48932 --- /dev/null +++ b/packages/classes/src/message/activity.luau @@ -0,0 +1,33 @@ +--[[ + Implementation of the Discord Activity class in Luau + + https://discord.com/developers/docs/resources/message#message-object-message-activity-structure +]] + +local apiTypes = require("@api-types/apiTypes") +local messageTypes = require("@api-types/message") + +local Activity = {} + +Activity.Interface = {} +Activity.Prototype = {} + +function Activity.Prototype.sync(self: Activity, activityData: apiTypes.MessageActivityObject) + self.type = messageTypes.MessageAcitvityType[activityData.type] + self.partyId = activityData.party_id +end + +function Activity.Interface.new(activityData: apiTypes.MessageActivityObject): Activity + local self = setmetatable({} :: Activity, { __index = Activity.Prototype }) + + self:sync(activityData) + + return self +end + +export type Activity = typeof(Activity.Prototype) & { + type: messageTypes.MessageAcitvityType, + partyId: string?, +} + +return Activity.Interface diff --git a/packages/classes/src/message/call.luau b/packages/classes/src/message/call.luau new file mode 100644 index 0000000..76009c2 --- /dev/null +++ b/packages/classes/src/message/call.luau @@ -0,0 +1,42 @@ +--[[ + Implementation of the Discord Call class in Luau + + https://discord.com/developers/docs/resources/message#message-call-object +]] + +local datetime = require("@std-polyfills/datetime") + +local apiTypes = require("@api-types/apiTypes") + +local user = require("@classes/user") + +local Call = {} + +Call.Interface = {} +Call.Prototype = {} + +function Call.Prototype.sync(self: Call, callData: apiTypes.MessageCallObject) + local userArray = {} + + for _, userData in callData.participants do + table.insert(userArray, user.new(userData)) + end + + self.participants = userArray + self.endedTimestamp = datetime.fromIsoDate(callData.ended_timestamp) +end + +function Call.Interface.new(callData: apiTypes.MessageCallObject): Call + local self = setmetatable({} :: Call, { __index = Call.Prototype }) + + self:sync(callData) + + return self +end + +export type Call = typeof(Call.Prototype) & { + participants: { user.User }, + endedTimestamp: datetime.DateTime, +} + +return Call.Interface diff --git a/packages/classes/src/message/components/actionRow.luau b/packages/classes/src/message/components/actionRow.luau new file mode 100644 index 0000000..0a4e2cd --- /dev/null +++ b/packages/classes/src/message/components/actionRow.luau @@ -0,0 +1,64 @@ +--[[ + Implementation of the Discord ActionRow class in Luau + + https://discord.com/developers/docs/resources/message#message-object-message-activity-structure +]] + +local apiTypes = require("@api-types/apiTypes") + +local button = require("@classes/message/components/button") +local textInput = require("@classes/message/components/textInput") +local selectMenu = require("@classes/message/components/selectMenu/selectMenu") + +local ActionRow = {} + +ActionRow.Interface = {} +ActionRow.Prototype = {} + +function ActionRow.Prototype.sync(self: ActionRow, activityObject: apiTypes.ActionRowComponentObject) + local components: { button.Button | textInput.TextInput | selectMenu.SelectMenu | ActionRow } = {} + + for _, componentData in activityObject.components do + if componentData.type == 1 then + local data = componentData :: apiTypes.ActionRowComponentObject + + table.insert(components, ActionRow.Interface.new(data)) + elseif componentData.type == 2 then + local data = componentData :: apiTypes.ButtonComponentObject + + table.insert(components, button.new(data)) + elseif componentData.type == 4 then + local data = componentData :: apiTypes.TextInputComponentObject + + table.insert(components, textInput.new(data)) + elseif + componentData.type == 3 + or componentData.type == 5 + or componentData.type == 6 + or componentData.type == 7 + or componentData.type == 8 + then + local data = componentData :: apiTypes.SelectMenuComponentObject + + table.insert(components, selectMenu.new(data)) + end + end + + self.type = activityObject.type + self.components = components +end + +function ActionRow.Interface.new(activityObject: apiTypes.ActionRowComponentObject): ActionRow + local self = setmetatable({} :: ActionRow, { __index = ActionRow.Prototype }) + + self:sync(activityObject) + + return self +end + +export type ActionRow = typeof(ActionRow.Prototype) & { + type: number, + components: { button.Button | textInput.TextInput | selectMenu.SelectMenu | ActionRow }, +} + +return ActionRow.Interface diff --git a/packages/classes/src/message/components/button.luau b/packages/classes/src/message/components/button.luau new file mode 100644 index 0000000..82bd00b --- /dev/null +++ b/packages/classes/src/message/components/button.luau @@ -0,0 +1,47 @@ +--[[ + Implementation of the Discord Button class in Luau + + https://discord.com/developers/docs/interactions/message-components#buttons +]] + +local apiTypes = require("@api-types/apiTypes") +local messageTypes = require("@api-types/message") + +local emoji = require("@classes/emoji") + +local Button = {} + +Button.Interface = {} +Button.Prototype = {} + +function Button.Prototype.sync(self: Button, buttonData: apiTypes.ButtonComponentObject) + self.style = messageTypes.ButtonStyle[buttonData.style] + + self.type = 2 + self.label = buttonData.label + self.emoji = buttonData.emoji and emoji.new(buttonData.emoji) + self.customId = buttonData.custom_id + self.url = buttonData.url + self.disabled = buttonData.disabled +end + +function Button.Interface.new(buttonData: apiTypes.ButtonComponentObject): Button + local self = setmetatable({} :: Button, { __index = Button.Prototype }) + + self:sync(buttonData) + + return self +end + +export type Button = typeof(Button.Prototype) & { + type: number, + style: messageTypes.ButtonStyle, + label: string?, + emoji: emoji.Emoji?, + customId: string?, + skuId: apiTypes.Snowflake?, + url: string?, + disabled: boolean?, +} + +return Button.Interface diff --git a/packages/classes/src/message/components/selectMenu/defaultValue.luau b/packages/classes/src/message/components/selectMenu/defaultValue.luau new file mode 100644 index 0000000..51ec220 --- /dev/null +++ b/packages/classes/src/message/components/selectMenu/defaultValue.luau @@ -0,0 +1,33 @@ +--[[ + Implementation of the Discord DefaultValue class in Luau + + https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-default-value-structure +]] + +local apiTypes = require("@api-types/apiTypes") +local messageTypes = require("@api-types/message") + +local DefaultValue = {} + +DefaultValue.Interface = {} +DefaultValue.Prototype = {} + +function DefaultValue.Prototype.sync(self: DefaultValue, valueData: apiTypes.SelectDefaultValueObject) + self.id = valueData.id + self.type = messageTypes.SelectDefaultValueType[valueData.type] +end + +function DefaultValue.Interface.new(valueData: apiTypes.SelectDefaultValueObject): DefaultValue + local self = setmetatable({} :: DefaultValue, { __index = DefaultValue.Prototype }) + + self:sync(valueData) + + return self +end + +export type DefaultValue = typeof(DefaultValue.Prototype) & { + id: apiTypes.Snowflake, + type: messageTypes.SelectDefaultValueType, +} + +return DefaultValue.Interface diff --git a/packages/classes/src/message/components/selectMenu/option.luau b/packages/classes/src/message/components/selectMenu/option.luau new file mode 100644 index 0000000..a9c1e80 --- /dev/null +++ b/packages/classes/src/message/components/selectMenu/option.luau @@ -0,0 +1,40 @@ +--[[ + Implementation of the Discord Option class in Luau + + https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-menu-structure +]] + +local apiTypes = require("@api-types/apiTypes") + +local emoji = require("@classes/emoji") + +local Option = {} + +Option.Interface = {} +Option.Prototype = {} + +function Option.Prototype.sync(self: Option, optionData: apiTypes.SelectOptionObject) + self.label = optionData.label + self.value = optionData.value + self.description = optionData.description + self.emoji = optionData.emoji and emoji.new(optionData.emoji) + self.default = optionData.default +end + +function Option.Interface.new(optionData: apiTypes.SelectOptionObject): Option + local self = setmetatable({} :: Option, { __index = Option.Prototype }) + + self:sync(optionData) + + return self +end + +export type Option = typeof(Option.Prototype) & { + label: string, + value: string, + description: string?, + emoji: emoji.Emoji?, + default: boolean?, +} + +return Option.Interface diff --git a/packages/classes/src/message/components/selectMenu/selectMenu.luau b/packages/classes/src/message/components/selectMenu/selectMenu.luau new file mode 100644 index 0000000..0d804c6 --- /dev/null +++ b/packages/classes/src/message/components/selectMenu/selectMenu.luau @@ -0,0 +1,66 @@ +--[[ + Implementation of the Discord SelectMenu class in Luau + + https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-menu-structure +]] + +local apiTypes = require("@api-types/apiTypes") +local channelTypes = require("@api-types/channel") + +local option = require("@classes/message/components/selectMenu/option") +local defaultValue = require("@classes/message/components/selectMenu/defaultValue") + +local SelectMenu = {} + +SelectMenu.Interface = {} +SelectMenu.Prototype = {} + +function SelectMenu.Prototype.sync(self: SelectMenu, selectMenuData: apiTypes.SelectMenuComponentObject) + local optionArray = {} + local defaultValues = {} + local channelTypesArray: { channelTypes.ChannelType } = {} + + for _, optionData in next, selectMenuData.options or {} do + table.insert(optionArray, option.new(optionData)) + end + + for _, defaultValueData in next, selectMenuData.default_values or {} do + table.insert(defaultValues, defaultValue.new(defaultValueData)) + end + + for _, channelType in next, selectMenuData.channel_types or {} do + table.insert(channelTypesArray, channelTypes.ChannelTypes[channelType]) + end + + self.type = selectMenuData.type + self.customId = selectMenuData.custom_id + self.options = optionArray + self.channelTypes = channelTypesArray + self.placeholder = selectMenuData.placeholder + self.defaultValues = defaultValues + self.minValues = selectMenuData.min_values + self.maxValues = selectMenuData.max_values + self.disabled = selectMenuData.disabled +end + +function SelectMenu.Interface.new(selectMenuData: apiTypes.SelectMenuComponentObject): SelectMenu + local self = setmetatable({} :: SelectMenu, { __index = SelectMenu.Prototype }) + + self:sync(selectMenuData) + + return self +end + +export type SelectMenu = typeof(SelectMenu.Prototype) & { + type: number, + customId: string, + options: { option.Option }?, + channelTypes: { channelTypes.ChannelType }?, + placeholder: string?, + defaultValues: { defaultValue.DefaultValue }?, + minValues: number?, + maxValues: number?, + disabled: boolean?, +} + +return SelectMenu.Interface diff --git a/packages/classes/src/message/components/textInput.luau b/packages/classes/src/message/components/textInput.luau new file mode 100644 index 0000000..64ec310 --- /dev/null +++ b/packages/classes/src/message/components/textInput.luau @@ -0,0 +1,47 @@ +--[[ + Implementation of the Discord TextInput class in Luau + + https://discord.com/developers/docs/interactions/message-components#text-input-object-text-input-structure +]] + +local apiTypes = require("@api-types/apiTypes") +local messageTypes = require("@api-types/message") + +local TextInput = {} + +TextInput.Interface = {} +TextInput.Prototype = {} + +function TextInput.Prototype.sync(self: TextInput, textInputData: apiTypes.TextInputComponentObject) + self.type = textInputData.type + self.customId = textInputData.custom_id + self.style = messageTypes.TextInputStyle[textInputData.style] + self.label = textInputData.label + self.minLength = textInputData.min_length + self.maxLength = textInputData.max_length + self.required = textInputData.required + self.value = textInputData.value + self.placeholder = textInputData.placeholder +end + +function TextInput.Interface.new(textInputData: apiTypes.TextInputComponentObject): TextInput + local self = setmetatable({} :: TextInput, { __index = TextInput.Prototype }) + + self:sync(textInputData) + + return self +end + +export type TextInput = typeof(TextInput.Prototype) & { + type: number, + customId: string, + style: messageTypes.TextInputStyle, + label: string, + minLength: number?, + maxLength: number?, + required: boolean?, + value: string?, + placeholder: string?, +} + +return TextInput.Interface diff --git a/packages/classes/src/message/ineractionMetadata.luau b/packages/classes/src/message/ineractionMetadata.luau new file mode 100644 index 0000000..82b0cd7 --- /dev/null +++ b/packages/classes/src/message/ineractionMetadata.luau @@ -0,0 +1,66 @@ +--[[ + Implementation of the Discord IneractionMetadata class in Luau + + https://discord.com/developers/docs/resources/message#message-interaction-metadata-object-message-interaction-metadata-structure +]] + +local apiTypes = require("@api-types/apiTypes") +local applicationTypes = require("@api-types/application") +local interactionTypes = require("@api-types/interaction") + +local user = require("@classes/user") + +local IneractionMetadata = {} + +IneractionMetadata.Interface = {} +IneractionMetadata.Prototype = {} + +function IneractionMetadata.Prototype.sync( + self: IneractionMetadata, + interactionData: apiTypes.MessageInteractionMetadatObject +) + local authorizingIntegrationOwners = {} + + for integrationType, owner in ipairs(interactionData.authorizing_integration_owners) do + -- todo: are these actually numbers or strings? + + if integrationType == 0 then + authorizingIntegrationOwners.GuildInstall = owner + elseif integrationType == 1 then + authorizingIntegrationOwners.UserInstall = owner + end + end + + self.type = interactionTypes.InteractionType[interactionData.type] + + self.user = user.new(interactionData.user) + + self.id = interactionData.id + self.authorizingIntegrationOwners = authorizingIntegrationOwners + self.originalResponseMessageId = interactionData.original_response_message_id + self.interactedMessageId = interactionData.interacted_message_id + self.triggeringInteractionMetadata = interactionData.triggering_interaction_metadata + and IneractionMetadata.Interface.new(interactionData.triggering_interaction_metadata) +end + +function IneractionMetadata.Interface.new(interactionData: apiTypes.MessageInteractionMetadatObject): IneractionMetadata + local self = setmetatable({} :: IneractionMetadata, { __index = IneractionMetadata.Prototype }) + + self:sync(interactionData) + + return self +end + +export type IneractionMetadata = typeof(IneractionMetadata.Prototype) & { + id: apiTypes.Snowflake, + type: interactionTypes.InteractionType, + user: user.User, + authorizingIntegrationOwners: { + [applicationTypes.IntegrationTypesConfig]: apiTypes.Snowflake, + }, + originalResponseMessageId: apiTypes.Snowflake?, + interactedMessageId: apiTypes.Snowflake?, + triggeringInteractionMetadata: IneractionMetadata, +} + +return IneractionMetadata.Interface diff --git a/packages/classes/src/message/interaction.luau b/packages/classes/src/message/interaction.luau new file mode 100644 index 0000000..c3447b0 --- /dev/null +++ b/packages/classes/src/message/interaction.luau @@ -0,0 +1,44 @@ +--[[ + Implementation of the Discord Interaction class in Luau + + https://discord.com/developers/docs/interactions/receiving-and-responding#message-interaction-object-message-interaction-structure +]] + +local apiTypes = require("@api-types/apiTypes") +local interactionTypes = require("@api-types/interaction") + +local user = require("@classes/user") +local member = require("@classes/guild/member") + +local Interaction = {} + +Interaction.Interface = {} +Interaction.Prototype = {} + +function Interaction.Prototype.sync(self: Interaction, interactionData: apiTypes.MessageInteractionObject) + self.type = interactionTypes.InteractionType[interactionData.type] + + self.user = user.new(interactionData.user) + self.member = interactionData.member and member.new(interactionData.member) + + self.id = interactionData.id + self.name = interactionData.name +end + +function Interaction.Interface.new(interactionData: apiTypes.MessageInteractionObject): Interaction + local self = setmetatable({} :: Interaction, { __index = Interaction.Prototype }) + + self:sync(interactionData) + + return self +end + +export type Interaction = typeof(Interaction.Prototype) & { + id: apiTypes.Snowflake, + type: interactionTypes.InteractionType, + name: string, + user: user.User, + member: member.Member?, +} + +return Interaction.Interface diff --git a/packages/classes/src/message/message.luau b/packages/classes/src/message/message.luau new file mode 100644 index 0000000..1c289a1 --- /dev/null +++ b/packages/classes/src/message/message.luau @@ -0,0 +1,198 @@ +--[[ + Implementation of the Discord Message class in Luau + + https://discord.com/developers/docs/resources/message#message-object +]] + +local datetime = require("@std-polyfills/datetime") + +local import = require("@utils/import") + +local apiTypes: string = require("@api-types/apiTypes") +local messageTypes = require("@api-types/message") + +local user = require("@classes/user") +local role = require("@classes/guild/role") +local channelMention = require("@classes/channels/mention") +local attachment = require("@classes/attachment") +local embed = require("@classes/embed/embed") +local reaction = require("@classes/reaction/reaction") +local activity = require("@classes/message/activity") +local application = require("@classes/application/application") +local reference = require("@classes/message/reference") +local interactionMetadata = require("@classes/message/ineractionMetadata") +local interaction = require("@classes/message/interaction") +local stickerItem = require("@classes/message/stickerItem") +local roleSubscriptionData = require("@classes/message/roleSubscriptionData") +local member = require("@classes/guild/member") +local poll = require("@classes/message/poll/poll") +local call = require("@classes/message/call") +local state = require("@classes/state") + +local textInputComponent = require("@classes/message/components/textInput") +local selectMenuComponent = require("@classes/message/components/selectMenu/selectMenu") +local actionRowComponent = require("@classes/message/components/actionRow") +local buttonComponent = require("@classes/message/components/button") + +local messageBitflag = require("@classes/bitflags/message") + +local Message = {} + +Message.Interface = {} +Message.Prototype = {} + +function Message.Prototype.sync(self: Message, messageData: apiTypes.MessageObject) + self.type = messageTypes.MessageType[messageData.type] + + local mentionArray = {} + local roleArray = {} + local channelMentionArray = {} + local attachmentArray = {} + local embedArray = {} + local reactionArray = {} + local stickerItemArray = {} + + local componentArray: { + textInputComponent.TextInput + | selectMenuComponent.SelectMenu + | actionRowComponent.ActionRow + | buttonComponent.Button + } = + {} + + for _, stickerData in next, messageData.sticker_items or {} do + table.insert(stickerItemArray, stickerItem.new(stickerData)) + end + + for _, componentData in next, messageData.components or {} do + if componentData.type == 1 then + local data = componentData :: apiTypes.ActionRowComponentObject + + table.insert(componentArray, actionRowComponent.new(data)) + elseif componentData.type == 2 then + local data = componentData :: apiTypes.ButtonComponentObject + + table.insert(componentArray, buttonComponent.new(data)) + elseif componentData.type == 4 then + local data = componentData :: apiTypes.TextInputComponentObject + + table.insert(componentArray, textInputComponent.new(data)) + elseif + componentData.type == 3 + or componentData.type == 5 + or componentData.type == 6 + or componentData.type == 7 + or componentData.type == 8 + then + local data = componentData :: apiTypes.SelectMenuComponentObject + + table.insert(componentArray, selectMenuComponent.new(data)) + end + end + + self.id = messageData.id + self.channelId = messageData.channel_id + self.author = user.new(messageData.author) + self.content = messageData.content + self.timestamp = datetime.fromIsoDate(messageData.timestamp) + self.tts = messageData.tts + self.mentionEveryone = messageData.mention_everyone + self.mentionChannels = mentionArray + self.mentionRoles = roleArray + self.mentionChannels = channelMentionArray + self.attachments = attachmentArray + self.embeds = embedArray + self.reactions = reactionArray + self.nonce = tostring(messageData.nonce) + self.pinned = messageData.pinned + self.webhookId = messageData.webhook_id + self.activity = messageData.activity and activity.new(messageData.activity) + self.application = messageData.application and application.new(self.state, messageData.application) + self.applicationId = messageData.application_id + self.flags = messageData.flags and messageBitflag.new(messageData.flags) + self.messageReference = messageData.message_reference and reference.new(messageData.message_reference) + self.referencedMessage = messageData.referenced_message + and Message.Interface.new(self.state, messageData.referenced_message) + self.interactionMetadata = messageData.interaction_metadata + and interactionMetadata.new(messageData.interaction_metadata) + self.interaction = messageData.interaction and interaction.new(messageData.interaction) + self.components = componentArray + self.stickerItems = stickerItemArray + self.position = messageData.position + self.roleSubscriptionData = messageData.role_subscription_data + and roleSubscriptionData.new(messageData.role_subscription_data) + self.poll = messageData.poll and poll.new(messageData.poll) + self.call = messageData.call and call.new(messageData.call) + + -- cyclic dependency, can only require during runtime. + self.resolved = messageData.resolved and import("@classes/resolved").new(self.state, messageData.resolved) +end + +function Message.Interface.new(state: state.State, messageData: apiTypes.MessageObject): Message + local self = setmetatable( + { + state = state, + } :: Message, + { __index = Message.Prototype } + ) + + self:sync(messageData) + + return self +end + +export type Message = typeof(Message.Prototype) & { + state: state.State, + + id: apiTypes.Snowflake, + channelId: apiTypes.Snowflake?, + author: user.User, + content: string, + timestamp: datetime.DateTime, + editedTimestamp: datetime.DateTime?, + tts: boolean, + mentionEveryone: boolean, + mentions: { user.User }, + mentionRoles: { role.Role }, + mentionChannels: { channelMention.ChannelMention }, + attachments: { attachment.Attachment }, + embeds: { embed.Embed }, + reactions: { reaction.Reaction }, + nonce: string, + pinned: boolean, + webhookId: apiTypes.Snowflake?, + type: messageTypes.MessageType, + activity: activity.Activity?, + application: application.Application?, + applicationId: apiTypes.Snowflake?, + flags: messageBitflag.MessageBitflag?, + messageReference: reference.Reference?, + -- messageSnapshots: unknown?, -- todo: add support for message snapshots + referencedMessage: Message?, + interactionMetadata: interactionMetadata.IneractionMetadata?, + interaction: interaction.Interaction?, + -- thread: unknown?, -- fixme: we can't type the channels since channels require this message class. + components: { + textInputComponent.TextInput + | selectMenuComponent.SelectMenu + | actionRowComponent.ActionRow + | buttonComponent.Button + }, + stickerItems: { stickerItem.StickerItem }, + position: number?, + roleSubscriptionData: roleSubscriptionData.RoleSubscriptionData, + resolved: { + state: state.State, + + users: { [apiTypes.Snowflake]: user.User }?, + members: { [apiTypes.Snowflake]: member.Member }?, + roles: { [apiTypes.Snowflake]: role.Role }, + -- channels: { [apiTypes.Snowflake]: unknown }, -- fixme: we can't type the channels since channels require this message class. + messages: { [apiTypes.Snowflake]: Message }, + attachments: { [apiTypes.Snowflake]: attachment.Attachment }, + }?, + poll: poll.Poll?, + call: call.Call?, +} + +return Message.Interface diff --git a/packages/classes/src/message/poll/answer.luau b/packages/classes/src/message/poll/answer.luau new file mode 100644 index 0000000..c1b0b59 --- /dev/null +++ b/packages/classes/src/message/poll/answer.luau @@ -0,0 +1,34 @@ +--[[ + Implementation of the Discord Answer class in Luau + + https://discord.com/developers/docs/resources/poll#poll-answer-object-poll-answer-object-structure +]] + +local apiTypes = require("@api-types/apiTypes") + +local mediaObject = require("@classes/message/poll/mediaObject") + +local Answer = {} + +Answer.Interface = {} +Answer.Prototype = {} + +function Answer.Prototype.sync(self: Answer, answerData: apiTypes.PollAnswerObject) + self.answerId = answerData.answer_id + self.pollMedia = mediaObject.new(answerData.poll_media) +end + +function Answer.Interface.new(answerData: apiTypes.PollAnswerObject): Answer + local self = setmetatable({} :: Answer, { __index = Answer.Prototype }) + + self:sync(answerData) + + return self +end + +export type Answer = typeof(Answer.Prototype) & { + answerId: number, + pollMedia: mediaObject.MediaObject, +} + +return Answer.Interface diff --git a/packages/classes/src/message/poll/answerCount.luau b/packages/classes/src/message/poll/answerCount.luau new file mode 100644 index 0000000..624f629 --- /dev/null +++ b/packages/classes/src/message/poll/answerCount.luau @@ -0,0 +1,34 @@ +--[[ + Implementation of the Discord AnswerCount class in Luau + + packages/classes/src/message/poll/results.luau +]] + +local apiTypes = require("@api-types/apiTypes") + +local AnswerCount = {} + +AnswerCount.Interface = {} +AnswerCount.Prototype = {} + +function AnswerCount.Prototype.sync(self: AnswerCount, answerCountData: apiTypes.PollAnswerCountObject) + self.id = answerCountData.id + self.count = answerCountData.count + self.meVoted = answerCountData.me_voted +end + +function AnswerCount.Interface.new(answerCountData: apiTypes.PollAnswerCountObject): AnswerCount + local self = setmetatable({} :: AnswerCount, { __index = AnswerCount.Prototype }) + + self:sync(answerCountData) + + return self +end + +export type AnswerCount = typeof(AnswerCount.Prototype) & { + id: number, + count: number, + meVoted: boolean, +} + +return AnswerCount.Interface diff --git a/packages/classes/src/message/poll/mediaObject.luau b/packages/classes/src/message/poll/mediaObject.luau new file mode 100644 index 0000000..80094d2 --- /dev/null +++ b/packages/classes/src/message/poll/mediaObject.luau @@ -0,0 +1,34 @@ +--[[ + Implementation of the Discord MediaObject class in Luau + + https://discord.com/developers/docs/resources/poll#poll-object +]] + +local apiTypes = require("@api-types/apiTypes") + +local emoji = require("@classes/emoji") + +local MediaObject = {} + +MediaObject.Interface = {} +MediaObject.Prototype = {} + +function MediaObject.Prototype.sync(self: MediaObject, mediaData: apiTypes.PollMediaObject) + self.text = mediaData.text + self.emoji = mediaData.emoji and emoji.new(mediaData.emoji) +end + +function MediaObject.Interface.new(mediaData: apiTypes.PollMediaObject): MediaObject + local self = setmetatable({} :: MediaObject, { __index = MediaObject.Prototype }) + + self:sync(mediaData) + + return self +end + +export type MediaObject = typeof(MediaObject.Prototype) & { + text: string?, + emoji: emoji.Emoji?, +} + +return MediaObject.Interface diff --git a/packages/classes/src/message/poll/poll.luau b/packages/classes/src/message/poll/poll.luau new file mode 100644 index 0000000..3677edd --- /dev/null +++ b/packages/classes/src/message/poll/poll.luau @@ -0,0 +1,54 @@ +--[[ + Implementation of the Discord Poll class in Luau + + https://discord.com/developers/docs/resources/poll#poll-object +]] + +local datetime = require("@std-polyfills/datetime") + +local apiTypes = require("@api-types/apiTypes") +local pollTypes = require("@api-types/poll") + +local answer = require("@classes/message/poll/answer") +local mediaObject = require("@classes/message/poll/mediaObject") +local results = require("@classes/message/poll/results") + +local Poll = {} + +Poll.Interface = {} +Poll.Prototype = {} + +function Poll.Prototype.sync(self: Poll, pollData: apiTypes.PollObject) + local answerArray = {} + + for _, answerData in pollData.answers do + table.insert(answerArray, answer.new(answerData)) + end + + self.answers = answerArray + + self.question = mediaObject.new(pollData.question) + self.expiry = datetime.fromIsoDate(pollData.expiry) + self.allowMultiselect = pollData.allow_multiselect + self.layoutType = (pollData.layout_type == 1 and "Default") :: pollTypes.PollLayoutType + self.results = results.new(pollData.results) +end + +function Poll.Interface.new(pollData: apiTypes.PollObject): Poll + local self = setmetatable({} :: Poll, { __index = Poll.Prototype }) + + self:sync(pollData) + + return self +end + +export type Poll = typeof(Poll.Prototype) & { + question: mediaObject.MediaObject, + answers: { answer.Answer }, + expiry: datetime.DateTime, + allowMultiselect: boolean, + layoutType: pollTypes.PollLayoutType, + results: results.Results?, +} + +return Poll.Interface diff --git a/packages/classes/src/message/poll/results.luau b/packages/classes/src/message/poll/results.luau new file mode 100644 index 0000000..c365389 --- /dev/null +++ b/packages/classes/src/message/poll/results.luau @@ -0,0 +1,40 @@ +--[[ + Implementation of the Discord Results class in Luau + + https://discord.com/developers/docs/resources/poll#poll-results-object-poll-results-object-structure +]] + +local apiTypes = require("@api-types/apiTypes") + +local answerCount = require("@classes/message/poll/answerCount") + +local Results = {} + +Results.Interface = {} +Results.Prototype = {} + +function Results.Prototype.sync(self: Results, resultsData: apiTypes.PollResultObject) + local countArray = {} + + for _, answerCountData in resultsData.answer_counts do + table.insert(countArray, answerCount.new(answerCountData)) + end + + self.isFinalized = resultsData.is_finalized + self.answerCounts = countArray +end + +function Results.Interface.new(resultsData: apiTypes.PollResultObject): Results + local self = setmetatable({} :: Results, { __index = Results.Prototype }) + + self:sync(resultsData) + + return self +end + +export type Results = typeof(Results.Prototype) & { + isFinalized: boolean, + answerCounts: { answerCount.AnswerCount }, +} + +return Results.Interface diff --git a/packages/classes/src/message/reference.luau b/packages/classes/src/message/reference.luau new file mode 100644 index 0000000..d1271d2 --- /dev/null +++ b/packages/classes/src/message/reference.luau @@ -0,0 +1,40 @@ +--[[ + Implementation of the Discord Reference class in Luau + + https://discord.com/developers/docs/resources/message#message-reference-structure +]] + +local apiTypes = require("@api-types/apiTypes") +local messageTypes = require("@api-types/message") + +local Reference = {} + +Reference.Interface = {} +Reference.Prototype = {} + +function Reference.Prototype.sync(self: Reference, referenceData: apiTypes.MessageReferenceObject) + self.type = messageTypes.MessageReferenceType[referenceData.type] + + self.messageId = referenceData.message_id + self.guildId = referenceData.guild_id + self.channelId = referenceData.channel_id + self.failIfNotExists = referenceData.fail_if_not_exists +end + +function Reference.Interface.new(referenceData: apiTypes.MessageReferenceObject): Reference + local self = setmetatable({} :: Reference, { __index = Reference.Prototype }) + + self:sync(referenceData) + + return self +end + +export type Reference = typeof(Reference.Prototype) & { + type: messageTypes.MessageReferenceType?, + messageId: apiTypes.Snowflake?, + channelId: apiTypes.Snowflake?, + guildId: apiTypes.Snowflake?, + failIfNotExists: boolean?, +} + +return Reference.Interface diff --git a/packages/classes/src/message/roleSubscriptionData.luau b/packages/classes/src/message/roleSubscriptionData.luau new file mode 100644 index 0000000..131214d --- /dev/null +++ b/packages/classes/src/message/roleSubscriptionData.luau @@ -0,0 +1,39 @@ +--[[ + Implementation of the Discord RoleSubscriptionData class in Luau + + https://discord.com/developers/docs/resources/message#role-subscription-data-object +]] + +local apiTypes = require("@api-types/apiTypes") + +local RoleSubscriptionData = {} + +RoleSubscriptionData.Interface = {} +RoleSubscriptionData.Prototype = {} + +function RoleSubscriptionData.Prototype.sync( + self: RoleSubscriptionData, + subscriptionData: apiTypes.RoleSubscriptionDataObject +) + self.roleSubscriptionListingId = subscriptionData.role_subscription_listing_id + self.tierName = subscriptionData.tier_name + self.totalMonthsSubscribed = subscriptionData.total_months_subscribed + self.isRenewal = subscriptionData.is_renewal +end + +function RoleSubscriptionData.Interface.new(subscriptionData: apiTypes.RoleSubscriptionDataObject): RoleSubscriptionData + local self = setmetatable({} :: RoleSubscriptionData, { __index = RoleSubscriptionData.Prototype }) + + self:sync(subscriptionData) + + return self +end + +export type RoleSubscriptionData = typeof(RoleSubscriptionData.Prototype) & { + roleSubscriptionListingId: apiTypes.Snowflake, + tierName: string, + totalMonthsSubscribed: number, + isRenewal: boolean, +} + +return RoleSubscriptionData.Interface diff --git a/packages/classes/src/message/stickerItem.luau b/packages/classes/src/message/stickerItem.luau new file mode 100644 index 0000000..31b1292 --- /dev/null +++ b/packages/classes/src/message/stickerItem.luau @@ -0,0 +1,35 @@ +--[[ + Implementation of the Discord StickerItem class in Luau + + https://discord.com/developers/docs/resources/sticker#sticker-item-object +]] + +local apiTypes = require("@api-types/apiTypes") +local stickerTypes = require("@api-types/sticker") + +local StickerItem = {} + +StickerItem.Interface = {} +StickerItem.Prototype = {} + +function StickerItem.Prototype.sync(self: StickerItem, stickerData: apiTypes.SitckerItemObject) + self.id = stickerData.id + self.name = stickerData.name + self.formatType = stickerTypes.StickerFormatTypes[stickerData.format_type] +end + +function StickerItem.Interface.new(stickerData: apiTypes.SitckerItemObject): StickerItem + local self = setmetatable({} :: StickerItem, { __index = StickerItem.Prototype }) + + self:sync(stickerData) + + return self +end + +export type StickerItem = typeof(StickerItem.Prototype) & { + id: apiTypes.Snowflake, + name: string, + formatType: stickerTypes.StickerFormatType, +} + +return StickerItem.Interface diff --git a/packages/classes/src/permission.luau b/packages/classes/src/permission.luau new file mode 100644 index 0000000..155ecfb --- /dev/null +++ b/packages/classes/src/permission.luau @@ -0,0 +1,38 @@ +--[[ + Implementation of the Discord Permissions in Luau + + https://discord.com/developers/docs/topics/permissions +]] + +local permissionTypes = require("@api-types/permission") + +local bit = require("@vendor/bit") + +local Permission = {} + +Permission.Interface = {} +Permission.Prototype = {} + +--[[ + Responsible for querying what permission a discord permission bitflag has. +]] +function Permission.Prototype.hasPermission(self: Permission, permission: permissionTypes.Permissions) + return bit.band(self.permission, permissionTypes.Permissions[permission]) == permissionTypes.Permissions[permission] +end + +function Permission.Interface.new(permission: string): Permission + local self = setmetatable( + { + permission = tonumber(permission), + } :: Permission, + { __index = Permission.Prototype } + ) + + return self +end + +export type Permission = typeof(Permission.Prototype) & { + permission: number, +} + +return Permission.Interface diff --git a/packages/classes/src/presence.luau b/packages/classes/src/presence.luau new file mode 100644 index 0000000..a81114e --- /dev/null +++ b/packages/classes/src/presence.luau @@ -0,0 +1,47 @@ +--[[ + Implementation of the Discord Presence class in Luau + + https://discord.com/developers/docs/topics/gateway-events#update-presence +]] + +local datetime = require("@std-polyfills/datetime") + +local apiTypes = require("@api-types/apiTypes") + +local activity = require("@classes/activity/activity") + +local Presence = {} + +Presence.Interface = {} +Presence.Prototype = {} + +function Presence.Prototype.sync(self: Presence, presenceData: apiTypes.PresenceObject) + local activities = {} + + for _, activityData in ipairs(presenceData.activities) do + table.insert(activities, activity.new(activityData)) + end + + self.activities = activities + + self.since = datetime.fromUnixTimestamp(presenceData.since) + self.status = presenceData.status + self.afk = presenceData.afk +end + +function Presence.Interface.new(presenceData: apiTypes.PresenceObject): Presence + local self = setmetatable({} :: Presence, { __index = Presence.Prototype }) + + self:sync(presenceData) + + return self +end + +export type Presence = typeof(Presence.Prototype) & { + since: datetime.DateTime, + activities: { activity.Activity }, + status: string, + afk: boolean, +} + +return Presence.Interface diff --git a/packages/classes/src/reaction/countDetails.luau b/packages/classes/src/reaction/countDetails.luau new file mode 100644 index 0000000..7545c1e --- /dev/null +++ b/packages/classes/src/reaction/countDetails.luau @@ -0,0 +1,32 @@ +--[[ + Implementation of the Discord CountDetails class in Luau + + https://discord.com/developers/docs/resources/message#reaction-count-details-object +]] + +local apiTypes = require("@api-types/apiTypes") + +local CountDetails = {} + +CountDetails.Interface = {} +CountDetails.Prototype = {} + +function CountDetails.Prototype.sync(self: CountDetails, reactionCountDetailsData: apiTypes.ReactionCountDetailsObject) + self.burst = reactionCountDetailsData.burst + self.normal = reactionCountDetailsData.normal +end + +function CountDetails.Interface.new(reactionCountDetailsData: apiTypes.ReactionCountDetailsObject): CountDetails + local self = setmetatable({} :: CountDetails, { __index = CountDetails.Prototype }) + + self:sync(reactionCountDetailsData) + + return self +end + +export type CountDetails = typeof(CountDetails.Prototype) & { + burst: number, + normal: number, +} + +return CountDetails.Interface diff --git a/packages/classes/src/reaction/reaction.luau b/packages/classes/src/reaction/reaction.luau new file mode 100644 index 0000000..b098ae6 --- /dev/null +++ b/packages/classes/src/reaction/reaction.luau @@ -0,0 +1,43 @@ +--[[ + Implementation of the Discord Reaction class in Luau + + https://discord.com/developers/docs/resources/message#reaction-object-reaction-structure +]] + +local apiTypes = require("@api-types/apiTypes") + +local countDetails = require("@classes/reaction/countDetails") +local emoji = require("@classes/emoji") + +local Reaction = {} + +Reaction.Interface = {} +Reaction.Prototype = {} + +function Reaction.Prototype.sync(self: Reaction, reactionData: apiTypes.ReactionObject) + self.count = reactionData.count + self.countDetails = countDetails.new(reactionData.count_details) + self.me = reactionData.me + self.meBurst = reactionData.me_burst + self.emoji = emoji.new(reactionData.emoji) + self.burstColors = reactionData.burst_colors +end + +function Reaction.Interface.new(reactionData: apiTypes.ReactionObject): Reaction + local self = setmetatable({} :: Reaction, { __index = Reaction.Prototype }) + + self:sync(reactionData) + + return self +end + +export type Reaction = typeof(Reaction.Prototype) & { + count: number, + countDetails: countDetails.CountDetails, + me: boolean, + meBurst: boolean, + emoji: emoji.Emoji, + burstColors: { string }, +} + +return Reaction.Interface diff --git a/packages/classes/src/resolved.luau b/packages/classes/src/resolved.luau new file mode 100644 index 0000000..b9d09c6 --- /dev/null +++ b/packages/classes/src/resolved.luau @@ -0,0 +1,78 @@ +--[[ + Implementation of the Discord Resolved class in Luau + + https://discord.com/developers/docs/resources/message#attachment-object +]] + +local apiTypes = require("@api-types/apiTypes") + +local user = require("@classes/user") +local member = require("@classes/guild/member") +local role = require("@classes/guild/role") +local attachment = require("@classes/attachment") +local message = require("@classes/message/message") +local state = require("@classes/state") + +local Resolved = {} + +Resolved.Interface = {} +Resolved.Prototype = {} + +function Resolved.Prototype.sync(self: Resolved, resolvedData: apiTypes.ResolvedDataStructure) + for id, userData in next, resolvedData.users or {} do + self.users[id] = user.new(userData) + end + + for id, memberData in next, resolvedData.members or {} do + self.members[id] = member.new(memberData) + end + + for id, roleData in next, resolvedData.roles or {} do + self.roles[id] = role.new(roleData) + end + + -- for id, channelData in next, resolvedData.channels or {} do + -- self.channels[id] = channel.new(channelData) + -- end + + for id, messageData in next, resolvedData.messages or {} do + self.messages[id] = message.new(self.state, messageData) + end + + for id, attachmentData in next, resolvedData.attachments or {} do + self.attachments[id] = attachment.new(attachmentData) + end +end + +function Resolved.Interface.new(state: state.State, resolvedData: apiTypes.ResolvedDataStructure): Resolved + local self = setmetatable( + { + state = state, + + users = {}, + members = {}, + roles = {}, + -- channels = {}, + messages = {}, + attachments = {}, + } :: Resolved, + { __index = Resolved.Prototype } + ) + + self:sync(resolvedData) + + return self +end + +export type Resolved = typeof(Resolved.Prototype) & { + state: state.State, + + users: { [apiTypes.Snowflake]: user.User }, + members: { [apiTypes.Snowflake]: member.Member }, + roles: { [apiTypes.Snowflake]: role.Role }, + -- channels: { [apiTypes.Snowflake]: unknown }, -- fixme: we can't type the channels since channels require this message class. + messages: { [apiTypes.Snowflake]: Message }, + attachments: { [apiTypes.Snowflake]: attachment.Attachment }, +} + +return Resolved.Interface diff --git a/packages/classes/src/rest.luau b/packages/classes/src/rest.luau new file mode 100644 index 0000000..269f38d --- /dev/null +++ b/packages/classes/src/rest.luau @@ -0,0 +1,55 @@ +--[[ + REST represents an interface to manipulate the Discord REST API. + + When making any sort of REST request, it is important that this class is used so that we accurately + track the rate limits, as well as adding token into each request object. +]] + +local secret = require("@classes/secret") +local request = require("@rest/request") + +local REST = {} + +REST.Interface = {} +REST.Prototype = {} + +--[[ + Responsible for creating a new request object, this object can be used with any of the + @rest/* api endpoints. +]] +function REST.Prototype.newRequest(self: REST): request.Request + local newRequest = request.new({ + token = self.token.value, + restApiVersion = self.version, + }) + + return newRequest +end + +--[[ + Constructor for the REST class. +]] +function REST.Interface.new(token: secret.Secret, intents: number, version: number): REST + local self = setmetatable( + { + token = token, + + intents = intents, + version = version, + } :: REST, + { + __index = REST.Prototype, + } + ) + + return self +end + +export type REST = typeof(REST.Prototype) & { + token: secret.Secret, + + intents: number, + version: number, +} + +return REST.Interface diff --git a/packages/classes/src/secret.luau b/packages/classes/src/secret.luau new file mode 100644 index 0000000..dccdf64 --- /dev/null +++ b/packages/classes/src/secret.luau @@ -0,0 +1,64 @@ +--[[ + Implementation of a basic secret object in Luau, primarily used to store bot tokens so that + they can't be accidentally leaked into the console through a `print` +]] + +local Secret = {} + +Secret.Interface = {} +Secret.Prototype = {} + +function Secret.Interface.new(value: T): Secret + local self = setmetatable( + { + value = value, + } :: Secret, + { + __concat = function(_self: Secret, otherValue: string) + assert(typeof(_self.value) == "string", "Cannot concat a Secret with a non-string value.") + + return _self.value .. otherValue + end, + __eq = function(_self: Secret, otherValue: string) + return _self.value == otherValue + end, + __len = function(_self: Secret) + assert( + typeof(_self.value) == "table" or typeof(_self.value) == "string", + "Cannot length a Secret with a non-table/non-string value." + ) + + return #_self.value + end, + + __index = Secret.Prototype, + __metatabble = "The metatable is locked.", + __newindex = function() + error(`Cannot modify a Secret.`) + end, + __tostring = function(_self: Secret) + local typeOfValue = typeof(_self.value) + + if typeOfValue == "string" then + local value = (_self.value :: any) :: string + + if #value < 20 then + return "Secret(" .. string.rep("#", #value) .. ")" + end + + return `Secret({string.sub(value, 1, #value - 20)}####################)` + else + return `Secret({typeOfValue})` + end + end, + } + ) + + return self +end + +export type Secret = typeof(Secret.Prototype) & { + value: T, +} + +return Secret.Interface diff --git a/packages/classes/src/state.luau b/packages/classes/src/state.luau new file mode 100644 index 0000000..b24995a --- /dev/null +++ b/packages/classes/src/state.luau @@ -0,0 +1,87 @@ +--[[ + State class, this class is responsible for storing the state of the bot as + as well as providing a way to interact with the state. + + This class is responsible for storing the following elements: + - Token + - Discord Cache + - Discord REST API + - Discord Gateway Manager + +]] + +local apiTypes = require("@api-types/apiTypes") + +local secret = require("@classes/secret") +local cache = require("@classes/cache") +local rest = require("@classes/rest") + +local webSocketManager = require("@websocket/manager") + +local State = {} + +State.Interface = {} +State.Prototype = {} + +function State.Prototype.setApplicationId(self: State, applicationId: apiTypes.Snowflake) + self.applicationId = applicationId +end + +--[[ + Constructor for the State class. +]] +function State.Interface.new(token: string, intents: number, version: number): State + local secretToken = secret.new(token) + + local self = setmetatable( + { + token = secretToken, + applicationId = "", + + rest = rest.new(secretToken, intents, version), + webSocketManager = webSocketManager.new({ + token = secretToken, + intents = intents, + webSocketVersion = version, + largeThreshold = 250, + }), + + intents = intents, + version = version, + + cache = { + guilds = cache.new(30 * 60 * 60), + users = cache.new(30 * 60 * 60), + channels = cache.new(30 * 60 * 60), + roles = cache.new(30 * 60 * 60), + emojis = cache.new(30 * 60 * 60), + }, + } :: State, + { + __index = State.Prototype, + } + ) + + return self +end + +export type State = typeof(State.Prototype) & { + token: secret.Secret, + applicationId: apiTypes.Snowflake, + + rest: rest.REST, + webSocketManager: webSocketManager.Manager, + + intents: number, + version: number, + + cache: { + guilds: cache.Cache, + users: cache.Cache, + channels: cache.Cache, + roles: cache.Cache, + emojis: cache.Cache, + }, +} + +return State.Interface diff --git a/packages/classes/src/sticker.luau b/packages/classes/src/sticker.luau new file mode 100644 index 0000000..44e7089 --- /dev/null +++ b/packages/classes/src/sticker.luau @@ -0,0 +1,53 @@ +--[[ + Implementation of the Discord Sticker class in Luau + + https://discord.com/developers/docs/resources/sticker#sticker-object +]] + +local apiTypes = require("@api-types/apiTypes") +local stickerTypes = require("@api-types/sticker") + +local user = require("@classes/user") + +local Sticker = {} + +Sticker.Interface = {} +Sticker.Prototype = {} + +function Sticker.Prototype.sync(self: Sticker, stickerData: apiTypes.StickerObject) + self.id = stickerData.id + self.packId = stickerData.pack_id + self.name = stickerData.name + self.description = stickerData.description + self.tags = stickerData.tags + self.type = stickerTypes.StickerTypes[stickerData.type] + self.formatType = stickerTypes.StickerFormatTypes[stickerData.format_type] + self.available = stickerData.available + self.guildId = stickerData.guild_id + self.user = stickerData.user and user.new(stickerData.user) + self.sortValue = stickerData.sort_value +end + +function Sticker.Interface.new(stickerData: apiTypes.StickerObject): Sticker + local self = setmetatable({} :: Sticker, { __index = Sticker.Prototype }) + + self:sync(stickerData) + + return self +end + +export type Sticker = typeof(Sticker.Prototype) & { + id: apiTypes.Snowflake, + packId: apiTypes.Snowflake?, + name: string, + description: string?, + tags: string, + type: stickerTypes.StickerType, + formatType: stickerTypes.StickerFormatType, + available: boolean?, + guildId: apiTypes.Snowflake?, + user: user.User?, + sortValue: number?, +} + +return Sticker.Interface diff --git a/packages/classes/src/threadMember.luau b/packages/classes/src/threadMember.luau new file mode 100644 index 0000000..29f5fde --- /dev/null +++ b/packages/classes/src/threadMember.luau @@ -0,0 +1,46 @@ +--[[ + Implementation of the Discord ThreadMember class in Luau + + https://discord.com/developers/docs/resources/channel#thread-member-object +]] + +local datetime = require("@std-polyfills/datetime") + +local apiTypes = require("@api-types/apiTypes") + +local threadMemberBitflag = require("@classes/bitflags/threadMember") + +local member = require("@classes/guild/member") + +local ThreadMember = {} + +ThreadMember.Interface = {} +ThreadMember.Prototype = {} + +function ThreadMember.Prototype.sync(self: ThreadMember, threadMemberData: apiTypes.ThreadMemberObject) + self.joinTimestamp = datetime.fromIsoDate(threadMemberData.join_timestamp) + + self.flags = threadMemberData.flags and threadMemberBitflag.new(threadMemberData.flags) + self.member = threadMemberData.member and member.new(threadMemberData.member) + + self.id = threadMemberData.id + self.userId = threadMemberData.user_id +end + +function ThreadMember.Interface.new(threadMemberData: apiTypes.ThreadMemberObject): ThreadMember + local self = setmetatable({} :: ThreadMember, { __index = ThreadMember.Prototype }) + + self:sync(threadMemberData) + + return self +end + +export type ThreadMember = typeof(ThreadMember.Prototype) & { + id: apiTypes.Snowflake?, + userId: apiTypes.Snowflake?, + joinTimestamp: datetime.DateTime, + flags: threadMemberBitflag.ThreadMemberBitflag, + member: member.Member?, +} + +return ThreadMember.Interface diff --git a/packages/classes/src/user.luau b/packages/classes/src/user.luau new file mode 100644 index 0000000..f01bbc7 --- /dev/null +++ b/packages/classes/src/user.luau @@ -0,0 +1,73 @@ +--[[ + Implementation of the Discord User class in Luau + + https://discord.com/developers/docs/resources/user#user-object +]] + +local apiTypes = require("@api-types/apiTypes") +local userTypes = require("@api-types/user") + +local avatarDecoration = require("@classes/avatarDecoration") + +local User = {} + +User.Interface = {} +User.Prototype = {} + +function User.Prototype.sync(self: User, userData: apiTypes.UserObject) + self.username = userData.username + self.premiumType = userTypes.NitroType[userData.premium_type] + self.discriminator = userData.discriminator + self.globalName = userData.global_name + self.avatar = userData.avatar + self.bot = userData.bot + self.system = userData.system + self.mfaEnabled = userData.mfa_enabled + self.banner = userData.banner + self.accentColor = userData.accent_color + self.locale = userData.locale + self.verified = userData.verified + self.email = userData.email + self.flags = userData.flags + self.publicFlags = userData.public_flags + self.avatarDecorationData = userData.avatar_decoration_data + and avatarDecoration.new(userData.avatar_decoration_data.asset, userData.avatar_decoration_data.sku_id) +end + +function User.Interface.new(userData: apiTypes.UserObject): User + local self = setmetatable( + { + id = userData.id, + } :: User, + { __index = User.Prototype } + ) + + if userData then + self:sync(userData) + end + + return self +end + +export type User = typeof(User.Prototype) & { + id: apiTypes.Snowflake, + + username: string, + discriminator: string, + globalName: string, + avatar: string, + bot: boolean?, + system: boolean?, + mfaEnabled: boolean?, + banner: string?, + accentColor: number?, + locale: apiTypes.LanguageLocales?, + verified: boolean?, + email: string?, + flags: number?, + premiumType: userTypes.NitroType?, + publicFlags: number?, + avatarDecorationData: avatarDecoration.AvatarDecoration?, +} + +return User.Interface diff --git a/packages/core/README.md b/packages/core/README.md new file mode 100644 index 0000000..80d19b9 --- /dev/null +++ b/packages/core/README.md @@ -0,0 +1,9 @@ +
+

+ discord-luau +

+
+ +## DiscordLuau - Core + +This package represents the core of the Discord Luau Library. Developers will be interacting with this package to create their own Discord Bot/Application. \ No newline at end of file diff --git a/packages/core/src/bot.luau b/packages/core/src/bot.luau new file mode 100644 index 0000000..e60b226 --- /dev/null +++ b/packages/core/src/bot.luau @@ -0,0 +1,443 @@ +local datetime = require("@std-polyfills/datetime") + +local future = require("@vendor/future") +local logger = require("@vendor/logger") +local emitter = require("@vendor/emitter") + +local gateway = require("@rest/gateway") + +local gatewayTypes = require("@api-types/gateway/types") +local apiTypes = require("@api-types/apiTypes") + +local receiveEvents = require("@api-types/gateway/receiveEvents") + +local application = require("@classes/application/application") +local guild = require("@classes/guild/guild") +local unavailableGuild = require("@classes/guild/unavailableGuild") +local user = require("@classes/user") +local member = require("@classes/guild/member") +local message = require("@classes/message/message") + +local autocompleteInteraction = require("@classes/interaction/types/autocomplete") +local commandInteraction = require("@classes/interaction/types/command") +local componentInteraction = require("@classes/interaction/types/component") +local modalInteraction = require("@classes/interaction/types/modal") +local pingInteraction = require("@classes/interaction/types/ping") + +local dmChannel = require("@classes/channels/types/dm") +local groupDmChannel = require("@classes/channels/types/groupDm") +local guildAnnouncementChannel = require("@classes/channels/types/guildAnnouncement") +local guildCategoryChannel = require("@classes/channels/types/guildCategory") +local guildDirectoryChannel = require("@classes/channels/types/guildDirectory") +local guildStageVoiceChannel = require("@classes/channels/types/guildStageVoice") +local guildTextChannel = require("@classes/channels/types/guildText") +local guildVoiceChannel = require("@classes/channels/types/guildVoice") + +local state = require("@classes/state") + +local DISCORD_VERSION = 10 + +local Bot = {} + +Bot.Interface = {} +Bot.Prototype = {} + +--[[ + Queries the Discord Gateway for bot information. + + This function sends a request to the Discord Gateway to retrieve information + about the bot, such as the recommended number of shards and session start limit. +]] +function Bot.Prototype.queryGatewayInformation(self: Bot) + return future.new(function() + local request = self.state.rest:newRequest() + + local status, response = gateway.getGatewayBotAsync(request):await() + + assert(status == "Fulfilled", tostring(response)) + + return response + end) +end + +--[[ + Connects the bot to the Discord gateway asynchronously. + + This function performs the following steps: + 1. Queries gateway information + 2. Asserts that the query was successful + 3. Connects to the WebSocket using the obtained gateway information + + Returns a future that resolves when the connection is established. +]] +function Bot.Prototype.connectAsync(self: Bot) + return future.new(function() + local status, gatewayInformation = self:queryGatewayInformation():await() + + assert(status == "Fulfilled", gatewayInformation) + + self.state.webSocketManager.onAllShardsReady:listen(function(data) + local application = application.new(self.state, data.payload.d.application) + local user = user.new(data.payload.d.user) + + self.application = application + self.user = user + + self.state:setApplicationId(application.id) + + self.onAllShardsReady:invoke() + end) + + self.state.webSocketManager.onDispatch:listen(function(object: { + shardId: number, + event: receiveEvents.ReceiveEvent, + payload: gatewayTypes.Payload, + }) + if object.event == receiveEvents["Ready"] then + self.onReady:invoke(object.shardId) + elseif object.event == receiveEvents["MessageCreate"] then + local typedObject = object.payload :: gatewayTypes.MessageCreatePayload + + self.onMessage:invoke(message.new(self.state, typedObject.d)) + elseif object.event == receiveEvents["MessageUpdate"] then + local typedObject = object.payload :: gatewayTypes.MessageUpdatePayload + + self.onMessageChanged:invoke(message.new(self.state, typedObject.d)) + elseif object.event == receiveEvents["MessageDelete"] then + local typedObject = object.payload :: gatewayTypes.MessageDeletePayload + + self.onMessageDeleted:invoke({ + messageId = typedObject.d.id, + channelId = typedObject.d.channel_id, + guildId = typedObject.d.guild_id, + }) + elseif object.event == receiveEvents["MessageDeleteBulk"] then + local typedObject = object.payload :: gatewayTypes.MessageDeleteBulkPayload + + self.onMessageBulkDeleted:invoke({ + channelId = typedObject.d.channel_id, + guildId = typedObject.d.guild_id, + ids = typedObject.d.ids, + }) + elseif object.event == receiveEvents["ChannelCreate"] then + local typedObject = object.payload :: gatewayTypes.CreateChannelPayload + + self.state.cache.channels:set(typedObject.d.id :: apiTypes.Snowflake, typedObject.d) + + if typedObject.d.type == 0 then + self.onTextChannelCreate:invoke(guildTextChannel.new(typedObject.d)) + elseif typedObject.d.type == 1 then + self.onDMChannelCreate:invoke(dmChannel.new(typedObject.d)) + elseif typedObject.d.type == 2 then + self.onVoiceChannelCreate:invoke(guildVoiceChannel.new(typedObject.d)) + elseif typedObject.d.type == 3 then + self.onGroupDMChannelCreate:invoke(groupDmChannel.new(typedObject.d)) + elseif typedObject.d.type == 4 then + self.onCategoryChannelCreate:invoke(guildCategoryChannel.new(typedObject.d)) + elseif typedObject.d.type == 5 then + self.onAnnouncementChannelCreate:invoke(guildAnnouncementChannel.new(typedObject.d)) + elseif typedObject.d.type == 13 then + self.onStageVoiceChannelCreate:invoke(guildStageVoiceChannel.new(typedObject.d)) + elseif typedObject.d.type == 14 then + self.onDirectoryChannelCreate:invoke(guildDirectoryChannel.new(typedObject.d)) + end + elseif object.event == receiveEvents["ChannelUpdate"] then + local typedObject = object.payload :: gatewayTypes.UpdateChannelPayload + + self.state.cache.channels:set(typedObject.d.id :: apiTypes.Snowflake, typedObject.d) + + if typedObject.d.type == 0 then + self.onTextChannelUpdated:invoke(guildTextChannel.new(typedObject.d)) + elseif typedObject.d.type == 1 then + self.onDMChannelUpdated:invoke(dmChannel.new(typedObject.d)) + elseif typedObject.d.type == 2 then + self.onVoiceChannelUpdated:invoke(guildVoiceChannel.new(typedObject.d)) + elseif typedObject.d.type == 3 then + self.onGroupDMChannelUpdated:invoke(groupDmChannel.new(typedObject.d)) + elseif typedObject.d.type == 4 then + self.onCategoryChannelUpdated:invoke(guildCategoryChannel.new(typedObject.d)) + elseif typedObject.d.type == 5 then + self.onAnnouncementChannelUpdated:invoke(guildAnnouncementChannel.new(typedObject.d)) + elseif typedObject.d.type == 13 then + self.onStageVoiceChannelUpdated:invoke(guildStageVoiceChannel.new(typedObject.d)) + elseif typedObject.d.type == 14 then + self.onDirectoryChannelUpdated:invoke(guildDirectoryChannel.new(typedObject.d)) + end + elseif object.event == receiveEvents["ChannelDelete"] then + local typedObject = object.payload :: gatewayTypes.DeleteChannelPayload + + if typedObject.d.type == 0 then + self.onTextChannelDeleted:invoke(guildTextChannel.new(typedObject.d)) + elseif typedObject.d.type == 1 then + self.onDMChannelDeleted:invoke(dmChannel.new(typedObject.d)) + elseif typedObject.d.type == 2 then + self.onVoiceChannelDeleted:invoke(guildVoiceChannel.new(typedObject.d)) + elseif typedObject.d.type == 3 then + self.onGroupDMChannelDeleted:invoke(groupDmChannel.new(typedObject.d)) + elseif typedObject.d.type == 4 then + self.onCategoryChannelDeleted:invoke(guildCategoryChannel.new(typedObject.d)) + elseif typedObject.d.type == 5 then + self.onAnnouncementChannelDeleted:invoke(guildAnnouncementChannel.new(typedObject.d)) + elseif typedObject.d.type == 13 then + self.onStageVoiceChannelDeleted:invoke(guildStageVoiceChannel.new(typedObject.d)) + elseif typedObject.d.type == 14 then + self.onDirectoryChannelDeleted:invoke(guildDirectoryChannel.new(typedObject.d)) + end + elseif object.event == receiveEvents["ChannelPinsUpdate"] then + local typedObject = object.payload :: gatewayTypes.ChannelPinsUpdatePayload + + self.onChannelPinsUpdate:invoke({ + channelId = typedObject.d.channel_id, + guildId = typedObject.d.guild_id, + lastPinTimestamp = typedObject.d.last_pin_timestamp + and datetime.fromIsoDate(typedObject.d.last_pin_timestamp), + }) + elseif object.event == receiveEvents["UserUpdate"] then + local typedObject = object.payload :: gatewayTypes.UserUpdatePayload + + self.state.cache.users:set(typedObject.d.id :: apiTypes.Snowflake, typedObject.d) + + self.onUserUpdated:invoke(user.new(typedObject.d)) + elseif object.event == receiveEvents["GuildCreate"] then + local typedObject = object.payload :: gatewayTypes.GuildCreatePayload + + if typedObject.d.unavailable then + self.onGuildCreate:invoke(unavailableGuild.new(self.state, typedObject.d.id :: string)) + else + -- fixme: using 'any' below because otherwise typedObject.d doesn't work with apiTypes.GuildObject? + local guildData = typedObject.d :: any + + self.state.cache.guilds:set(typedObject.d.id :: apiTypes.Snowflake, guildData) + + self.onGuildCreate:invoke(guild.new(self.state, guildData)) + end + elseif object.event == receiveEvents["GuildUpdate"] then + local typedObject = object.payload :: gatewayTypes.GuildUpdatePayload + + self.state.cache.guilds:set(typedObject.d.id :: apiTypes.Snowflake, typedObject.d) + + self.onGuildUpdate:invoke(guild.new(self.state, typedObject.d)) + elseif object.event == receiveEvents["GuildDelete"] then + local typedObject = object.payload :: gatewayTypes.GuildDeletePayload + + self.onGuildDelete:invoke(unavailableGuild.new(self.state, typedObject.d.id)) + elseif object.event == receiveEvents["GuildBanAdd"] then + local typedObject = object.payload :: gatewayTypes.GuildBanAddPayload + + self.state.cache.users:set(typedObject.d.user.id :: apiTypes.Snowflake, typedObject.d.user) + + self.onGuildMemberBanned:invoke({ + guildId = typedObject.d.guild_id, + user = user.new(typedObject.d.user), + }) + elseif object.event == receiveEvents["GuildBanRemove"] then + local typedObject = object.payload :: gatewayTypes.GuildBanRemovePayload + + self.state.cache.users:set(typedObject.d.user.id :: apiTypes.Snowflake, typedObject.d.user) + + self.onGuildMemberUnbanned:invoke({ + guildId = typedObject.d.guild_id, + user = user.new(typedObject.d.user), + }) + elseif object.event == receiveEvents["GuildMemberAdd"] then + local typedObject = object.payload :: gatewayTypes.GuildMemberAddPayload + + self.onGuildMemberJoined:invoke({ + guildId = typedObject.d.guild_id, + member = member.new(typedObject.d), + }) + elseif object.event == receiveEvents["GuildMemberRemove"] then + local typedObject = object.payload :: gatewayTypes.GuildMemberRemovePayload + + self.onGuildMemberLeft:invoke({ + guildId = typedObject.d.guild_id, + user = user.new(typedObject.d.user), + }) + elseif object.event == receiveEvents["GuildMemberUpdate"] then + local typedObject = object.payload :: gatewayTypes.GuildMemberUpdatePayload + + self.onGuildMemberUpdated:invoke({ + guildId = typedObject.d.guild_id, + roles = typedObject.d.roles, + user = user.new(typedObject.d.user), + nick = typedObject.d.nick, + avatar = typedObject.d.avatar, + joinedAt = typedObject.d.joined_at and datetime.fromIsoDate(typedObject.d.joined_at), + premiumSince = typedObject.d.premium_since and datetime.fromIsoDate(typedObject.d.premium_since), + deaf = typedObject.d.deaf, + mute = typedObject.d.mute, + pending = typedObject.d.pending, + communicationDisabledUntil = typedObject.d.communication_disabled_until + and datetime.fromIsoDate(typedObject.d.communication_disabled_until), + }) + elseif object.event == receiveEvents["InteractionCreate"] then + local typedObject = object.payload :: gatewayTypes.InteractionCreatePayload + + if typedObject.d.type == 1 then + self.onPingInteraction:invoke(pingInteraction.new(self.state, typedObject.d)) + elseif typedObject.d.type == 2 then + self.onCommandInteraction:invoke(commandInteraction.new(self.state, typedObject.d)) + elseif typedObject.d.type == 3 then + self.onComponentInteraction:invoke(componentInteraction.new(self.state, typedObject.d)) + elseif typedObject.d.type == 4 then + self.onAutocompleteInteraction:invoke(autocompleteInteraction.new(self.state, typedObject.d)) + elseif typedObject.d.type == 5 then + self.onModalInteraction:invoke(modalInteraction.new(self.state, typedObject.d)) + else + error(`Unknown interaction type: {typedObject.d.type}`) + end + end + end) + + self.state.webSocketManager:connectAsync(gatewayInformation) + self.state.webSocketManager.onConnected:wait() + end) +end + +function Bot.Interface.new(options: { + token: string, + intents: number, +}) + local self = setmetatable( + { + state = state.new(options.token, options.intents, DISCORD_VERSION), + logger = logger.new("Bot"), + + onReady = emitter.new(), + onAllShardsReady = emitter.new(), + onMessage = emitter.new(), + onMessageChanged = emitter.new(), + onMessageDeleted = emitter.new(), + onMessageBulkDeleted = emitter.new(), + + onGroupDMChannelCreate = emitter.new(), + onCategoryChannelCreate = emitter.new(), + onAnnouncementChannelCreate = emitter.new(), + onDirectoryChannelCreate = emitter.new(), + onDMChannelCreate = emitter.new(), + onTextChannelCreate = emitter.new(), + onVoiceChannelCreate = emitter.new(), + onStageVoiceChannelCreate = emitter.new(), + + onGroupDMChannelUpdated = emitter.new(), + onCategoryChannelUpdated = emitter.new(), + onAnnouncementChannelUpdated = emitter.new(), + onDirectoryChannelUpdated = emitter.new(), + onDMChannelUpdated = emitter.new(), + onTextChannelUpdated = emitter.new(), + onVoiceChannelUpdated = emitter.new(), + onStageVoiceChannelUpdated = emitter.new(), + + onGroupDMChannelDeleted = emitter.new(), + onCategoryChannelDeleted = emitter.new(), + onAnnouncementChannelDeleted = emitter.new(), + onDirectoryChannelDeleted = emitter.new(), + onDMChannelDeleted = emitter.new(), + onTextChannelDeleted = emitter.new(), + onVoiceChannelDeleted = emitter.new(), + onStageVoiceChannelDeleted = emitter.new(), + + onUserUpdated = emitter.new(), + onChannelPinsUpdate = emitter.new(), + + onGuildCreate = emitter.new(), + onGuildUpdate = emitter.new(), + onGuildDelete = emitter.new(), + + onGuildMemberBanned = emitter.new(), + onGuildMemberUnbanned = emitter.new(), + + onGuildMemberJoined = emitter.new(), + onGuildMemberLeft = emitter.new(), + onGuildMemberUpdated = emitter.new(), + + onPingInteraction = emitter.new(), + onCommandInteraction = emitter.new(), + onComponentInteraction = emitter.new(), + onAutocompleteInteraction = emitter.new(), + onModalInteraction = emitter.new(), + } :: Bot, + { __index = Bot.Prototype } + ) + + return self +end + +export type Bot = + typeof(Bot.Prototype) + & { + state: state.State, + logger: logger.Logger, + + application: application.Application?, + user: user.User?, + + onReady: emitter.Emitter, + onAllShardsReady: emitter.Emitter<()>, + + onMessage: emitter.Emitter, + onMessageChanged: emitter.Emitter, + onMessageDeleted: emitter.Emitter<{ messageId: apiTypes.Snowflake, channelId: apiTypes.Snowflake, guildId: apiTypes.Snowflake }>, + onMessageBulkDeleted: emitter.Emitter<{ channelId: string, guildId: string, ids: { string } }>, + + onGroupDMChannelCreate: emitter.Emitter, + onCategoryChannelCreate: emitter.Emitter, + onAnnouncementChannelCreate: emitter.Emitter, + onDirectoryChannelCreate: emitter.Emitter, + onDMChannelCreate: emitter.Emitter, + onTextChannelCreate: emitter.Emitter, + onVoiceChannelCreate: emitter.Emitter, + onStageVoiceChannelCreate: emitter.Emitter, + + onGroupDMChannelUpdated: emitter.Emitter, + onCategoryChannelUpdated: emitter.Emitter, + onAnnouncementChannelUpdated: emitter.Emitter, + onDirectoryChannelUpdated: emitter.Emitter, + onDMChannelUpdated: emitter.Emitter, + onTextChannelUpdated: emitter.Emitter, + onVoiceChannelUpdated: emitter.Emitter, + onStageVoiceChannelUpdated: emitter.Emitter, + + onGroupDMChannelDeleted: emitter.Emitter, + onCategoryChannelDeleted: emitter.Emitter, + onAnnouncementChannelDeleted: emitter.Emitter, + onDirectoryChannelDeleted: emitter.Emitter, + onDMChannelDeleted: emitter.Emitter, + onTextChannelDeleted: emitter.Emitter, + onVoiceChannelDeleted: emitter.Emitter, + onStageVoiceChannelDeleted: emitter.Emitter, + + onUserUpdated: emitter.Emitter, + onChannelPinsUpdate: emitter.Emitter<{ guildId: string, channelId: string, lastPinTimestamp: datetime.DateTime? }>, + + onGuildCreate: emitter.Emitter, + onGuildUpdate: emitter.Emitter, + onGuildDelete: emitter.Emitter, + + onGuildMemberBanned: emitter.Emitter<{ guildId: string, user: user.User }>, + onGuildMemberUnbanned: emitter.Emitter<{ guildId: string, user: user.User }>, + onGuildMemberJoined: emitter.Emitter, + onGuildMemberLeft: emitter.Emitter<{ guildId: string, user: user.User }>, + + onGuildMemberUpdated: emitter.Emitter<{ + guildId: string, + roles: { string }, + user: user.User, + nick: string?, + avatar: string, + joinedAt: datetime.DateTime, + premiumSince: datetime.DateTime, + deaf: boolean?, + mute: boolean?, + pending: boolean?, + communicationDisabledUntil: datetime.DateTime?, + }>, + + onPingInteraction: emitter.Emitter, + onCommandInteraction: emitter.Emitter, + onComponentInteraction: emitter.Emitter, + onAutocompleteInteraction: emitter.Emitter, + onModalInteraction: emitter.Emitter, + } + +return Bot.Interface diff --git a/packages/core/src/init.luau b/packages/core/src/init.luau new file mode 100644 index 0000000..7eb2d48 --- /dev/null +++ b/packages/core/src/init.luau @@ -0,0 +1,11 @@ +local bot = require("@core/bot") + +local DiscordLuau = {} + +DiscordLuau.Bot = bot +export type Bot = bot.Bot + +return DiscordLuau + +-- tehe, secret egg? The thunder before the storm? Who knows - all I know is that Discord-Luau is going to be super-charged after this.. +-- this might take a while.. sorry! diff --git a/packages/extensions/README.md b/packages/extensions/README.md new file mode 100644 index 0000000..680ff2b --- /dev/null +++ b/packages/extensions/README.md @@ -0,0 +1,11 @@ +
+

+ discord-luau +

+
+ +## DiscordLuau - Extensions + +This package provides developers with a way to develop extensions for the Discord Luau Library. Extensions are a way to extend the functionality of the Discord Luau Library. + +The idea behind this, is to allow developers to create their own packages that can be used to extend the functionality of the Discord Luau Library. \ No newline at end of file diff --git a/packages/extensions/src/snowflake.luau b/packages/extensions/src/snowflake.luau new file mode 100644 index 0000000..1fd05bc --- /dev/null +++ b/packages/extensions/src/snowflake.luau @@ -0,0 +1,72 @@ +--[[ + Wrapper around Discord snowflakes, allwowing developers to query information about a snowflake +]] + +local datetime = require("@std-polyfills/datetime") + +local bit = require("@vendor/bit") + +local DISCORD_EPOCH = 1420070400000 + +local Snowflake = {} + +Snowflake.Interface = {} +Snowflake.Prototype = {} + +--[[ + Returns the timestamp of the snowflake +]] +function Snowflake.Prototype.getTimestamp(self: Snowflake) + local snowflake = tonumber(self.snowflake) :: number + local timestamp = bit.shift(snowflake, 22) + DISCORD_EPOCH + + return datetime.fromUnixTimestamp(timestamp / 1000) +end + +--[[ + Returns the internal worker ID of the snowflake +]] +function Snowflake.Prototype.getInternalWorkerId(self: Snowflake) + local snowflake = tonumber(self.snowflake) :: number + local internalWorkerId = bit.shift(bit.band(snowflake, 0x3E0000), 17) + + return internalWorkerId +end + +--[[ + Returns the internal process ID of the snowflake +]] +function Snowflake.Prototype.getInternalProcessId(self: Snowflake) + local snowflake = tonumber(self.snowflake) :: number + local internalProcessId = bit.shift(bit.band(snowflake, 0x1F0000), 12) + + return internalProcessId +end + +--[[ + Returns the increment of the snowflake +]] +function Snowflake.Prototype.getIncrement(self: Snowflake) + local snowflake = tonumber(self.snowflake) :: number + local internalProcessId = bit.band(snowflake, 0xFFF) + + return internalProcessId +end + +--[[ + Creates a new snowflake +]] +function Snowflake.Interface.new(snowflake: string) + return setmetatable( + { + snowflake = snowflake, + } :: Snowflake, + { __index = Snowflake.Prototype } + ) +end + +export type Snowflake = typeof(Snowflake.Prototype) & { + snowflake: string, +} + +return Snowflake.Interface diff --git a/packages/rest/README.md b/packages/rest/README.md new file mode 100644 index 0000000..d1770ea --- /dev/null +++ b/packages/rest/README.md @@ -0,0 +1,9 @@ +
+

+ discord-luau +

+
+ +## DiscordLuau - REST + +This package provides developers with a way to interact with the Discord API. \ No newline at end of file diff --git a/packages/rest/src/application.luau b/packages/rest/src/application.luau new file mode 100644 index 0000000..e012ebb --- /dev/null +++ b/packages/rest/src/application.luau @@ -0,0 +1,53 @@ +local serde = require("@std-polyfills/serde") + +local request = require("@rest/request") +local future = require("@vendor/future") + +local restTypes = require("@api-types/rest/types") +local apiTypes = require("@api-types/apiTypes") +local restEndpoints = require("@api-types/rest/endpoints") + +--[[ + HTTP Rest API implementation for the following Resource: + + - https://discord.com/developers/docs/resources/application +]] +local Application = {} + +-- https://discord.com/developers/docs/resources/application#get-current-application +function Application.getCurrentApplicationAsync( + instance: request.Request, + applicationId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetCurrentApplication, applicationId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/application#edit-current-application +function Application.editCurrentApplicationAsync( + instance: request.Request, + applicationId: apiTypes.Snowflake, + jsonParams: restTypes.EditCurrentApplicationRequest +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PATCH") + instance:setUrl(string.format(restEndpoints.EditCurrentApplication, applicationId)) + instance:setBody(serde.encode("json", jsonParams, true)) + + instance:executeAsync():await() + end) +end + +return Application diff --git a/packages/rest/src/applicationRoleConnectionMetadata.luau b/packages/rest/src/applicationRoleConnectionMetadata.luau new file mode 100644 index 0000000..05ded77 --- /dev/null +++ b/packages/rest/src/applicationRoleConnectionMetadata.luau @@ -0,0 +1,57 @@ +local request = require("@rest/request") +local future = require("@vendor/future") + +local restTypes = require("@api-types/rest/types") +local apiTypes = require("@api-types/apiTypes") +local restEndpoints = require("@api-types/rest/endpoints") + +--[[ + HTTP Rest API implementation for the following Resource: + + - https://discord.com/developers/docs/resources/application-role-connection-metadata +]] +local ApplicationRoleConnectionMetadata = {} + +-- https://discord.com/developers/docs/resources/application-role-connection-metadata#get-application-role-connection-metadata-records +function ApplicationRoleConnectionMetadata.getApplicationRoleConnectionMetadataRecordsAsync( + instance: request.Request, + applicationId: apiTypes.Snowflake +): future.Future< + restTypes.GetCurrentApplicationResponse +> + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetApplicationRoleConnectionMetadataRecords, applicationId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/application-role-connection-metadata#update-application-role-connection-metadata-records +function ApplicationRoleConnectionMetadata.updateApplicationRoleConnectionMetadataRecordsAsync( + instance: request.Request, + applicationId: apiTypes.Snowflake +): future.Future< + restTypes.UpdateApplicationRoleConnectionMetadataRecordsResponse +> + return future.new(function() + instance:assertToken() + + instance:setMethod("PUT") + instance:setUrl(string.format(restEndpoints.UpdateApplicationRoleConnectionMetadataRecords, applicationId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +return ApplicationRoleConnectionMetadata diff --git a/packages/rest/src/auditLog.luau b/packages/rest/src/auditLog.luau new file mode 100644 index 0000000..a036683 --- /dev/null +++ b/packages/rest/src/auditLog.luau @@ -0,0 +1,165 @@ +local request = require("@rest/request") +local future = require("@vendor/future") + +local restTypes = require("@api-types/rest/types") +local apiTypes = require("@api-types/apiTypes") +local restEndpoints = require("@api-types/rest/endpoints") + +local ACTION_TYPE_MAPPING = table.freeze({ + GuildUpdate = 1, + ChannelCreate = 10, + ChannelUpdate = 11, + ChannelDelete = 12, + ChannelOverwriteCreate = 13, + ChannelOverwriteUpdate = 14, + ChannelOverwriteDelete = 15, + MemberKick = 20, + MemberPrune = 21, + MemberBanAdd = 22, + MemberBanRemove = 23, + MemberUpdate = 24, + MemberRoleUpdate = 25, + MemberMove = 26, + MemberDisconnect = 27, + BotAdd = 28, + RoleCreate = 30, + RoleUpdate = 31, + RoleDelete = 32, + InviteCreate = 40, + InviteUpdate = 41, + InviteDelete = 42, + WebhookCreate = 50, + WebhookUpdate = 51, + WebhookDelete = 52, + EmojiCreate = 60, + EmojiUpdate = 61, + EmojiDelete = 62, + MessageDelete = 72, + MessageBulkDelete = 73, + MessagePin = 74, + MessageUnpin = 75, + IntegrationCreate = 80, + IntegrationUpdate = 81, + IntegrationDelete = 82, + StageInstanceCreate = 83, + StageInstanceUpdate = 84, + StageInstanceDelete = 85, + StickerCreate = 90, + StickerUpdate = 91, + StickerDelete = 92, + GuildScheduledEventCreate = 100, + GuildScheduledEventUpdate = 101, + GuildScheduledEventDelete = 102, + ThreadCreate = 110, + ThreadUpdate = 111, + ThreadDelete = 112, + ApplicationCommandPermissionUpdate = 121, + AutoModerationRuleCreate = 140, + AutoModerationRuleUpdate = 141, + AutoModerationRuleDelete = 142, + AutoModerationBlockMessage = 143, + AutoModerationFlagToChannel = 144, + AutoModerationUserCommunicationDisabled = 145, + CreatorMonetizationRequestCreated = 150, + CreatorMonetizationTermsAccepted = 151, +}) + +--[[ + HTTP Rest API implementation for the following Resource: + + - https://discord.com/developers/docs/resources/audit-log +]] +local AuditLog = {} + +-- https://discord.com/developers/docs/resources/audit-log#get-guild-audit-log +function AuditLog.getGuildAuditLogAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + urlParams: { + userId: apiTypes.Snowflake?, + before: apiTypes.Snowflake?, + after: apiTypes.Snowflake?, + limit: number?, + actionType: ActionType, + } +): future.Future + return future.new(function() + instance:assertToken() + + instance:addUrlParam("user_id", urlParams.userId :: string) + instance:addUrlParam("before", urlParams.before :: string) + instance:addUrlParam("after", urlParams.after :: string) + instance:addUrlParam("limit", urlParams.limit and tostring(urlParams.limit) or (nil :: any)) + instance:addUrlParam( + "action_type", + urlParams.actionType and ACTION_TYPE_MAPPING[urlParams.actionType] or (nil :: any) + ) + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetGuildAuditLog, guildId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +export type ActionType = + "GuildUpdate" + | "ChannelCreate" + | "ChannelUpdate" + | "ChannelDelete" + | "ChannelOverwriteCreate" + | "ChannelOverwriteUpdate" + | "ChannelOverwriteDelete" + | "MemberKick" + | "MemberPrune" + | "MemberBanAdd" + | "MemberBanRemove" + | "MemberUpdate" + | "MemberRoleUpdate" + | "MemberMove" + | "MemberDisconnect" + | "BotAdd" + | "RoleCreate" + | "RoleUpdate" + | "RoleDelete" + | "InviteCreate" + | "InviteUpdate" + | "InviteDelete" + | "WebhookCreate" + | "WebhookUpdate" + | "WebhookDelete" + | "EmojiCreate" + | "EmojiUpdate" + | "EmojiDelete" + | "MessageDelete" + | "MessageBulkDelete" + | "MessagePin" + | "MessageUnpin" + | "IntegrationCreate" + | "IntegrationUpdate" + | "IntegrationDelete" + | "StageInstanceCreate" + | "StageInstanceUpdate" + | "StageInstanceDelete" + | "StickerCreate" + | "StickerUpdate" + | "StickerDelete" + | "GuildScheduledEventCreate" + | "GuildScheduledEventUpdate" + | "GuildScheduledEventDelete" + | "ThreadCreate" + | "ThreadUpdate" + | "ThreadDelete" + | "ApplicationCommandPermissionUpdate" + | "AutoModerationRuleCreate" + | "AutoModerationRuleUpdate" + | "AutoModerationRuleDelete" + | "AutoModerationBlockMessage" + | "AutoModerationFlagToChannel" + | "AutoModerationUserCommunicationDisabled" + +return AuditLog diff --git a/packages/rest/src/autoModeration.luau b/packages/rest/src/autoModeration.luau new file mode 100644 index 0000000..c887cc6 --- /dev/null +++ b/packages/rest/src/autoModeration.luau @@ -0,0 +1,122 @@ +local serde = require("@std-polyfills/serde") +local request = require("@rest/request") +local future = require("@vendor/future") + +local restTypes = require("@api-types/rest/types") +local restEndpoints = require("@api-types/rest/endpoints") +local apiTypes = require("@api-types/apiTypes") + +--[[ + HTTP Rest API implementation for the following Resource: + + - https://discord.com/developers/docs/resources/auto-moderation +]] +local AutoModeration = {} + +-- https://discord.com/developers/docs/resources/auto-moderation#list-auto-moderation-rules-for-guild +function AutoModeration.listAutoModerationRulesForGuildAsync( + instance: request.Request, + guildId: apiTypes.Snowflake +): future.Future< + restTypes.ListAutoModerationRulesForGuildResponse +> + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.ListAutoModerationRulesForGuild, guildId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/auto-moderation#get-auto-moderation-rule +function AutoModeration.getAutoModerationRulesForGuildAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + autoModerationRuleId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetAutoModerationRule, guildId, autoModerationRuleId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/auto-moderation#create-auto-moderation-rule +function AutoModeration.createAutoModerationRuleAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + jsonParams: restTypes.CreateAutoModerationRuleRequest, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("POST") + instance:setUrl(string.format(restEndpoints.CreateAutoModerationRule, guildId)) + instance:setBody(serde.encode("json", jsonParams, true)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/auto-moderation#modify-auto-moderation-rule +function AutoModeration.modifyAutoModerationRuleAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + autoModerationRuleId: apiTypes.Snowflake, + jsonParams: restTypes.CreateAutoModerationRuleRequest, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PATCH") + instance:setUrl(string.format(restEndpoints.ModifyAutoModerationRule, guildId, autoModerationRuleId)) + instance:setBody(serde.encode("json", jsonParams, true)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/auto-moderation#delete-auto-moderation-rule +function AutoModeration.deleteAutoModerationRuleAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + autoModerationRuleId: apiTypes.Snowflake, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PATCH") + instance:setUrl(string.format(restEndpoints.DeleteAutoModerationRule, guildId, autoModerationRuleId)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + instance:executeAsync():await() + end) +end + +return AutoModeration diff --git a/packages/rest/src/channel.luau b/packages/rest/src/channel.luau new file mode 100644 index 0000000..7d5ff4f --- /dev/null +++ b/packages/rest/src/channel.luau @@ -0,0 +1,596 @@ +local serde = require("@std-polyfills/serde") + +local request = require("@rest/request") +local future = require("@vendor/future") + +local restTypes = require("@api-types/rest/types") +local apiTypes = require("@api-types/apiTypes") +local restEndpoints = require("@api-types/rest/endpoints") + +--[[ + HTTP Rest API implementation for the following Resource: + + - https://discord.com/developers/docs/resources/channel +]] +local Channel = {} + +-- https://discord.com/developers/docs/resources/channel#get-channel +function Channel.getChannelAsync( + instance: request.Request, + channelId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetChannel, channelId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/channel#modify-channel +function Channel.modifyChannelAsync( + instance: request.Request, + channelId: apiTypes.Snowflake, + jsonParams: restTypes.ModifyChannelRequest, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PATCH") + instance:setUrl(string.format(restEndpoints.ModifyChannel, channelId)) + instance:setBody(serde.encode("json", jsonParams, true)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/channel#deleteclose-channel +function Channel.deleteOrCloseChannelAsync( + instance: request.Request, + channelId: apiTypes.Snowflake, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("DELETE") + instance:setUrl(string.format(restEndpoints.DeleteOrCloseChannel, channelId)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/channel#edit-channel-permissions +function Channel.editChannelPermissionsAsync( + instance: request.Request, + channelId: apiTypes.Snowflake, + overwriteId: apiTypes.Snowflake, + jsonParams: restTypes.EditChannelPermissionsRequest, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PUT") + instance:setUrl(string.format(restEndpoints.EditChannelPermissions, channelId, overwriteId)) + instance:setBody(serde.encode("json", jsonParams, true)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/channel#get-channel-invites +function Channel.getChannelInvitesAsync( + instance: request.Request, + channelId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetChannelInvites, channelId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/channel#create-channel-invite +function Channel.createChannelInviteAsync( + instance: request.Request, + channelId: apiTypes.Snowflake, + jsonParams: restTypes.CreateChannelInviteRequest, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("POST") + instance:setUrl(string.format(restEndpoints.CreateChannelInvite, channelId)) + instance:setBody(serde.encode("json", jsonParams, true)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/channel#delete-channel-permission +function Channel.deleteChannelPermissionAsync( + instance: request.Request, + channelId: apiTypes.Snowflake, + overwriteId: apiTypes.Snowflake, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("DELETE") + instance:setUrl(string.format(restEndpoints.DeleteChannelPermission, channelId, overwriteId)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/channel#follow-announcement-channel +function Channel.followAnnouncementChannelAsync( + instance: request.Request, + channelId: apiTypes.Snowflake, + jsonParams: restTypes.FollowAnnouncementChannelRequest, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("POST") + instance:setUrl(string.format(restEndpoints.FollowAnnouncementChannel, channelId)) + instance:setBody(serde.encode("json", jsonParams, true)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/channel#trigger-typing-indicator +function Channel.triggerTypingIndicatorAsync( + instance: request.Request, + channelId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("POST") + instance:setUrl(string.format(restEndpoints.TriggerTypingChannel, channelId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/channel#get-pinned-messages +function Channel.getPinnedMessagesAsync( + instance: request.Request, + channelId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetPinnedMessages, channelId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/channel#pin-message +function Channel.pinMessageAsync( + instance: request.Request, + channelId: apiTypes.Snowflake, + messageId: apiTypes.Snowflake, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PUT") + instance:setUrl(string.format(restEndpoints.PinMessage, channelId, messageId)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/channel#unpin-message +function Channel.unpinMessageAsync( + instance: request.Request, + channelId: apiTypes.Snowflake, + messageId: apiTypes.Snowflake, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("DELETE") + instance:setUrl(string.format(restEndpoints.UnpinMessage, channelId, messageId)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/channel#group-dm-add-recipient +function Channel.groupDMAddRecipientAsync( + instance: request.Request, + channelId: apiTypes.Snowflake, + userId: apiTypes.Snowflake, + jsonParams: restTypes.GroupDMAddRecipientRequest +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PUT") + instance:setUrl(string.format(restEndpoints.GroupDMAddRecipient, channelId, userId)) + instance:setBody(serde.encode("json", jsonParams, true)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/channel#group-dm-remove-recipient +function Channel.groupDMRemoveRecipientAsync( + instance: request.Request, + channelId: apiTypes.Snowflake, + userId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("DELETE") + instance:setUrl(string.format(restEndpoints.GroupDMRemoveRecipient, channelId, userId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/channel#start-thread-from-message +function Channel.startThreadFromMessageAsync( + instance: request.Request, + channelId: apiTypes.Snowflake, + messageId: apiTypes.Snowflake, + jsonParams: restTypes.StartThreadFromMessageRequest, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("POST") + instance:setUrl(string.format(restEndpoints.StartThreadFromMessage, channelId, messageId)) + instance:setBody(serde.encode("json", jsonParams, true)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/channel#start-thread-without-message +function Channel.startThreadWithoutMessageAsync( + instance: request.Request, + channelId: apiTypes.Snowflake, + jsonParams: restTypes.StartThreadWithoutMessageRequest, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("POST") + instance:setUrl(string.format(restEndpoints.StartThreadWithoutMessage, channelId)) + instance:setBody(serde.encode("json", jsonParams, true)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/channel#start-thread-in-forum-or-media-channel +function Channel.startThreadInForumOrMediaChannelAsync( + instance: request.Request, + channelId: apiTypes.Snowflake, + jsonParams: restTypes.StartThreadInForumOrMediaChannelRequest, + auditLogReason: string? +): future.Future< + restTypes.StartThreadInForumOrMediaChannelResponse +> + return future.new(function() + instance:assertToken() + + instance:setMethod("POST") + instance:setUrl(string.format(restEndpoints.StartThreadInForumOrMediaChannel, channelId)) + instance:setBody(serde.encode("json", jsonParams, true)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/channel#join-thread +function Channel.joinThreadAsync( + instance: request.Request, + channelId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PUT") + instance:setUrl(string.format(restEndpoints.JoinThread, channelId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/channel#add-thread-member +function Channel.addThreadMemberAsync( + instance: request.Request, + channelId: apiTypes.Snowflake, + userId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PUT") + instance:setUrl(string.format(restEndpoints.AddThreadMember, channelId, userId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/channel#leave-thread +function Channel.leaveThreadAsync( + instance: request.Request, + channelId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("DELETE") + instance:setUrl(string.format(restEndpoints.LeaveThread, channelId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/channel#remove-thread-member +function Channel.removeThreadMemberAsync( + instance: request.Request, + channelId: apiTypes.Snowflake, + userId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("DELETE") + instance:setUrl(string.format(restEndpoints.RemoveThreadMember, channelId, userId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/channel#get-thread-member +function Channel.getThreadMemberAsync( + instance: request.Request, + channelId: apiTypes.Snowflake, + userId: apiTypes.Snowflake, + urlParams: { + withMember: boolean?, + } +): future.Future + return future.new(function() + instance:assertToken() + + instance:addUrlParam("with_member", urlParams.withMember and tostring(urlParams.withMember) or (nil :: any)) + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetThreadMember, channelId, userId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/channel#list-thread-members +function Channel.listThreadMembersAsync( + instance: request.Request, + channelId: apiTypes.Snowflake, + urlParams: { + withMember: boolean?, + after: apiTypes.Snowflake?, + limit: number?, + } +): future.Future + return future.new(function() + instance:assertToken() + + instance:addUrlParam("with_member", urlParams.withMember and tostring(urlParams.withMember) or (nil :: any)) + instance:addUrlParam("after", urlParams.after and tostring(urlParams.after) or (nil :: any)) + instance:addUrlParam("limit", urlParams.limit and tostring(urlParams.limit) or (nil :: any)) + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.ListThreadMembers, channelId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/channel#list-public-archived-threads +function Channel.listPublicArchivedThreadsAsync( + instance: request.Request, + channelId: apiTypes.Snowflake, + urlParams: { + before: string?, + limit: number?, + } +): future.Future + return future.new(function() + instance:assertToken() + + instance:addUrlParam("limit", urlParams.limit and tostring(urlParams.limit) or (nil :: any)) + instance:addUrlParam("before", urlParams.before or (nil :: any)) + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.ListPublicArchivedThreads, channelId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/channel#list-private-archived-threads +function Channel.listPrivateArchivedThreadsAsync( + instance: request.Request, + channelId: apiTypes.Snowflake, + urlParams: { + before: string?, + limit: number?, + } +): future.Future + return future.new(function() + instance:assertToken() + + instance:addUrlParam("limit", urlParams.limit and tostring(urlParams.limit) or (nil :: any)) + instance:addUrlParam("before", urlParams.before or (nil :: any)) + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.ListPrivateArchivedThreads, channelId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/channel#list-joined-private-archived-threads +function Channel.listJoinedPrivateArchivedThreadsAsync( + instance: request.Request, + channelId: apiTypes.Snowflake, + urlParams: { + before: string?, + limit: number?, + } +): future.Future + return future.new(function() + instance:assertToken() + + instance:addUrlParam("limit", urlParams.limit and tostring(urlParams.limit) or (nil :: any)) + instance:addUrlParam("before", urlParams.before or (nil :: any)) + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.ListJoinedPrivateArchivedThreads, channelId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +return Channel diff --git a/packages/rest/src/commands.luau b/packages/rest/src/commands.luau new file mode 100644 index 0000000..3c2871a --- /dev/null +++ b/packages/rest/src/commands.luau @@ -0,0 +1,357 @@ +local serde = require("@std-polyfills/serde") + +local request = require("@rest/request") +local future = require("@vendor/future") + +local restTypes = require("@api-types/rest/types") +local apiTypes = require("@api-types/apiTypes") +local restEndpoints = require("@api-types/rest/endpoints") + +--[[ + HTTP Rest API implementation for the following Resource: + + - https://discord.com/developers/docs/interactions/application-commands +]] +local Commands = {} + +-- https://discord.com/developers/docs/interactions/application-commands#get-global-application-commands +function Commands.getGlobalApplicationCommandsAsync( + instance: request.Request, + applicationId: apiTypes.Snowflake, + urlParams: { + withLocalizations: boolean?, + } +): future.Future + return future.new(function() + instance:assertToken() + + instance:addUrlParam("with_localizations", urlParams.withLocalizations and "true" or (nil :: any)) + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetGlobalApplicationCommands, applicationId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/interactions/application-commands#create-global-application-command +function Commands.createGlobalApplicationCommandAsync( + instance: request.Request, + applicationId: apiTypes.Snowflake, + jsonParams: restTypes.CreateGlobalApplicationCommandRequest +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("POST") + instance:setUrl(string.format(restEndpoints.CreateGlobalApplicationCommand, applicationId)) + instance:setBody(serde.encode("json", jsonParams, true)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/interactions/application-commands#get-global-application-command +function Commands.getGlobalApplicationCommandAsync( + instance: request.Request, + applicationId: apiTypes.Snowflake, + commandId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetGlobalApplicationCommand, applicationId, commandId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/interactions/application-commands#edit-global-application-command +function Commands.editGlobalApplicationCommandAsync( + instance: request.Request, + applicationId: apiTypes.Snowflake, + commandId: apiTypes.Snowflake, + jsonParams: restTypes.EditGlobalApplicationCommandRequest +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PATCH") + instance:setUrl(string.format(restEndpoints.EditGlobalApplicationCommand, applicationId, commandId)) + instance:setBody(serde.encode("json", jsonParams, true)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/interactions/application-commands#delete-global-application-command +function Commands.deleteGlobalApplicationCommandAsync( + instance: request.Request, + applicationId: apiTypes.Snowflake, + commandId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("DELETE") + instance:setUrl(string.format(restEndpoints.DeleteGlobalApplicationCommand, applicationId, commandId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-global-application-commands +function Commands.bulkOverwriteGlobalApplicationCommandsAsync( + instance: request.Request, + applicationId: apiTypes.Snowflake, + jsonParams: restTypes.BulkOverwriteGlobalApplicationCommandsRequest +): future.Future< + restTypes.BulkOverwriteGlobalApplicationCommandsResponse +> + return future.new(function() + instance:assertToken() + + instance:setMethod("PUT") + instance:setUrl(string.format(restEndpoints.BulkOverwriteGlobalApplicationCommands, applicationId)) + instance:setBody(serde.encode("json", jsonParams, true)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/interactions/application-commands#get-guild-application-commands +function Commands.getGuildApplicationCommandsAsync( + instance: request.Request, + applicationId: apiTypes.Snowflake, + guildId: apiTypes.Snowflake, + urlParams: { + withLocalizations: boolean?, + } +): future.Future + return future.new(function() + instance:assertToken() + + instance:addUrlParam("with_localizations", urlParams.withLocalizations and "true" or (nil :: any)) + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetGuildApplicationCommands, applicationId, guildId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command +function Commands.createGuildApplicationCommandAsync( + instance: request.Request, + applicationId: apiTypes.Snowflake, + guildId: apiTypes.Snowflake, + jsonParams: restTypes.CreateGuildApplicationCommandRequest +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("POST") + instance:setUrl(string.format(restEndpoints.CreateGuildApplicationCommand, applicationId, guildId)) + instance:setBody(serde.encode("json", jsonParams, true)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/interactions/application-commands#get-guild-application-command +function Commands.getGuildApplicationCommandAsync( + instance: request.Request, + applicationId: apiTypes.Snowflake, + guildId: apiTypes.Snowflake, + commandId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetGuildApplicationCommand, applicationId, guildId, commandId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/interactions/application-commands#edit-guild-application-command +function Commands.editGuildApplicationCommandAsync( + instance: request.Request, + applicationId: apiTypes.Snowflake, + guildId: apiTypes.Snowflake, + commandId: apiTypes.Snowflake, + jsonParams: restTypes.EditGuildApplicationCommandRequest +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PATCH") + instance:setUrl(string.format(restEndpoints.EditGuildApplicationCommand, applicationId, guildId, commandId)) + instance:setBody(serde.encode("json", jsonParams, true)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/interactions/application-commands#delete-guild-application-command +function Commands.deleteGuildApplicationCommandAsync( + instance: request.Request, + applicationId: apiTypes.Snowflake, + guildId: apiTypes.Snowflake, + commandId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("DELETE") + instance:setUrl(string.format(restEndpoints.DeleteGuildApplicationCommand, applicationId, guildId, commandId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-guild-application-commands +function Commands.bulkOverwriteGuildApplicationCommandsAsync( + instance: request.Request, + applicationId: apiTypes.Snowflake, + guildId: apiTypes.Snowflake, + jsonParams: restTypes.BulkOverwriteGuildApplicationCommandsRequest +): future.Future< + restTypes.BulkOverwriteGuildApplicationCommandsResponse +> + return future.new(function() + instance:assertToken() + + instance:setMethod("PUT") + instance:setUrl(string.format(restEndpoints.BulkOverwriteGuildApplicationCommands, applicationId, guildId)) + instance:setBody(serde.encode("json", jsonParams, true)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/interactions/application-commands#get-guild-application-command-permissions +function Commands.getGuildApplicationCommandPermissionsAsync( + instance: request.Request, + applicationId: apiTypes.Snowflake, + guildId: apiTypes.Snowflake +): future.Future< + restTypes.GetGuildApplicationCommandPermissionsResponse +> + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetGuildApplicationCommandPermissions, applicationId, guildId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/interactions/application-commands#get-application-command-permissions +function Commands.getApplicationCommandPermissionsAsync( + instance: request.Request, + applicationId: apiTypes.Snowflake, + guildId: apiTypes.Snowflake, + commandId: apiTypes.Snowflake +): future.Future< + restTypes.GetApplicationCommandPermissionsResponse +> + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl( + string.format(restEndpoints.GetApplicationCommandPermissions, applicationId, guildId, commandId) + ) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/interactions/application-commands#edit-application-command-permissions +function Commands.editApplicationCommandPermissionsAsync( + instance: request.Request, + applicationId: apiTypes.Snowflake, + guildId: apiTypes.Snowflake, + commandId: apiTypes.Snowflake, + jsonParams: restTypes.EditApplicationCommandPermissionsRequest +): future.Future< + restTypes.EditApplicationCommandPermissionsResponse +> + return future.new(function() + instance:assertToken() + + instance:setMethod("PUT") + instance:setUrl( + string.format(restEndpoints.EditApplicationCommandPermissions, applicationId, guildId, commandId) + ) + instance:setBody(serde.encode("json", jsonParams, true)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +return Commands diff --git a/packages/rest/src/emoji.luau b/packages/rest/src/emoji.luau new file mode 100644 index 0000000..937f27b --- /dev/null +++ b/packages/rest/src/emoji.luau @@ -0,0 +1,125 @@ +local serde = require("@std-polyfills/serde") + +local request = require("@rest/request") +local future = require("@vendor/future") + +local restTypes = require("@api-types/rest/types") +local apiTypes = require("@api-types/apiTypes") +local restEndpoints = require("@api-types/rest/endpoints") + +--[[ + HTTP Rest API implementation for the following Resource: + + - https://discord.com/developers/docs/resources/emoji#emoji-resource +]] +local Emoji = {} + +-- https://discord.com/developers/docs/resources/emoji#list-guild-emojis +function Emoji.listGuildEmojisAsync( + instance: request.Request, + guildId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.ListGuildEmojis, guildId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/emoji#get-guild-emoji +function Emoji.getGuildEmojisAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + emojId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetGuildEmoji, guildId, emojId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/emoji#create-guild-emoji +function Emoji.createGuildEmojiAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + jsonParams: restTypes.CreateGuildEmojiRequest, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("POST") + instance:setUrl(string.format(restEndpoints.GetGuildEmoji, guildId)) + instance:setBody(serde.encode("json", jsonParams, true)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/emoji#modify-guild-emoji +function Emoji.modifyGuildEmojiAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + emojId: apiTypes.Snowflake, + jsonParams: restTypes.ModifyGuildEmojiRequest, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PATCH") + instance:setUrl(string.format(restEndpoints.ModifyGuildEmoji, guildId, emojId)) + instance:setBody(serde.encode("json", jsonParams, true)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/emoji#delete-guild-emoji +function Emoji.deleteGuildEmojiAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + emojId: apiTypes.Snowflake, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("DELETE") + instance:setUrl(string.format(restEndpoints.DeleteGuildEmoji, guildId, emojId)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +return Emoji diff --git a/packages/rest/src/gateway.luau b/packages/rest/src/gateway.luau new file mode 100644 index 0000000..25503e3 --- /dev/null +++ b/packages/rest/src/gateway.luau @@ -0,0 +1,44 @@ +local request = require("@rest/request") +local future = require("@vendor/future") + +local restTypes = require("@api-types/rest/types") +local restEndpoints = require("@api-types/rest/endpoints") + +--[[ + HTTP Rest API implementation for the following Resource: + + - https://discord.com/developers/docs/topics/gateway +]] +local Gateway = {} + +-- https://discord.com/developers/docs/topics/gateway#get-gateway +function Gateway.getGatewayAsync(instance: request.Request): future.Future + return future.new(function() + instance:setMethod("GET") + instance:setUrl(restEndpoints.GetGateway) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/topics/gateway#get-gateway-bot +function Gateway.getGatewayBotAsync(instance: request.Request): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(restEndpoints.GetGatewayBot) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +return Gateway diff --git a/packages/rest/src/guild.luau b/packages/rest/src/guild.luau new file mode 100644 index 0000000..13ac20f --- /dev/null +++ b/packages/rest/src/guild.luau @@ -0,0 +1,980 @@ +local serde = require("@std-polyfills/serde") + +local request = require("@rest/request") +local future = require("@vendor/future") + +local restTypes = require("@api-types/rest/types") +local apiTypes = require("@api-types/apiTypes") +local restEndpoints = require("@api-types/rest/endpoints") + +--[[ + HTTP Rest API implementation for the following Resource: + + - https://discord.com/developers/docs/resources/guild +]] +local Guild = {} + +-- https://discord.com/developers/docs/resources/guild#create-guild +function Guild.createGuildAsync( + instance: request.Request, + jsonParams: restTypes.CreateGuildRequest +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("POST") + instance:setUrl(restEndpoints.CreateGuild) + instance:setBody(serde.encode("json", jsonParams, true)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#get-guild +function Guild.getGuildAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + urlParams: { + withCounts: boolean?, + } +): future.Future + return future.new(function() + instance:assertToken() + + instance:addUrlParam("with_counts", urlParams.withCounts and tostring(urlParams.withCounts) or (nil :: any)) + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetGuild, guildId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#get-guild-preview +function Guild.getGuildPreviewAsync( + instance: request.Request, + guildId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetGuildPreview, guildId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#modify-guild +function Guild.modifyGuildAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + jsonParams: restTypes.ModifyGuildRequest, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PATCH") + instance:setUrl(string.format(restEndpoints.ModifyGuild, guildId)) + instance:setBody(serde.encode("json", jsonParams, true)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#delete-guild +function Guild.deleteGuildAsync( + instance: request.Request, + guildId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("DELETE") + instance:setUrl(string.format(restEndpoints.DeleteGuild, guildId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#get-guild-channels +function Guild.getGuildChannelsAsync( + instance: request.Request, + guildId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetGuildChannels, guildId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#create-guild-channel +function Guild.createGuildChannelAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + jsonParams: restTypes.CreateGuildChannelRequest, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("POST") + instance:setUrl(string.format(restEndpoints.CreateGuildChannel, guildId)) + instance:setBody(serde.encode("json", jsonParams, true)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#modify-guild-channel-positions +function Guild.modifyGuildChannelPositionsAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + jsonParams: restTypes.ModifyGuildChannelPositionsRequest +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PATCH") + instance:setUrl(string.format(restEndpoints.ModifyGuildChannelPositions, guildId)) + instance:setBody(serde.encode("json", jsonParams, true)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#list-active-guild-threads +function Guild.listActiveGuildThreadsAsync( + instance: request.Request, + guildId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.ListActiveGuildThreads, guildId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#get-guild-member +function Guild.getGuildMemberAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + userId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetGuildMember, guildId, userId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#list-guild-members +function Guild.listGuildMembersAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + urlParams: { + limit: number?, + after: number?, + } +): future.Future + return future.new(function() + instance:assertToken() + + instance:addUrlParam("limit", urlParams.limit and tostring(urlParams.limit) or (nil :: any)) + instance:addUrlParam("after", urlParams.after and tostring(urlParams.after) or (nil :: any)) + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.ListGuildMembers, guildId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#search-guild-members +function Guild.searchGuildMembersAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + urlParams: { + query: string, + limit: number?, + } +): future.Future + return future.new(function() + instance:assertToken() + + instance:addUrlParam("query", urlParams.query) + instance:addUrlParam("limit", urlParams.limit and tostring(urlParams.limit) or (nil :: any)) + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.SearchGuildMembers, guildId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#add-guild-member +function Guild.addGuildMemberAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + userId: apiTypes.Snowflake, + jsonParams: restTypes.AddGuildMemberRequest +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PUT") + instance:setUrl(string.format(restEndpoints.AddGuildMember, guildId, userId)) + instance:setBody(serde.encode("json", jsonParams, true)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#modify-guild-member +function Guild.modifyGuildMemberAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + userId: apiTypes.Snowflake, + jsonParams: restTypes.ModifyGuildMemberRequest, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PATCH") + instance:setUrl(string.format(restEndpoints.ModifyGuildMember, guildId, userId)) + instance:setBody(serde.encode("json", jsonParams, true)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#modify-current-member +function Guild.modifyCurrentMemberAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + jsonParams: restTypes.ModifyCurrentMemberRequest, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PATCH") + instance:setUrl(string.format(restEndpoints.ModifyCurrentMember, guildId)) + instance:setBody(serde.encode("json", jsonParams, true)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#add-guild-member-role +function Guild.addGuildMemberRoleAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + userId: apiTypes.Snowflake, + roleId: apiTypes.Snowflake, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PUT") + instance:setUrl(string.format(restEndpoints.AddGuildMemberRole, guildId, userId, roleId)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#remove-guild-member-role +function Guild.removeGuildMemberRoleAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + userId: apiTypes.Snowflake, + roleId: apiTypes.Snowflake, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("DELETE") + instance:setUrl(string.format(restEndpoints.RemoveGuildMemberRole, guildId, userId, roleId)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#remove-guild-member +function Guild.removeGuildMemberAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + userId: apiTypes.Snowflake, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("DELETE") + instance:setUrl(string.format(restEndpoints.RemoveGuildMember, guildId, userId)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#get-guild-bans +function Guild.getGuildBansAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + urlParams: { + limit: number?, + before: apiTypes.Snowflake?, + after: apiTypes.Snowflake?, + } +): future.Future + return future.new(function() + instance:assertToken() + + instance:addUrlParam("limit", urlParams.limit and tostring(urlParams.limit) or (nil :: any)) + instance:addUrlParam("before", urlParams.before and tostring(urlParams.before) or (nil :: any)) + instance:addUrlParam("after", urlParams.after and tostring(urlParams.after) or (nil :: any)) + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetGuildBans, guildId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#get-guild-ban +function Guild.getGuildBanAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + userId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetGuildBan, guildId, userId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#create-guild-ban +function Guild.createGuildBanAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + userId: apiTypes.Snowflake, + jsonParams: restTypes.CreateGuildBanRequest, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PUT") + instance:setUrl(string.format(restEndpoints.CreateGuildBan, guildId, userId)) + instance:setBody(serde.encode("json", jsonParams, true)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#remove-guild-ban +function Guild.removeGuildBanAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + userId: apiTypes.Snowflake, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("DELETE") + instance:setUrl(string.format(restEndpoints.RemoveGuildBan, guildId, userId)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#bulk-guild-ban +function Guild.bulkGuildBanAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + jsonParams: restTypes.BulkGuildBanRequest, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("POST") + instance:setUrl(string.format(restEndpoints.BulkGuildBan, guildId)) + instance:setBody(serde.encode("json", jsonParams, true)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#get-guild-roles +function Guild.getGuildRolesAsync( + instance: request.Request, + guildId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetGuildRoles, guildId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#create-guild-role +function Guild.createGuildRoleAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + jsonParams: restTypes.CreateGuildRoleRequest, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("POST") + instance:setUrl(string.format(restEndpoints.CreateGuildRole, guildId)) + instance:setBody(serde.encode("json", jsonParams, true)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#modify-guild-role-positions +function Guild.modifyGuildRolePositionsAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + jsonParams: restTypes.ModifyGuildRolePositionsRequest, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PATCH") + instance:setUrl(string.format(restEndpoints.ModifyGuildRolePositions, guildId)) + instance:setBody(serde.encode("json", jsonParams, true)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#modify-guild-role +function Guild.modifyGuildRoleAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + roleId: apiTypes.Snowflake, + jsonParams: restTypes.ModifyGuildRoleRequest, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PATCH") + instance:setUrl(string.format(restEndpoints.ModifyGuildRole, guildId, roleId)) + instance:setBody(serde.encode("json", jsonParams, true)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#modify-guild-mfa-level +function Guild.modifyGuildMFALevelAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + jsonParams: restTypes.ModifyGuildMFALevelRequest, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("POST") + instance:setUrl(string.format(restEndpoints.ModifyGuildMFALevel, guildId)) + instance:setBody(serde.encode("json", jsonParams, true)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#delete-guild-role +function Guild.deleteGuildRoleAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + roleId: apiTypes.Snowflake, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("POST") + instance:setUrl(string.format(restEndpoints.DeleteGuildRole, guildId, roleId)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#get-guild-prune-count +function Guild.getGuildPruneCountAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + urlParams: { + days: number, + includeRoles: string, + } +): future.Future + return future.new(function() + instance:assertToken() + + instance:addUrlParam("days", tostring(urlParams.days)) + instance:addUrlParam("includeRoles", tostring(urlParams.includeRoles)) + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetGuildPruneCount, guildId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#begin-guild-prune +function Guild.beginGuildPruneAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + jsonParams: restTypes.BeginGuildPruneRequest, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("POST") + instance:setUrl(string.format(restEndpoints.BeginGuildPrune, guildId)) + instance:setBody(serde.encode("json", jsonParams, true)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#get-guild-voice-regions +function Guild.getGuildVoiceRegionsAsync( + instance: request.Request, + guildId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetGuildVoiceRegions, guildId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#get-guild-invites +function Guild.getGuildInvitesAsync( + instance: request.Request, + guildId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetGuildInvites, guildId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#get-guild-integrations +function Guild.getGuildIntegrationsAsync( + instance: request.Request, + guildId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetGuildIntegrations, guildId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#delete-guild-integration +function Guild.deleteGuildIntegrationAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + integrationId: apiTypes.Snowflake, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("DELETE") + instance:setUrl(string.format(restEndpoints.DeleteGuildIntegration, guildId, integrationId)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#get-guild-widget-settings +function Guild.getGuildWidgetSettingsAsync( + instance: request.Request, + guildId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetGuildWidgetSettings, guildId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#modify-guild-widget +function Guild.modifyGuildWidgetAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + jsonParams: restTypes.ModifyGuildWidgetRequest, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PATCH") + instance:setUrl(string.format(restEndpoints.GetGuildWidgetSettings, guildId)) + instance:setBody(serde.encode("json", jsonParams, true)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#get-guild-widget +function Guild.getGuildWidgetAsync( + instance: request.Request, + guildId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetGuildWidget, guildId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#get-guild-vanity-url +function Guild.getGuildVanityURLAsync( + instance: request.Request, + guildId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetGuildVanityURL, guildId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#get-guild-widget-image +function Guild.getGuildWidgetImageAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + urlParams: { + style: ("shield" | "banner1" | "banner2" | "banner3" | "banner4")?, + } +): future.Future + return future.new(function() + instance:assertToken() + + instance:addUrlParam("style", urlParams.style :: any) + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetGuildWidgetImage, guildId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#get-guild-welcome-screen +function Guild.getGuildWelcomeScreenAsync( + instance: request.Request, + guildId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetGuildWelcomeScreen, guildId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#modify-guild-welcome-screen +function Guild.modifyGuildWelcomeScreenAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + jsonParams: restTypes.ModifyGuildWelcomeScreenRequest, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PATCH") + instance:setUrl(string.format(restEndpoints.ModifyGuildWelcomeScreen, guildId)) + instance:setBody(serde.encode("json", jsonParams, true)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#get-guild-onboarding +function Guild.getGuildOnboardingAsync( + instance: request.Request, + guildId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PATCH") + instance:setUrl(string.format(restEndpoints.GetGuildOnboarding, guildId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild#modify-guild-onboarding +function Guild.modifyGuildOnboardingAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + jsonParams: restTypes.ModifyGuildOnboardingRequest, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PUT") + instance:setUrl(string.format(restEndpoints.ModifyGuildOnboarding, guildId)) + instance:setBody(serde.encode("json", jsonParams, true)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +return Guild diff --git a/packages/rest/src/guildScheduledEvent.luau b/packages/rest/src/guildScheduledEvent.luau new file mode 100644 index 0000000..9113799 --- /dev/null +++ b/packages/rest/src/guildScheduledEvent.luau @@ -0,0 +1,161 @@ +local serde = require("@std-polyfills/serde") + +local request = require("@rest/request") +local future = require("@vendor/future") + +local restTypes = require("@api-types/rest/types") +local apiTypes = require("@api-types/apiTypes") +local restEndpoints = require("@api-types/rest/endpoints") + +--[[ + HTTP Rest API implementation for the following Resource: + + - https://discord.com/developers/docs/resources/guild-scheduled-event +]] +local GuildScheduledEvent = {} + +-- https://discord.com/developers/docs/resources/guild-scheduled-event#list-scheduled-events-for-guild +function GuildScheduledEvent.listScheduledEventsForGuildAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + urlParams: { + withUserCount: boolean?, + } +): future.Future< + restTypes.ListScheduledEventsForGuildResponse +> + return future.new(function() + instance:assertToken() + + instance:addUrlParam( + "with_user_count", + urlParams.withUserCount and tostring(urlParams.withUserCount) or (nil :: any) + ) + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.ListGuildScheduledForEvents, guildId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild-scheduled-event#create-guild-scheduled-event +function GuildScheduledEvent.createGuildScheduledEvent( + instance: request.Request, + guildId: apiTypes.Snowflake, + jsonParams: restTypes.CreateGuildScheduledEventRequest, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("POST") + instance:setUrl(string.format(restEndpoints.CreateGuildScheduledEvent, guildId)) + instance:setBody(serde.encode("json", jsonParams, true)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild-scheduled-event#get-guild-scheduled-event +function GuildScheduledEvent.getScheduledEventsForGuildAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + scheduledEventId: apiTypes.Snowflake, + urlParams: { + withUserCount: boolean?, + } +): future.Future + return future.new(function() + instance:assertToken() + + instance:addUrlParam( + "with_user_count", + urlParams.withUserCount and tostring(urlParams.withUserCount) or (nil :: any) + ) + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetGuildScheduledEvent, guildId, scheduledEventId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild-scheduled-event#modify-guild-scheduled-event +function GuildScheduledEvent.modifyGuildScheduledEvent( + instance: request.Request, + guildId: apiTypes.Snowflake, + scheduledEventId: apiTypes.Snowflake, + jsonParams: restTypes.ModifyGuildScheduledEventRequest, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PATCH") + instance:setUrl(string.format(restEndpoints.ModifyGuildScheduledEvent, guildId, scheduledEventId)) + instance:setBody(serde.encode("json", jsonParams, true)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild-scheduled-event#delete-guild-scheduled-event +function GuildScheduledEvent.deleteGuildScheduledEvent( + instance: request.Request, + guildId: apiTypes.Snowflake, + scheduledEventId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("DELETE") + instance:setUrl(string.format(restEndpoints.DeleteGuildScheduledEvent, guildId, scheduledEventId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild-scheduled-event#get-guild-scheduled-event-users +function GuildScheduledEvent.getGuildScheduledEventUsers( + instance: request.Request, + guildId: apiTypes.Snowflake, + scheduledEventId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetGuildScheduledEventUsers, guildId, scheduledEventId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +return GuildScheduledEvent diff --git a/packages/rest/src/guildTemplate.luau b/packages/rest/src/guildTemplate.luau new file mode 100644 index 0000000..d9d05cd --- /dev/null +++ b/packages/rest/src/guildTemplate.luau @@ -0,0 +1,159 @@ +local serde = require("@std-polyfills/serde") + +local request = require("@rest/request") +local future = require("@vendor/future") + +local restTypes = require("@api-types/rest/types") +local apiTypes = require("@api-types/apiTypes") +local restEndpoints = require("@api-types/rest/endpoints") + +--[[ + HTTP Rest API implementation for the following Resource: + + - https://discord.com/developers/docs/resources/guild-template +]] +local GuildTemplate = {} + +-- https://discord.com/developers/docs/resources/guild-template#get-guild-template +function GuildTemplate.getGuildTemplateAsync( + instance: request.Request, + templateCode: string +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetGuildTemplate, templateCode)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild-template#create-guild-from-guild-template +function GuildTemplate.createGuildFromGuildTemplateAsync( + instance: request.Request, + templateCode: string, + jsonParams: restTypes.CreateGuildFromGuildTemplateRequest +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("POST") + instance:setUrl(string.format(restEndpoints.CreateGuildFromTemplate, templateCode)) + instance:setBody(serde.encode("json", jsonParams, true)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild-template#get-guild-templates +function GuildTemplate.getGuildTemplatesAsync( + instance: request.Request, + guildId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetGuildTemplates, guildId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild-template#create-guild-template +function GuildTemplate.createGuildTemplateAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + jsonParams: restTypes.CreateGuildTemplateRequest +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("POST") + instance:setUrl(string.format(restEndpoints.CreateGuildTemplate, guildId)) + instance:setBody(serde.encode("json", jsonParams, true)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild-template#sync-guild-template +function GuildTemplate.syncGuildTemplateAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + templateCode: string +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PUT") + instance:setUrl(string.format(restEndpoints.SyncGuildTemplate, guildId, templateCode)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild-template#modify-guild-template +function GuildTemplate.modifyGuildTemplateAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + templateCode: string, + jsonParams: restTypes.ModifyGuildTemplateRequest +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PATCH") + instance:setUrl(string.format(restEndpoints.ModifyGuildTemplate, guildId, templateCode)) + instance:setBody(serde.encode("json", jsonParams, true)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/guild-template#delete-guild-template +function GuildTemplate.deleteGuildTemplateAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + templateCode: string +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("DELETE") + instance:setUrl(string.format(restEndpoints.DeleteGuildTemplate, guildId, templateCode)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +return GuildTemplate diff --git a/packages/rest/src/interaction.luau b/packages/rest/src/interaction.luau new file mode 100644 index 0000000..de17321 --- /dev/null +++ b/packages/rest/src/interaction.luau @@ -0,0 +1,223 @@ +local serde = require("@std-polyfills/serde") + +local request = require("@rest/request") +local future = require("@vendor/future") + +local restTypes = require("@api-types/rest/types") +local apiTypes = require("@api-types/apiTypes") +local restEndpoints = require("@api-types/rest/endpoints") + +--[[ + HTTP Rest API implementation for the following Resource: + + - https://discord.com/developers/docs/interactions/receiving-and-responding +]] +local Interaction = {} + +-- https://discord.com/developers/docs/interactions/receiving-and-responding#create-interaction-response +function Interaction.createInteractionResponseAsync( + instance: request.Request, + interactionId: apiTypes.Snowflake, + interactionToken: string, + jsonParams: restTypes.CreateInteractionRequest, + urlParams: { + withResponse: boolean?, + } +): future.Future + return future.new(function() + instance:assertToken() + + instance:addUrlParam( + "with_response", + urlParams.withResponse and tostring(urlParams.withResponse) or (nil :: any) + ) + + instance:setMethod("POST") + instance:setUrl(string.format(restEndpoints.CreateInteractionResponse, interactionId, interactionToken)) + instance:setBody(serde.encode("json", jsonParams, true)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/interactions/receiving-and-responding#get-original-interaction-response +function Interaction.getOriginalInteractionResponseAsync( + instance: request.Request, + applicationId: apiTypes.Snowflake, + interactionToken: string, + urlParams: { + threadId: apiTypes.Snowflake?, + } +): future.Future + return future.new(function() + instance:assertToken() + + instance:addUrlParam("thread_id", urlParams.threadId and tostring(urlParams.threadId) or (nil :: any)) + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetOriginalInteractionResponse, applicationId, interactionToken)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/interactions/receiving-and-responding#edit-original-interaction-response +function Interaction.editOriginalInteractionResponseAsync( + instance: request.Request, + applicationId: apiTypes.Snowflake, + interactionToken: string, + jsonParams: restTypes.EditOriginalInteractionRequest, + urlParams: { + threadId: apiTypes.Snowflake?, + } +): future.Future + return future.new(function() + instance:assertToken() + + instance:addUrlParam("thread_id", urlParams.threadId and tostring(urlParams.threadId) or (nil :: any)) + + instance:setMethod("PATCH") + instance:setUrl(string.format(restEndpoints.GetOriginalInteractionResponse, applicationId, interactionToken)) + instance:setBody(serde.encode("json", jsonParams, true)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/interactions/receiving-and-responding#delete-original-interaction-response +function Interaction.deleteOriginalInteractionResponseAsync( + instance: request.Request, + applicationId: apiTypes.Snowflake, + interactionToken: string +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("DELETE") + instance:setUrl(string.format(restEndpoints.DeleteOriginalInteractionResponse, applicationId, interactionToken)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/interactions/receiving-and-responding#create-followup-message +function Interaction.createFollowupMessageAsync( + instance: request.Request, + applicationId: apiTypes.Snowflake, + interactionToken: string, + jsonParams: restTypes.CreateFollowupMessageRequest, + urlParams: { + wait: boolean?, + threadId: apiTypes.Snowflake?, + } +): future.Future + return future.new(function() + instance:assertToken() + + instance:addUrlParam("wait", urlParams.wait and tostring(urlParams.wait) or (nil :: any)) + instance:addUrlParam("thread_id", urlParams.threadId and tostring(urlParams.threadId) or (nil :: any)) + + instance:setMethod("POST") + instance:setUrl(string.format(restEndpoints.CreateFollowupMessage, applicationId, interactionToken)) + instance:setBody(serde.encode("json", jsonParams, true)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/interactions/receiving-and-responding#get-followup-message +function Interaction.getFollowupMessageAsync( + instance: request.Request, + applicationId: apiTypes.Snowflake, + interactionToken: string, + messageId: apiTypes.Snowflake, + urlParams: { + threadId: apiTypes.Snowflake?, + } +): future.Future + return future.new(function() + instance:assertToken() + + instance:addUrlParam("thread_id", urlParams.threadId and tostring(urlParams.threadId) or (nil :: any)) + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetFollowupMessage, applicationId, interactionToken, messageId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/interactions/receiving-and-responding#edit-followup-message +function Interaction.editFollowupMessageAsync( + instance: request.Request, + applicationId: apiTypes.Snowflake, + interactionToken: string, + messageId: apiTypes.Snowflake, + jsonParams: restTypes.EditFollowupMessageRequest, + urlParams: { + threadId: apiTypes.Snowflake?, + } +): future.Future + return future.new(function() + instance:assertToken() + + instance:addUrlParam("thread_id", urlParams.threadId and tostring(urlParams.threadId) or (nil :: any)) + + instance:setMethod("POST") + instance:setUrl(string.format(restEndpoints.EditFollowupMessage, applicationId, interactionToken, messageId)) + instance:setBody(serde.encode("json", jsonParams, true)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/interactions/receiving-and-responding#delete-followup-message +function Interaction.deleteFollowupMessageAsync( + instance: request.Request, + applicationId: apiTypes.Snowflake, + interactionToken: string, + messageId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("DELETE") + instance:setUrl(string.format(restEndpoints.DeleteFollowupMessage, applicationId, interactionToken, messageId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +return Interaction diff --git a/packages/rest/src/invite.luau b/packages/rest/src/invite.luau new file mode 100644 index 0000000..3f5ef24 --- /dev/null +++ b/packages/rest/src/invite.luau @@ -0,0 +1,68 @@ +local request = require("@rest/request") +local future = require("@vendor/future") + +local restTypes = require("@api-types/rest/types") +local apiTypes = require("@api-types/apiTypes") +local restEndpoints = require("@api-types/rest/endpoints") + +--[[ + HTTP Rest API implementation for the following Resource: + + - https://discord.com/developers/docs/resources/invite +]] +local Invite = {} + +-- https://discord.com/developers/docs/resources/invite#get-invite +function Invite.getInviteAsync( + instance: request.Request, + inviteCode: string, + urlParams: { + withCounts: number?, + withExpiration: boolean?, + guildScheduledEventId: apiTypes.Snowflake?, + } +): future.Future + return future.new(function() + instance:addUrlParam("with_counts", urlParams.withCounts and tostring(urlParams.withCounts) or (nil :: any)) + instance:addUrlParam( + "with_expiration", + urlParams.withExpiration and tostring(urlParams.withExpiration) or (nil :: any) + ) + instance:addUrlParam( + "guild_scheduled_event_id", + urlParams.guildScheduledEventId and tostring(urlParams.guildScheduledEventId) or (nil :: any) + ) + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetInvite, inviteCode)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/invite#delete-invite +function Invite.deleteInviteAsync( + instance: request.Request, + inviteCode: string, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("DELETE") + instance:setUrl(string.format(restEndpoints.DeleteInvite, inviteCode)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +return Invite diff --git a/packages/rest/src/message.luau b/packages/rest/src/message.luau new file mode 100644 index 0000000..3fc0020 --- /dev/null +++ b/packages/rest/src/message.luau @@ -0,0 +1,334 @@ +local serde = require("@std-polyfills/serde") +local net = require("@std-polyfills/net") + +local request = require("@rest/request") +local future = require("@vendor/future") + +local restTypes = require("@api-types/rest/types") +local apiTypes = require("@api-types/apiTypes") +local restEndpoints = require("@api-types/rest/endpoints") + +--[[ + HTTP Rest API implementation for the following Resource: + + - https://discord.com/developers/docs/resources/message +]] +local Message = {} + +-- https://discord.com/developers/docs/resources/message#get-channel-messages +function Message.getChannelMessagesAsync( + instance: request.Request, + channelId: string, + urlParams: { + around: apiTypes.Snowflake?, + before: apiTypes.Snowflake?, + after: apiTypes.Snowflake?, + limit: number?, + } +): future.Future + return future.new(function() + instance:assertToken() + + instance:addUrlParam("around", urlParams.around and tostring(urlParams.around) or (nil :: any)) + instance:addUrlParam("before", urlParams.before and tostring(urlParams.before) or (nil :: any)) + instance:addUrlParam("after", urlParams.after and tostring(urlParams.after) or (nil :: any)) + instance:addUrlParam("limit", urlParams.limit and tostring(urlParams.limit) or (nil :: any)) + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetChannelMessages, channelId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/message#get-channel-message +function Message.getChannelMessageAsync( + instance: request.Request, + channelId: string, + messageId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetChannelMessage, channelId, messageId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/message#create-message +function Message.createMessageAsync( + instance: request.Request, + channelId: string, + jsonParams: restTypes.CreateMessageRequest +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("POST") + instance:setUrl(string.format(restEndpoints.CreateMessage, channelId)) + instance:setBody(serde.encode("json", jsonParams, true)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/message#crosspost-message +function Message.crosspostMessageAsync( + instance: request.Request, + channelId: string, + jsonParams: restTypes.CrosspostMessageRequest +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("POST") + instance:setUrl(string.format(restEndpoints.CrosspostMessage, channelId)) + instance:setBody(serde.encode("json", jsonParams, true)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/message#create-reaction +function Message.createReactionAsync( + instance: request.Request, + channelId: string, + messageId: apiTypes.Snowflake, + emojiId: apiTypes.Snowflake, + emojiName: string +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PUT") + instance:setUrl( + string.format(restEndpoints.CreateReaction, channelId, messageId, net.urlEncode(`{emojiName}:{emojiId}`)) + ) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/message#delete-own-reaction +function Message.deleteOwnReactionAsync( + instance: request.Request, + channelId: string, + messageId: apiTypes.Snowflake, + emojiId: apiTypes.Snowflake, + emojiName: string +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("DELETE") + instance:setUrl( + string.format(restEndpoints.DeleteOwnReaction, channelId, messageId, net.urlEncode(`{emojiName}:{emojiId}`)) + ) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/message#delete-user-reaction +function Message.deleteUserReactionAsync( + instance: request.Request, + channelId: string, + messageId: apiTypes.Snowflake, + emojiId: apiTypes.Snowflake, + emojiName: string, + userId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("DELETE") + instance:setUrl( + string.format( + restEndpoints.DeleteUserReaction, + channelId, + messageId, + net.urlEncode(`{emojiName}:{emojiId}`), + userId + ) + ) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/message#get-reactions +function Message.getReactionsAsync( + instance: request.Request, + channelId: string, + messageId: apiTypes.Snowflake, + emojiId: apiTypes.Snowflake, + emojiName: string, + urlParams: { + type: ("Normal" | "Burst")?, + after: apiTypes.Snowflake?, + before: number?, + } +): future.Future + return future.new(function() + instance:assertToken() + + instance:addUrlParam("type", (urlParams.type and (urlParams.type == "Normal" and 0 or 1)) or (nil :: any)) + instance:addUrlParam("after", urlParams.after and tostring(urlParams.after) or (nil :: any)) + instance:addUrlParam("before", urlParams.before and tostring(urlParams.before) or (nil :: any)) + + instance:setMethod("GET") + instance:setUrl( + string.format(restEndpoints.GetReactions, channelId, messageId, net.urlEncode(`{emojiName}:{emojiId}`)) + ) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/message#delete-all-reactions +function Message.deleteAllReactionsAsync( + instance: request.Request, + channelId: string, + messageId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("DELETE") + instance:setUrl(string.format(restEndpoints.DeleteAllReactions, channelId, messageId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/message#delete-all-reactions-for-emoji +function Message.deleteAllReactionsForEmojiAsync( + instance: request.Request, + channelId: string, + messageId: apiTypes.Snowflake, + emojiId: apiTypes.Snowflake, + emojiName: string +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("DELETE") + instance:setUrl( + string.format( + restEndpoints.DeleteAllReactions, + channelId, + messageId, + net.urlEncode(`{emojiName}:{emojiId}`) + ) + ) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/message#edit-message +function Message.editMessageAsync( + instance: request.Request, + channelId: string, + messageId: apiTypes.Snowflake, + jsonParams: restTypes.EditMessageRequest +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PATCH") + instance:setUrl(string.format(restEndpoints.EditMessage, channelId, messageId)) + instance:setBody(serde.encode("json", jsonParams, true)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/message#delete-message +function Message.deleteMessageAsync( + instance: request.Request, + channelId: string, + messageId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("DELETE") + instance:setUrl(string.format(restEndpoints.DeleteMessage, channelId, messageId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/message#bulk-delete-messages +function Message.bulkDeleteMessagesAsync( + instance: request.Request, + channelId: string, + jsonParams: restTypes.BulkDeleteMessagesRequest +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("DELETE") + instance:setUrl(string.format(restEndpoints.BulkDeleteMessages, channelId)) + instance:setBody(serde.encode("json", jsonParams, true)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +return Message diff --git a/packages/rest/src/poll.luau b/packages/rest/src/poll.luau new file mode 100644 index 0000000..adfb591 --- /dev/null +++ b/packages/rest/src/poll.luau @@ -0,0 +1,63 @@ +local request = require("@rest/request") +local future = require("@vendor/future") + +local restTypes = require("@api-types/rest/types") +local apiTypes = require("@api-types/apiTypes") +local restEndpoints = require("@api-types/rest/endpoints") + +--[[ + HTTP Rest API implementation for the following Resource: + + - https://discord.com/developers/docs/resources/poll +]] +local Poll = {} + +-- https://discord.com/developers/docs/resources/poll#get-answer-voters +function Poll.getAnswerVotersAsync( + instance: request.Request, + channelId: apiTypes.Snowflake, + messageId: apiTypes.Snowflake, + answerId: apiTypes.Snowflake, + urlParams: { + after: number?, + limit: number?, + } +): future.Future + return future.new(function() + instance:assertToken() + + instance:addUrlParam("after", urlParams.after and tostring(urlParams.after) or (nil :: any)) + instance:addUrlParam("limit", urlParams.limit and tostring(urlParams.limit) or (nil :: any)) + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetAnswerVoters, channelId, messageId, answerId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/poll#end-poll +function Poll.endPollAsync( + instance: request.Request, + channelId: apiTypes.Snowflake, + messageId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("POST") + instance:setUrl(string.format(restEndpoints.EndPoll, channelId, messageId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +return Poll diff --git a/packages/rest/src/request.luau b/packages/rest/src/request.luau new file mode 100644 index 0000000..dc0cd93 --- /dev/null +++ b/packages/rest/src/request.luau @@ -0,0 +1,242 @@ +local net = require("@std-polyfills/net") + +local future = require("@vendor/future") + +--[[ + Request is a class that represents a single HTTP request. The idea behind this class is to provide + some sort of structure and boilerplate for making HTTP requests. + + by default, this class will add the following headers: + - User-Agent: "DiscordLuau" + - Authorization: "Bot " + - Content-Type: "application/json" +]] +local Request = {} + +Request.Prototype = {} +Request.Interface = {} + +--[[ + Responsible for setting the Body of this request. Body must be a string. +]] +function Request.Prototype.setBody(self: Request, body: string) + self.body = body +end + +--[[ + Responsible for adding to the Url Params of this request. Both the param key and values are strings. +]] +function Request.Prototype.addUrlParam(self: Request, paramName: string, paramValue: string) + self.urlParams[paramName] = paramValue +end + +--[[ + Responsible for adding to the Headers of this request. Both the header key and values are strings. + + You are not allowed to set the following headers: + - User-Agent + - Authorization +]] +function Request.Prototype.addHeader(self: Request, headerName: string, headerValue: string) + assert(headerName ~= "user-agent", `User-Agent header cannot be set!`) + assert(headerName ~= "authorization", `Authorization header cannot be set!`) + + self.headers[headerName] = headerValue +end + +--[[ + Responsible for setting the URL of this request. URL must be a string, and formatted like so: + + - https://example.com/path/to/resource +]] +function Request.Prototype.setUrl(self: Request, url: string) + -- assert(url:match("^%a+://"), `Request URL '{url}' is not a valid URL`) + + self.url = url +end + +--[[ + Responsible for setting the Method of this request. Method must be one of GET, POST, PUT, PATCH, or DELETE. + + Methods define the nature of the request, and the response. +]] +function Request.Prototype.setMethod(self: Request, method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE") + assert( + method == "GET" or method == "POST" or method == "PUT" or method == "PATCH" or method == "DELETE", + `Request method must be one of GET, POST, PUT, PATCH, or DELETE.` + ) + + self.method = method +end + +--[[ + Responsible for validating the Authorization token of this request. Some calls require token authorization, and + some do not, so the relevant calls should assert that the token is set. +]] +function Request.Prototype.assertToken(self: Request) + assert(self.token, `Request authorization token is not set!`) +end + +--[[ + Enables developers to add a hook to this request. Hooks are functions that are called before or after the request is + executed. Hooks are useful for things like logging, or modifying the request before it is executed. + + The hook callback should return a boolean, and if it returns false, the request will not be executed. +]] +function Request.Prototype.addHook( + self: Request, + hookState: "BeforeRequest" | "AfterRequest", + hookCallback: (request: Request) -> boolean +) + if hookState == "AfterRequest" then + table.insert(self.afterHooks, hookCallback) + elseif hookState == "BeforeRequest" then + table.insert(self.beforeHooks, hookCallback) + else + error(`Invalid hook state '{hookState}'`) + end +end + +--[[ + Responsible for executing this request asynchronously. This will return a future that will resolve when the request is + completed. + + This function will decode the response body, and return a table with the following keys: + - headers: A table of headers returned from the request. + - body: The body of the request, decoded from the response. +]] +function Request.Prototype.executeAsync(self: Request): future.Future<{ headers: { [string]: string }, body: Resolve }> + return future.new(function() + assert(self.method, `Request method is not set!`) + assert(self.url, `Request URL is not set!`) + assert(self.headers, `Request headers are not set!`) + + if self.method == "POST" then + assert(self.body, `Request body is not set!`) + end + + local headerTable = {} + local baseUrl = `https://discord.com/api/v{self.restApiVersion}{self.url}` + local hasUrlParams = next(self.urlParams) ~= nil + + headerTable["user-agent"] = "DiscordBot (https://github.com/DiscordLuau/discord-luau, 0.1.0)" + headerTable["content-type"] = "application/json" + + if self.token then + headerTable["authorization"] = `Bot {self.token}` + end + + for header, value in self.headers do + headerTable[header] = value + end + + if hasUrlParams then + baseUrl = baseUrl .. "?" + end + + for param, value in self.urlParams do + baseUrl = baseUrl .. `{param}={value}&` + end + + if hasUrlParams then + baseUrl = baseUrl:sub(1, #baseUrl - 1) + end + + for _, hook in self.beforeHooks do + if + not hook(self, { + url = baseUrl, + method = self.method, + body = self.body, + headers = headerTable, + }) + then + error(`Hook: {debug.info(hook, "s")} returned false, failed to send Network request!`) + end + end + + -- FIXME: This table satisfies FetchParams, yet it somehow cannot be casted + local request = net.request({ + url = baseUrl, + method = self.method, + body = self.body, + headers = headerTable, + }) + + if not request.ok then + error(setmetatable({ + statusCode = request.statusCode, + statusMessage = request.statusMessage, + body = request.body, + }, { + __tostring = function(self) + return `HTTP REST Request failed: {self.statusCode} {self.statusMessage}\n{self.body}` + end, + })) + end + + local decodeSuccess, decodedBody = pcall(net.jsonDecode, request.body) + + for _, hook in self.afterHooks do + hook(self, { + url = baseUrl, + method = self.method, + body = self.body, + headers = headerTable, + }, request) + end + + return { + headers = request.headers, + body = decodeSuccess and decodedBody or request.body, + } + end) +end + +--[[ + Constructor for the Request object. +]] +function Request.Interface.new(settings: { + token: string?, + + restApiVersion: number, +}): Request + return setmetatable( + { + token = settings.token, + restApiVersion = settings.restApiVersion, + headers = {}, + urlParams = {}, + beforeHooks = {}, + afterHooks = {}, + } :: Request, + { __index = Request.Prototype } + ) +end + +export type Request = typeof(Request.Prototype) & { + token: string?, + restApiVersion: number, + + beforeHooks: { + ( + self: Request, + { url: string, method: string, body: string, headers: { [string]: string } } + ) -> boolean + }, + afterHooks: { + ( + self: Request, + { url: string, method: string, body: string, headers: { [string]: string } }, + net.FetchResponse + ) -> () + }, + + method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", + url: string, + headers: { [string]: string }, + urlParams: { [string]: string }, + body: string, +} + +return Request.Interface diff --git a/packages/rest/src/stageInstance.luau b/packages/rest/src/stageInstance.luau new file mode 100644 index 0000000..3c3a210 --- /dev/null +++ b/packages/rest/src/stageInstance.luau @@ -0,0 +1,100 @@ +local serde = require("@std-polyfills/serde") + +local request = require("@rest/request") +local future = require("@vendor/future") + +local restTypes = require("@api-types/rest/types") +local apiTypes = require("@api-types/apiTypes") +local restEndpoints = require("@api-types/rest/endpoints") + +--[[ + HTTP Rest API implementation for the following Resource: + + - https://discord.com/developers/docs/resources/stage-instance +]] +local StageInstance = {} + +-- https://discord.com/developers/docs/resources/stage-instance#create-stage-instance +function StageInstance.createStageInstanceAsync( + instance: request.Request, + jsonParams: restTypes.CreateStageInstanceRequest, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("POST") + instance:setUrl(restEndpoints.CreateStageInstance) + instance:setBody(serde.encode("json", jsonParams, true)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/stage-instance#get-stage-instance +function StageInstance.getStageInstanceAsync( + instance: request.Request, + channelId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetStageInstance, channelId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/stage-instance#modify-stage-instance +function StageInstance.modifyStageInstanceAsync( + instance: request.Request, + channelId: apiTypes.Snowflake, + jsonParams: restTypes.ModifyStageInstanceRequest, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PATCH") + instance:setUrl(string.format(restEndpoints.ModifyStageInstance, channelId)) + instance:setBody(serde.encode("json", jsonParams, true)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/stage-instance#delete-stage-instance +function StageInstance.deleteStageInstanceAsync( + instance: request.Request, + channelId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("DELETE") + instance:setUrl(string.format(restEndpoints.DeleteStageInstance, channelId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +return StageInstance diff --git a/packages/rest/src/sticker.luau b/packages/rest/src/sticker.luau new file mode 100644 index 0000000..4d9e7f4 --- /dev/null +++ b/packages/rest/src/sticker.luau @@ -0,0 +1,160 @@ +local serde = require("@std-polyfills/serde") + +local request = require("@rest/request") +local future = require("@vendor/future") + +local restTypes = require("@api-types/rest/types") +local apiTypes = require("@api-types/apiTypes") +local restEndpoints = require("@api-types/rest/endpoints") + +--[[ + HTTP Rest API implementation for the following Resource: + + - https://discord.com/developers/docs/resources/sticker +]] +local Sticker = {} + +-- https://discord.com/developers/docs/resources/sticker#get-sticker +function Sticker.getStickerAsync( + instance: request.Request, + stickerId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetSticker, stickerId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/sticker#list-sticker-packs +function Sticker.listStickerPacksAsync(instance: request.Request): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(restEndpoints.ListStickerPacks) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/sticker#list-guild-stickers +function Sticker.listGuildStickersAsync( + instance: request.Request, + guildId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.ListGuildStickers, guildId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/sticker#get-guild-sticker +function Sticker.getGuildStickerAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + stickerId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetGuildSticker, guildId, stickerId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/sticker#create-guild-sticker +function Sticker.createGuildStickerAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + jsonParams: restTypes.CreateGuildStickerRequest, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("POST") + instance:setUrl(string.format(restEndpoints.CreateGuildSticker, guildId)) + instance:setBody(serde.encode("json", jsonParams, true)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/sticker#modify-guild-sticker +function Sticker.modifyGuildStickerAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + stickerId: apiTypes.Snowflake, + jsonParams: restTypes.ModifyGuildStickerRequest, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PATCH") + instance:setUrl(string.format(restEndpoints.ModifyGuildSticker, guildId, stickerId)) + instance:setBody(serde.encode("json", jsonParams, true)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/sticker#delete-guild-sticker +function Sticker.deleteGuildStickerAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + stickerId: apiTypes.Snowflake, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("DELETE") + instance:setUrl(string.format(restEndpoints.DeleteGuildSticker, guildId, stickerId)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +return Sticker diff --git a/packages/rest/src/user.luau b/packages/rest/src/user.luau new file mode 100644 index 0000000..df20ca8 --- /dev/null +++ b/packages/rest/src/user.luau @@ -0,0 +1,261 @@ +local serde = require("@std-polyfills/serde") + +local request = require("@rest/request") +local future = require("@vendor/future") + +local restTypes = require("@api-types/rest/types") +local apiTypes = require("@api-types/apiTypes") +local restEndpoints = require("@api-types/rest/endpoints") + +--[[ + HTTP Rest API implementation for the following Resource: + + - https://discord.com/developers/docs/resources/user +]] +local User = {} + +-- https://discord.com/developers/docs/resources/user#get-current-user +function User.getCurrentUserAsync(instance: request.Request): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(restEndpoints.GetCurrentUser) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/user#get-user +function User.getUserAsync( + instance: request.Request, + userId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetUser, userId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/user#modify-current-user +function User.modifyCurrentUserAsync( + instance: request.Request, + jsonParams: restTypes.ModifyCurrentUserRequest +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PATCH") + instance:setUrl(restEndpoints.ModifyCurrentUser) + instance:setBody(serde.encode("json", jsonParams, true)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/user#modify-current-user +function User.modifyCurrentUserAsync( + instance: request.Request, + jsonParams: restTypes.ModifyCurrentUserRequest +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PATCH") + instance:setUrl(restEndpoints.ModifyCurrentUser) + instance:setBody(serde.encode("json", jsonParams, true)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/user#get-current-user-guilds +function User.getCurrentUserGuildsAsync( + instance: request.Request, + urlParams: { + before: apiTypes.Snowflake?, + after: apiTypes.Snowflake?, + limit: number?, + withCounts: boolean?, + } +): future.Future + return future.new(function() + instance:assertToken() + + instance:addUrlParam("before", urlParams.before and tostring(urlParams.before) or (nil :: any)) + instance:addUrlParam("after", urlParams.after and tostring(urlParams.after) or (nil :: any)) + instance:addUrlParam("limit", urlParams.limit and tostring(urlParams.limit) or (nil :: any)) + instance:addUrlParam("with_counts", urlParams.withCounts and tostring(urlParams.withCounts) or (nil :: any)) + + instance:setMethod("GET") + instance:setUrl(restEndpoints.GetCurrentUserGuilds) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/user#get-current-user-guild-member +function User.getCurrentUserGuildMember( + instance: request.Request, + guildId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetCurrentUserGuilds, guildId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/user#leave-guild +function User.leaveGuildAsync( + instance: request.Request, + guildId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("DELETE") + instance:setUrl(string.format(restEndpoints.GetCurrentUserGuilds, guildId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/user#create-dm +function User.createDMAsync( + instance: request.Request, + jsonParams: restTypes.CreateDMRequest +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("POST") + instance:setUrl(restEndpoints.CreateDM) + instance:setBody(serde.encode("json", jsonParams, true)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/user#create-group-dm +function User.createGroupDMAsync( + instance: request.Request, + jsonParams: restTypes.CreateGroupDMRequest +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("POST") + instance:setUrl(restEndpoints.CreateGroupDM) + instance:setBody(serde.encode("json", jsonParams, true)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/user#get-current-user-connections +function User.getCurrentUserConnectionsAsync( + instance: request.Request +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(restEndpoints.GetCurrentUserConnections) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/user#get-current-user-application-role-connection +function User.getCurrentUserApplicationRoleConnectionAsync( + instance: request.Request, + applicationId: apiTypes.Snowflake +): future.Future< + restTypes.GetCurrentUserApplicationRoleConnectionResponse +> + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetCurrentUserApplicationRolConnections, applicationId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/user#update-current-user-application-role-connection +function User.updateCurrentUserApplicationRoleConnectionAsync( + instance: request.Request, + applicationId: apiTypes.Snowflake, + jsonParams: restTypes.UpdateCurrentUserApplicationRoleConnectionRequest +): future.Future< + restTypes.UpdateCurrentUserApplicationRoleConnectionResponse +> + return future.new(function() + instance:assertToken() + + instance:setMethod("PUT") + instance:setUrl(string.format(restEndpoints.UpdateCurrentUserApplicationRoleConnection, applicationId)) + instance:setBody(serde.encode("json", jsonParams, true)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +return User diff --git a/packages/rest/src/voice.luau b/packages/rest/src/voice.luau new file mode 100644 index 0000000..96ed76d --- /dev/null +++ b/packages/rest/src/voice.luau @@ -0,0 +1,115 @@ +local serde = require("@std-polyfills/serde") + +local request = require("@rest/request") +local future = require("@vendor/future") + +local restTypes = require("@api-types/rest/types") +local apiTypes = require("@api-types/apiTypes") +local restEndpoints = require("@api-types/rest/endpoints") + +--[[ + HTTP Rest API implementation for the following Resource: + + - https://discord.com/developers/docs/resources/voice +]] +local Voice = {} + +-- https://discord.com/developers/docs/resources/voice#list-voice-regions +function Voice.listVoiceRegionsAsync(instance: request.Request): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(restEndpoints.ListVoiceRegions) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/voice#get-current-user-voice-state +function Voice.getCurrentUserVoiceStateAsync( + instance: request.Request, + guildId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetCurrentUserVoiceState, guildId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/voice#get-user-voice-state +function Voice.getUserVoiceStateAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + userId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetUserVoiceState, guildId, userId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/voice#modify-current-user-voice-state +function Voice.modifyCurrentUserVoiceStateAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + jsonParams: restTypes.ModifyCurrentUserVoiceStateRequest +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PATCH") + instance:setUrl(string.format(restEndpoints.ModifyCurrentUserVoiceState, guildId)) + instance:setBody(serde.encode("json", jsonParams, true)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/voice#modify-user-voice-state +function Voice.modifyUserVoiceStateAsync( + instance: request.Request, + guildId: apiTypes.Snowflake, + userId: apiTypes.Snowflake, + jsonParams: restTypes.ModifyUserVoiceStateRequest +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PATCH") + instance:setUrl(string.format(restEndpoints.ModifyUserVoiceState, guildId, userId)) + instance:setBody(serde.encode("json", jsonParams, true)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +return Voice diff --git a/packages/rest/src/webhook.luau b/packages/rest/src/webhook.luau new file mode 100644 index 0000000..acc476a --- /dev/null +++ b/packages/rest/src/webhook.luau @@ -0,0 +1,316 @@ +local serde = require("@std-polyfills/serde") + +local request = require("@rest/request") +local future = require("@vendor/future") + +local restTypes = require("@api-types/rest/types") +local apiTypes = require("@api-types/apiTypes") +local restEndpoints = require("@api-types/rest/endpoints") + +--[[ + HTTP Rest API implementation for the following Resource: + + - https://discord.com/developers/docs/resources/webhook +]] +local Webhook = {} + +-- https://discord.com/developers/docs/resources/webhook#create-webhook +function Webhook.createWebhookAsync( + instance: request.Request, + channelId: apiTypes.Snowflake, + jsonParams: restTypes.CreateWebhookRequest, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("POST") + instance:setUrl(string.format(restEndpoints.CreateWebhook, channelId)) + instance:setBody(serde.encode("json", jsonParams, true)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/webhook#get-channel-webhooks +function Webhook.getChannelWebhooksAsync( + instance: request.Request, + channelId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetChannelWebhooks, channelId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/webhook#get-guild-webhooks +function Webhook.getGuildWebhooksAsync( + instance: request.Request, + guildId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetGuildWebhooks, guildId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/webhook#get-webhook +function Webhook.getWebhookAsync( + instance: request.Request, + webhookId: apiTypes.Snowflake +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetWebhook, webhookId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/webhook#get-webhook-with-token +function Webhook.getWebhookWithTokenAsync( + instance: request.Request, + webhookId: apiTypes.Snowflake, + webhookToken: string +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("GET") + instance:setUrl(string.format(restEndpoints.GetWebhookWithToken, webhookId, webhookToken)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/webhook#modify-webhook +function Webhook.modifyWebhookAsync( + instance: request.Request, + webhookId: apiTypes.Snowflake, + jsonParams: restTypes.ModifyWebhookRequest, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PATCH") + instance:setUrl(string.format(restEndpoints.ModifyWebhook, webhookId)) + instance:setBody(serde.encode("json", jsonParams, true)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/webhook#modify-webhook-with-token +function Webhook.modifyWebhookWithTokenAsync( + instance: request.Request, + webhookId: apiTypes.Snowflake, + webhookToken: string, + jsonParams: restTypes.ModifyWebhookRequest, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("PATCH") + instance:setUrl(string.format(restEndpoints.ModifyWebhookWithToken, webhookId, webhookToken)) + instance:setBody(serde.encode("json", jsonParams, true)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/webhook#delete-webhook +function Webhook.deleteWebhookAsync( + instance: request.Request, + webhookId: apiTypes.Snowflake, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("DELETE") + instance:setUrl(string.format(restEndpoints.DeleteWebhook, webhookId)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/webhook#delete-webhook-with-token +function Webhook.deleteWebhookWithTokenAsync( + instance: request.Request, + webhookId: apiTypes.Snowflake, + webhookToken: string, + auditLogReason: string? +): future.Future + return future.new(function() + instance:assertToken() + + instance:setMethod("DELETE") + instance:setUrl(string.format(restEndpoints.DeleteWebhookWithToken, webhookId, webhookToken)) + instance:addHeader("x-audit-log-reason", auditLogReason :: string) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/webhook#execute-webhook +function Webhook.executeWebhookAsync( + instance: request.Request, + webhookId: apiTypes.Snowflake, + webhookToken: string, + jsonParams: restTypes.ExecuteWebhookRequest, + urlParams: { + wait: boolean?, + threadId: apiTypes.Snowflake?, + } +): future.Future + return future.new(function() + instance:assertToken() + + instance:addUrlParam("wait", urlParams.wait and tostring(urlParams.wait) or (nil :: any)) + instance:addUrlParam("thread_id", urlParams.threadId and tostring(urlParams.threadId) or (nil :: any)) + + instance:setMethod("POST") + instance:setUrl(string.format(restEndpoints.ExecuteWebhook, webhookId, webhookToken)) + instance:setBody(serde.encode("json", jsonParams, true)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/webhook#get-webhook-message +function Webhook.getWebhookMessageAsync( + instance: request.Request, + webhookId: apiTypes.Snowflake, + webhookToken: string, + messageId: apiTypes.Snowflake, + urlParams: { + threadId: apiTypes.Snowflake?, + } +): future.Future + return future.new(function() + instance:assertToken() + + instance:addUrlParam("thread_id", urlParams.threadId and tostring(urlParams.threadId) or (nil :: any)) + + instance:setMethod("POST") + instance:setUrl(string.format(restEndpoints.GetWebhookMessage, webhookId, webhookToken, messageId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/webhook#edit-webhook-message +function Webhook.editWebhookMessageAsync( + instance: request.Request, + webhookId: apiTypes.Snowflake, + webhookToken: string, + messageId: apiTypes.Snowflake, + jsonParams: restTypes.EditWebhookMessageRequest, + urlParams: { + threadId: apiTypes.Snowflake?, + } +): future.Future + return future.new(function() + instance:assertToken() + + instance:addUrlParam("thread_id", urlParams.threadId and tostring(urlParams.threadId) or (nil :: any)) + + instance:setMethod("PATCH") + instance:setUrl(string.format(restEndpoints.EditWebhookMessage, webhookId, webhookToken, messageId)) + instance:setBody(serde.encode("json", jsonParams, true)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +-- https://discord.com/developers/docs/resources/webhook#delete-webhook-message +function Webhook.deleteWebhookMessageAsync( + instance: request.Request, + webhookId: apiTypes.Snowflake, + webhookToken: string, + messageId: apiTypes.Snowflake, + urlParams: { + threadId: apiTypes.Snowflake?, + } +): future.Future + return future.new(function() + instance:assertToken() + + instance:addUrlParam("thread_id", urlParams.threadId and tostring(urlParams.threadId) or (nil :: any)) + + instance:setMethod("DELETE") + instance:setUrl(string.format(restEndpoints.EditWebhookMessage, webhookId, webhookToken, messageId)) + + local status, response = instance:executeAsync():await() + + assert(status == "Fulfilled", tostring(response)) + + return response.body + end) +end + +return Webhook diff --git a/packages/std-polyfills/README.md b/packages/std-polyfills/README.md new file mode 100644 index 0000000..4cda0ec --- /dev/null +++ b/packages/std-polyfills/README.md @@ -0,0 +1,11 @@ +
+

+ discord-luau +

+
+ +## DiscordLuau - Std Polyfills + +This package provides polyfills for the standard library. When swapping Discord-Luau to a new Luau Runtime, you'll need to make sure that the standard library is polyfilled. + +All packages under Discord-Luau will use this package as a dependency. \ No newline at end of file diff --git a/packages/std-polyfills/src/datetime.luau b/packages/std-polyfills/src/datetime.luau new file mode 100644 index 0000000..f481fe2 --- /dev/null +++ b/packages/std-polyfills/src/datetime.luau @@ -0,0 +1,12 @@ +--[=[ + @class Std.DateTime +]=] + +local DateTime = {} +local LuneDateTime = require("@lune/datetime") + +export type DateTime = LuneDateTime.DateTime + +setmetatable(DateTime, { __index = LuneDateTime }) + +return DateTime diff --git a/packages/std-polyfills/src/filesystem.luau b/packages/std-polyfills/src/filesystem.luau new file mode 100644 index 0000000..80f84b1 --- /dev/null +++ b/packages/std-polyfills/src/filesystem.luau @@ -0,0 +1,9 @@ +--[=[ + @class Std.FileSystem +]=] + +local FileSystem = {} + +setmetatable(FileSystem, { __index = require("@lune/fs") }) + +return FileSystem diff --git a/packages/std-polyfills/src/net.luau b/packages/std-polyfills/src/net.luau new file mode 100644 index 0000000..6bb61d2 --- /dev/null +++ b/packages/std-polyfills/src/net.luau @@ -0,0 +1,13 @@ +--[=[ + @class Std.Net +]=] + +local Net = {} +local LuneNet = require("@lune/net") + +export type WebSocket = LuneNet.WebSocket +export type FetchResponse = LuneNet.FetchResponse + +setmetatable(Net, { __index = LuneNet }) + +return Net diff --git a/packages/std-polyfills/src/process.luau b/packages/std-polyfills/src/process.luau new file mode 100644 index 0000000..899355f --- /dev/null +++ b/packages/std-polyfills/src/process.luau @@ -0,0 +1,9 @@ +--[=[ + @class Std.Process +]=] + +local Process = {} + +setmetatable(Process, { __index = require("@lune/process") }) + +return Process diff --git a/packages/std-polyfills/src/serde.luau b/packages/std-polyfills/src/serde.luau new file mode 100644 index 0000000..ea8f1ef --- /dev/null +++ b/packages/std-polyfills/src/serde.luau @@ -0,0 +1,9 @@ +--[=[ + @class Std.Serde +]=] + +local Serde = {} + +setmetatable(Serde, { __index = require("@lune/serde") }) + +return Serde diff --git a/packages/std-polyfills/src/stdio.luau b/packages/std-polyfills/src/stdio.luau new file mode 100644 index 0000000..844a890 --- /dev/null +++ b/packages/std-polyfills/src/stdio.luau @@ -0,0 +1,9 @@ +--[=[ + @class Std.stdio +]=] + +local StdIO = {} + +setmetatable(StdIO, { __index = require("@lune/stdio") }) + +return StdIO diff --git a/packages/std-polyfills/src/task.luau b/packages/std-polyfills/src/task.luau new file mode 100644 index 0000000..68984a4 --- /dev/null +++ b/packages/std-polyfills/src/task.luau @@ -0,0 +1,9 @@ +--[=[ + @class Std.Task +]=] + +local Task = {} + +setmetatable(Task, { __index = require("@lune/task") }) + +return Task diff --git a/packages/utils/README.md b/packages/utils/README.md new file mode 100644 index 0000000..b3691b6 --- /dev/null +++ b/packages/utils/README.md @@ -0,0 +1,9 @@ +
+

+ discord-luau +

+
+ +## DiscordLuau - Utils + +This packcage contains common utility modules and functions for DiscordLuau. diff --git a/packages/utils/src/import.luau b/packages/utils/src/import.luau new file mode 100644 index 0000000..aec59ae --- /dev/null +++ b/packages/utils/src/import.luau @@ -0,0 +1,9 @@ +--[[ + A very hacky way to get around cyclic dependencies +]] + +return function(filePath: string): T + local requireHook = require :: any + + return requireHook(filePath) +end diff --git a/packages/utils/src/table/reflect.luau b/packages/utils/src/table/reflect.luau new file mode 100644 index 0000000..4a0d3ed --- /dev/null +++ b/packages/utils/src/table/reflect.luau @@ -0,0 +1,11 @@ +return function(source: T): T + if type(source) ~= "table" then + return source + else + for key: unknown, value: unknown in source do + source[key] = value + end + end + + return source +end diff --git a/packages/utils/src/validateKebabCase.luau b/packages/utils/src/validateKebabCase.luau new file mode 100644 index 0000000..e6faa59 --- /dev/null +++ b/packages/utils/src/validateKebabCase.luau @@ -0,0 +1,30 @@ +return function(source: string) + -- Check if the string is empty + if source == "" then + return false + end + + -- Check if the string starts or ends with a hyphen + if string.sub(source, 1, 1) == "-" or string.sub(source, -1) == "-" then + return false + end + + -- Split the string by hyphens + local parts = string.split(source, "-") + + -- Check each part + for _, part in ipairs(parts) do + -- Check if the part is empty + if part == "" then + return false + end + + -- Check if the part contains only lowercase letters and numbers + if not string.match(part, "^[a-z0-9]+$") then + return false + end + end + + -- If all checks pass, the string is valid kebab case + return true +end diff --git a/packages/vendor/README.md b/packages/vendor/README.md new file mode 100644 index 0000000..8a5ccef --- /dev/null +++ b/packages/vendor/README.md @@ -0,0 +1,9 @@ +
+

+ discord-luau +

+
+ +## DiscordLuau - Vendor + +With the lack of a Package Manager for Luau, we have a separate package for vendoring dependencies used in DiscordLuau. diff --git a/packages/vendor/src/bit/bit64.luau b/packages/vendor/src/bit/bit64.luau new file mode 100644 index 0000000..2f0cdc7 --- /dev/null +++ b/packages/vendor/src/bit/bit64.luau @@ -0,0 +1,117 @@ +--[[ + An 64-bit library to provide 64-bit binary operation in luau, ideally luau would expose some sort of bit64 lib, + but they don't, so this is the un-optimised, awkward approach! +]] +local Bit64 = {} + +--[[ + Converts a number to a binary string. +]] +local function toBinary(number: number): string + local binary = select( + 1, + string.gsub(string.format("%o", number), ".", function(x) + return ({ + "000", + "001", + "010", + "011", + "100", + "101", + "110", + "111", + })[tonumber(x) :: number + 1] + end) + ) + + binary = `{string.rep("0", math.max(64 - #binary, 0))}{binary}` + + return binary +end + +--[[ + Converts a binary string to a number. +]] +local function toNumber(binary: string): number + return tonumber(binary, 2) :: number +end + +--[[ + Binary NOT operation for 64 bit ints +]] +function Bit64.bnot(value: number) + local binary = toBinary(value) + + binary = string.gsub(binary, ".", function(x) + if x == "0" then + return "1" + else + return "0" + end + end) + + return toNumber(binary) +end + +--[[ + Binary OR operation for 64 bit ints +]] +function Bit64.bor(value0: number, value1: number) + local binary0 = toBinary(value0) + local binary1 = toBinary(value1) + + local outputBinary = `` + + for index = 1, #binary0 do + local binary0Bit = tonumber(string.sub(binary0, index, index)) :: number + local binary1Bit = tonumber(string.sub(binary1, index, index)) :: number + + if binary0Bit + binary1Bit > 0 then + outputBinary ..= "1" + else + outputBinary ..= "0" + end + end + + return toNumber(outputBinary) +end + +--[[ + Binary AND operation for 64 bit ints +]] +function Bit64.band(value0: number, value1: number) + local binary0 = toBinary(value0) + local binary1 = toBinary(value1) + + local outputBinary = `` + + for index = 1, #binary0 do + local binary0Bit = tonumber(string.sub(binary0, index, index)) :: number + local binary1Bit = tonumber(string.sub(binary1, index, index)) :: number + + outputBinary ..= tostring(binary0Bit * binary1Bit) + end + + return toNumber(outputBinary) +end + +--[[ + Binary SHIFT operation for 64 bit ints, disposition is the number of bits to shift by, can support negative values +]] +function Bit64.shift(value: number, disposition: number) + local binary = toBinary(value) + + if disposition > 0 then + binary = string.sub(binary, 0, #binary - disposition) + binary = `{string.rep("0", disposition)}{binary}` + else + disposition = math.abs(disposition) + + binary = string.sub(binary, -(#binary - disposition)) + binary = `{binary}{string.rep("0", disposition)}` + end + + return toNumber(binary) +end + +return Bit64 diff --git a/packages/vendor/src/bit/init.luau b/packages/vendor/src/bit/init.luau new file mode 100644 index 0000000..1702fdc --- /dev/null +++ b/packages/vendor/src/bit/init.luau @@ -0,0 +1,75 @@ +--[[ + A Bit wrapper library that supports both 64 Bit integers and 32 Bit integers. Primarily required in the calculation + for snowflake IDs that the Discord API will give us. +]] + +local MAXIMUM_32BIT_VALUE = 2147483647 + +local Bit64 = require("bit64") +local Bit32 = bit32 + +local Bit = {} + +--[[ + Binary NOT operation for 64 bit ints +]] +function Bit.bnot(value: number): number + if value > MAXIMUM_32BIT_VALUE then + return Bit64.bnot(value) + else + return Bit32.bnot(value) + end +end + +--[[ + Binary OR operation for 64 bit ints +]] +function Bit.bor(value0: number, value1: number): number + if value0 > MAXIMUM_32BIT_VALUE then + return Bit64.bor(value0, value1) + else + return Bit32.bor(value0, value1) + end +end + +--[[ + Binary AND operation for 64 bit ints +]] +function Bit.band(value0: number, value1: number): number + if value0 > MAXIMUM_32BIT_VALUE then + return Bit64.band(value0, value1) + else + return Bit32.band(value0, value1) + end +end + +--[[ + Binary SHIFT operation for 64 bit ints, disposition is the number of bits to shift by, can support negative values +]] +function Bit.shift(value: number, disposition: number): number + if value > MAXIMUM_32BIT_VALUE then + return Bit64.shift(value, disposition) + else + if disposition > 0 then + return Bit32.rshift(value, disposition) + else + return Bit32.lshift(value, disposition) + end + end +end + +--[[ + Binary LEFT SHIFT operation for 64 bit ints +]] +function Bit.lshift(value: number, disposition: number): number + return Bit.shift(value, disposition) +end + +--[[ + Binary RIGHT SHIFT operation for 64 bit ints +]] +function Bit.rshift(value: number, disposition: number): number + return Bit.shift(value, -disposition) +end + +return Bit diff --git a/packages/vendor/src/buffer/init.luau b/packages/vendor/src/buffer/init.luau new file mode 100644 index 0000000..30f0579 --- /dev/null +++ b/packages/vendor/src/buffer/init.luau @@ -0,0 +1,50 @@ +--[[ + A buffer is a data structure that is used to store data in a sequential manner, only + allowed to access that data once it has been completely written to the buffer. + + It is used in th Discord Websocket to ensure that we receive an entire ZLib compressed + message before we attempt to decompress it. +]] + +local Buffer = {} + +Buffer.Interface = {} +Buffer.Prototype = {} + +--[[ + Responsible for writing data to the buffer. +]] +function Buffer.Prototype.write(self: Buffer, data: string) + self.data ..= data +end + +--[[ + Responsible for flushing the data from the buffer and returning it. +]] +function Buffer.Prototype.flush(self: Buffer): string + local data = self.data + + self.data = "" + + return data +end + +--[[ + Constructor for the Buffer object, will return a new instance of the Buffer object. +]] +function Buffer.Interface.new(): Buffer + return setmetatable( + { + data = "", + } :: Buffer, + { + __index = Buffer.Prototype, + } + ) +end + +export type Buffer = typeof(Buffer.Prototype) & { + data: string, +} + +return Buffer.Interface diff --git a/packages/vendor/src/emitter/init.luau b/packages/vendor/src/emitter/init.luau new file mode 100644 index 0000000..fcc010d --- /dev/null +++ b/packages/vendor/src/emitter/init.luau @@ -0,0 +1,99 @@ +--[[ + An emitter that can be used to emit events, this class will be used to push updates from the Discord Websockets + out to the Consumers of this library. + + This library has Unit Tests written for it under the /tests directory. +]] + +local task = require("@std-polyfills/task") + +local Emitter = {} + +Emitter.Interface = {} +Emitter.Prototype = {} + +--[[ + Listen to this event, returns a function that can be called to disconnect the listener +]] +function Emitter.Prototype.listen(self: Emitter, callback: (T...) -> ()): () -> () + local thread = coroutine.create(function() + while true do + callback(coroutine.yield()) + end + end) + + coroutine.resume(thread) + + table.insert(self._connectedThreads, thread) + + return function() + local index = table.find(self._connectedThreads, thread) + + if index then + table.remove(self._connectedThreads, index) + end + end +end + +--[[ + Listen to this event once, this will automatically disconnect after the first invocation +]] +function Emitter.Prototype.listenOnce(self: Emitter, callback: (T...) -> ()): () -> () + local disconnect + + disconnect = self:listen(function(...) + disconnect() + + callback(...) + end) + + return disconnect +end + +--[[ + Wait for this event to be emitted from, returns the result of the emittion +]] +function Emitter.Prototype.wait(self: Emitter): T... + table.insert(self._yieldingThreads, coroutine.running()) + + return coroutine.yield() +end + +--[[ + Responsible for invoking all the threads that are listening to this emitter. + + This will additionally resume all the threads that are currently waiting for an event to be emitted. +]] +function Emitter.Prototype.invoke(self: Emitter, ...): () + for _, thread in self._yieldingThreads do + task.spawn(thread, ...) + end + + self._yieldingThreads = {} + + for _, thread in self._connectedThreads do + task.spawn(thread, ...) + end +end + +--[[ + Constructor for the Emitter object, will return a new instance of the Emitter object. +]] +function Emitter.Interface.new(): Emitter + return setmetatable( + { + _yieldingThreads = {}, + _connectedThreads = {}, + } :: Emitter, + { + __index = Emitter.Prototype, + } + ) +end + +export type Emitter = typeof(Emitter.Prototype) & { + _yieldingThreads: { thread }, + _connectedThreads: { thread }, +} + +return Emitter.Interface diff --git a/packages/vendor/src/formdata/init.luau b/packages/vendor/src/formdata/init.luau new file mode 100644 index 0000000..3151598 --- /dev/null +++ b/packages/vendor/src/formdata/init.luau @@ -0,0 +1,258 @@ +--[[ + A multipart/form-data implementation for luau. + + This implementation is based on the following spec(s): + - https://www.rfc-editor.org/rfc/rfc2388 + - https://xhr.spec.whatwg.org/#formdata +]] + +local parser = require("parser") + +local FILE_FORMATS = table.freeze({ + jpg = "image/jpg", + jpeg = "image/jpeg", + png = "image/png", + webp = "image/webp", + gif = "image/gif", + + csv = "text/csv", + txt = "text/plain", + css = "text/css", + js = "text/javascript", + html = "text/html", + + mp4 = "video/mp4", +}) + +local FormData = {} + +FormData.Interface = {} +FormData.Prototype = {} + +--[[ + QoL function to filter an array with a predicate, returns the index and value of the first item that + matches the predicate +]] +local function filter(array, predicate: (T) -> boolean, after: number?): (number?, T?) + for index = after or 1, #array do + local value = array[index] + + if predicate(value) then + return index, value + end + end + + return nil +end + +--[[ + responsible for generating a UUID that we use in the compilation of the formdata object. +]] +local function uuid() + local template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx" + + return string.gsub(template, "[xy]", function(c) + local v = (c == "x") and math.random(0, 0xf) or math.random(8, 0xb) + + return string.format("%x", v) + end) +end + +--[[ + Not included in the spec, but we need this to be able to get the body for the formdata object. +]] +function FormData.Prototype.getBody(self: FormData): string + local multiformContent = "" + + for _, entry in self.entries do + local fileType = string.match(entry.filename or "", ".+%.(%S+)") + + multiformContent ..= `--{self.boundary}\r\n` + multiformContent ..= `Content-Disposition: form-data; name="{entry.name}"{entry.filename and `; filename="{entry.filename}"` or ""}\r\n` + multiformContent ..= `Content-Type: {FILE_FORMATS[fileType] or "application/octet-stream"}\r\n` + + multiformContent ..= `\r\n` + multiformContent ..= `{entry.value}\r\n` + end + + multiformContent ..= `--{self.boundary}--\r\n` + + return multiformContent +end + +--[[ + Not included in the spec, but we need this to be able to get the header for the formdata object. +]] +function FormData.Prototype.getHeader(self: FormData): string + return `multipart/form-data; boundary={self.boundary}` +end + +--[[ + Appends a new entry to the form data's internal 'entries' table. +]] +function FormData.Prototype.append( + self: FormData, + name: string, + value: string, + filename: string?, + encoding: string? +): () + table.insert(self.entries, { + name = name, + value = value, + filename = filename, + encoding = encoding, + }) +end + +--[[ + Sets the name, value and filename of an entry in the form data's internal 'entries' table. + + Follows the standard found here: https://xhr.spec.whatwg.org/#dom-formdata-append +]] +function FormData.Prototype.set(self: FormData, name: string, value: string, filename: string?, encoding: string?): () + local index = filter(self.entries, function(object) + return object.name == name + end) + + --[[ + If there are entries in this’s entry list whose name is name, + then replace the first such entry with entry and remove the others. + + Otherwise, append entry to this’s entry list. + ]] + + if index then + self.entries[index] = { + name = name, + value = value, + filename = filename, + encoding = encoding, + } + + local nextIndex = filter(self.entries, function(object) + return object.name == name + end, index) + + while nextIndex do + table.remove(self.entries, nextIndex) + + nextIndex = filter(self.entries, function(object) + return object.name == name + end, index) + end + else + self:append(name, value, filename) + end +end + +--[[ + Function responsible for the removal of a formdata entry from the formdata's internal 'entries' table. +]] +function FormData.Prototype.delete(self: FormData, name: string): () + local index = filter(self.entries, function(object) + return object.name == name + end) + + if index then + table.remove(self.entries, index) + end +end + +--[[ + Function responsible for checking if this formdata object has a specific entry. +]] +function FormData.Prototype.has(self: FormData, name: string): boolean + local _, formDataEntryValue = filter(self.entries, function(object) + return object.name == name + end) + + return formDataEntryValue ~= nil +end + +--[[ + Function responsible for getting the value of a formdata entry from the formdata's internal 'entries' table. +]] +function FormData.Prototype.get(self: FormData, name: string): FormDataEntryValue? + local _, formDataEntryValue = filter(self.entries, function(object) + return object.name == name + end) + + return (formDataEntryValue and formDataEntryValue.value) or nil +end + +--[[ + Function responsible for getting all the values of a formdata entry from the formdata's internal 'entries' table. + + If a name is provided, it will only return the values of all entries with the provided name. +]] +function FormData.Prototype.getAll(self: FormData, name: string?): { FormDataEntryValue } + local entries = {} + + if name then + local lastIndex + local formDataEntryValue + + while true do + lastIndex, formDataEntryValue = filter(self.entries, function(object) + return object.name == name + end, lastIndex) + + if not lastIndex or not formDataEntryValue then + break + end + + table.insert(entries, (formDataEntryValue :: FormDataEntryMap).value) + end + else + for _, entry in self.entries do + table.insert(entries, entry.value) + end + end + + return entries +end + +--[[ + Constructor for the FormData object. + + This function is missing the form and submitter fields that are defined in the spec, but are not relevant for the + implementation of this feature in luau. +]] +function FormData.Interface.new(): FormData + local entries = {} + + return setmetatable( + { + entries = entries, + boundary = uuid(), + } :: FormData, + { + __index = FormData.Prototype, + __iter = entries, + } + ) +end + +--[[ + Parses formdata a string into a FormData object. +]] +function FormData.Interface.parse(textSource: string, mimeType: string): FormData + local formData = FormData.Interface.new() + local parsedFiles = parser(textSource, mimeType) + + for _, entry in parsedFiles do + formData:append(entry.name, entry.body, entry.filename, entry.contentTransferEncoding) + end + + return formData +end + +export type FormDataEntryValue = string +export type FormDataEntryMap = { name: string, value: string, filename: string?, encoding: string? } +export type FormData = typeof(FormData.Prototype) & { + entries: { FormDataEntryMap }, + boundary: string, +} + +return FormData.Interface diff --git a/packages/vendor/src/formdata/parser.luau b/packages/vendor/src/formdata/parser.luau new file mode 100644 index 0000000..cb88a03 --- /dev/null +++ b/packages/vendor/src/formdata/parser.luau @@ -0,0 +1,264 @@ +--[[ + A multipart/form-data parser. + + This parser is based on the following spec(s): + - https://www.rfc-editor.org/rfc/rfc2388 + - https://www.rfc-editor.org/rfc/rfc1867 + - https://www.rfc-editor.org/rfc/rfc2047 + - https://andreubotella.github.io/multipart-form-data/#multipart-form-data-parser +]] + +local Stream = require("@vendor/stream") + +local ILLEGAL_HTTP_TOKEN_CHARACTERS = table.freeze({ + ['"'] = true, + ["("] = true, + [")"] = true, + [","] = true, + ["/"] = true, + [":"] = true, + [";"] = true, + ["<"] = true, + ["="] = true, + [">"] = true, + ["?"] = true, + ["@"] = true, + ["["] = true, + ["]"] = true, + ["{"] = true, + ["}"] = true, + ["\20"] = true, + ["\9"] = true, + ["\r"] = true, + ["\n"] = true, +}) :: { [string]: boolean } + +--[[ + Validate that the name of the Content-Disposition header is valid ABNF grammar as defined in RFC 2047. +]] +local function validateEncodedToken_RFC2047(source: string) + local sourceSplit = string.split(source, "") + + for token in ILLEGAL_HTTP_TOKEN_CHARACTERS do + local index = table.find(sourceSplit, token) + + if index then + error(`Invalid form data: {source} (invalid encoded token)`) + end + end +end + +--[[ + This function is responsible for parsing the Content-Disposition header. +]] +local function parseMultipartContentDisposition(stream: Stream.Stream): (string, string?) + local header = stream:advance(20) + + assert(header, `Invalid form data: {stream.textSource} (unexpected end of stream)`) + assert( + string.lower(header) == "content-disposition:", + `Invalid form data: {stream.textSource} (invalid content-disposition)` + ) + + -- skil all whitespaces + stream:trim() + + assert(stream:advance(10) == "form-data;", `Invalid form data: {stream.textSource} (invalid content-disposition)`) + + -- skil all whitespaces + stream:trim() + + local valueMap = stream:advanceUntil(function(character) + return character == "\r" + end) + + -- skip the newline + stream:advance(1) + + assert(valueMap, `Invalid form data: {stream.textSource} (unexpected end of stream)`) + + local iterator = string.gmatch(valueMap, `"(%S+)"`) + + local name = iterator() + local filename = iterator() + + assert(name, `Invalid form data: {stream.textSource} (invalid content-disposition)`) + + validateEncodedToken_RFC2047(name) + + name = string.gsub(name, "%%0A", "\n") + name = string.gsub(name, "%%0D", "\r") + name = string.gsub(name, "%%22", '"') + + return name, filename +end + +--[[ + This function is responsible for parsing the Content-Type header. +]] +local function parseContentType(stream: Stream.Stream): string + local header = stream:advance(13) + + assert(header, `Invalid form data: {stream.textSource} (unexpected end of stream)`) + assert(string.lower(header) == "content-type:", `Invalid form data: {stream.textSource} (invalid content-type)`) + + -- skil all whitespaces + stream:trim() + + local contentType = stream:advanceUntil(function(character) + return character == "\r" + end) + + -- skip the newline + stream:advance(1) + + assert(contentType, `Invalid form data: {stream.textSource} (unexpected end of stream)`) + + contentType = string.sub(contentType, 1, #contentType - 1) + + return contentType +end + +--[[ + This function is responsible for parsing the optional Content-Transfer-Encoding header. +]] +local function parseContentTransferEncoding(stream: Stream.Stream): string? + local header = stream:peek(26) + + if header and string.lower(header) == "content-transfer-encoding:" then + stream:advance(26) + + -- skil all whitespaces + stream:trim() + + local contentTransferEncoding = stream:advanceUntil(function(character) + return character == "\r" + end) + + -- skip the newline + stream:advance(1) + + assert(contentTransferEncoding, `Invalid form data: {stream.textSource} (unexpected end of stream)`) + + return contentTransferEncoding + else + return nil + end +end + +--[[ + This function is responsible for parsing the entire form data, it uses a combination of the above + functions to parse the form data. +]] +local function parseMultipartFormDataValue(stream: Stream.Stream): DecodedFormDataValue + local name, filename = parseMultipartContentDisposition(stream) + local contentType = parseContentType(stream) + local contentTransferEncoding = parseContentTransferEncoding(stream) + + assert(stream:advance(2) == "\r\n", `Invalid form data: {stream.textSource} (CR Ls)`) + + local body = stream:readUntilEnd() + + assert(body, `Invalid form data: {stream.textSource} (unexpected end of stream)`) + + return { + name = name, + filename = filename, + contentType = contentType, + contentTransferEncoding = contentTransferEncoding, + body = body, + } +end + +--[[ + The main function that is called to parse the form data. +]] +local function parseMultipartFormData(stream: Stream.Stream, boundary: string): { DecodedFormDataValue } + local formData = {} + + while true do + -- assertions to ensure that the header is valid + assert(stream:advance(2) == "--", `Invalid form data: {stream.textSource} (expected formdata boundary)`) + assert( + stream:advance(#boundary) == boundary, + `Invalid form data: {stream.textSource} (expected formdata boundary)` + ) + assert(stream:advance(2) == "\r\n", `Invalid form data: {stream.textSource} (CR Ls)`) + + -- get the body of the formdata entry + local body = "" + + stream:advanceUntil(function(character) + body ..= character + + return string.sub(body, -#`--{boundary}`) == `--{boundary}` + end) + + local isLastEntry = stream:peek(4) == "--\r\n" + + -- reserve the boundary for the next iteration + stream:advance(-(#boundary + 2)) + + -- 6 for the last entry to cover: LF/CR/-- + body = string.sub(body, 1, #body - #boundary - 4) + + -- parse the form data body + table.insert(formData, parseMultipartFormDataValue(Stream.new(body))) + + if isLastEntry then + break + end + end + + return formData +end + +--[[ + This function is responsible for parsing the mime type. Returning the 'boundary' from the mime parameters. +]] +local function parseMultipartMime(stream: Stream.Stream): string + local matchedBoundary + local boundary + + assert( + stream:advance(19) == "multipart/form-data", + `Invalid mime type: {stream.textSource} (expected formdata mime type)` + ) + + -- as defined in the examples in RFC1867 - ; can be used alongside , to split the parameters of this + -- mime type + assert(stream:peek() == ";" or stream:peek() == ",", `Invalid mime type: {stream.textSource} (unknown delimiter)`) + + -- skip the token since we only :peek() above + stream:advance() + + -- skil all whitespaces + stream:trim() + + -- read the boundary key/value pair + boundary = stream:readUntilEnd() + + -- match the boundary value + matchedBoundary = string.match(boundary, "boundary=(.+)") + + assert(matchedBoundary ~= nil, `Invalid mime type: {stream.textSource} (failed to match boundary)`) + + return matchedBoundary +end + +export type DecodedFormDataValue = { + name: string, + filename: string?, + contentType: string, + contentTransferEncoding: string?, + body: string, +} + +return function(textSource: string, mimeType: string): { DecodedFormDataValue } + local textSourceStream = Stream.new(textSource) + local mimeTypeStream = Stream.new(mimeType) + + local boundary = parseMultipartMime(mimeTypeStream) + + return parseMultipartFormData(textSourceStream, boundary) +end diff --git a/packages/vendor/src/future/init.luau b/packages/vendor/src/future/init.luau new file mode 100644 index 0000000..f671d66 --- /dev/null +++ b/packages/vendor/src/future/init.luau @@ -0,0 +1,194 @@ +--[[ + A luau future implementation, Futures are a simpler way to query asynchronous functions, an alternative to Future would + be to use a promise. + + This library has Unit Tests written for it under the /tests directory. +]] + +local task = require("@std-polyfills/task") +local stdio = require("@std-polyfills/stdio") + +local Future = {} + +Future.Prototype = {} +Future.Interface = {} + +--[[ + Wait for this future to be resolved, this will block the current thread until the future is resolved. +]] +function Future.Prototype.await(self: Future): (FutureStatus, ...T) + if self.status == "Pending" then + local thread = coroutine.running() + + table.insert(self._yieldingThreads, thread) + + return coroutine.yield() + end + + return self.status, table.unpack(self._valueList) +end + +--[[ + After the future is resolved, call this callback, this callback will only be + called if the future is Fulfilled, and not rejected.. +]] +function Future.Prototype.after(self: Future, callback: (...T) -> ()): Future + if self.status == "Fulfilled" then + task.spawn(callback, table.unpack(self._valueList)) + + return self + end + + table.insert(self._afterCallbacks, callback) + + return self +end + +--[[ + Catch any errors that occur in the future, this will only be called if the future is rejected. +]] +function Future.Prototype.catch(self: Future, callback: (string) -> ()): Future + if self.status == "Rejected" then + task.spawn(callback, self.exception :: string) + + return self + end + + table.insert(self._catchCallback, callback) + + return self +end + +--[[ + Expect the future to resolve, this will throw an error if the future is rejected. (optionally pass + an error message yourself) +]] +function Future.Prototype.expect(self: Future, errorMessage: string?): ...T + if self.status == "Pending" then + self:await() + end + + assert(self.status == "Fulfilled", errorMessage or `Expected Future to be fulfilled, got ${self.status}`) + + return table.unpack(self._valueList) +end + +--[[ + Query the values of the future, the values represent the result of the asynchronous function. +]] +function Future.Prototype.unwrap(self: Future): ...T + return table.unpack(self._valueList) +end + +--[[ + In the case the future has been rejected, or the asynchronous function returned nil - return + the value passed to this function. +]] +function Future.Prototype.unwrapOr(self: Future, value: T): T + local status: FutureStatus + local valueList: { T } + + if self.status == "Pending" then + self:await() + + status = self.status + valueList = self._valueList + else + status, valueList = self.status, self._valueList + end + + if status == "Fulfilled" then + local unwrappedObject = table.remove(valueList, 1) :: T + + if unwrappedObject == nil then + return value + end + + return unwrappedObject + else + return value + end +end + +--[[ + Execute the future, this will execute the asynchronous function, and resolve/reject the future. +]] +function Future.Prototype.execute(self: Future, ...): Future + local arguments = table.pack(...) + + task.spawn(function() + xpcall(function() + self._valueList = table.pack(self._callback(table.unpack(arguments))) + + self.status = "Fulfilled" + + for _, callback in self._afterCallbacks do + callback(table.unpack(self._valueList)) + end + end, function(exception: string) + self.exception = exception + self.status = "Rejected" + + if #self._catchCallback == 0 then + stdio.ewrite( + `{stdio.style("bold")}{stdio.color("red")}Future encountered an error:\n{stdio.color("blue")}{self.exception}\n` + ) + else + for _, callback in self._catchCallback do + task.spawn(callback, exception) + end + end + end) + + for _, callback in self._yieldingThreads do + task.spawn( + callback, + self.status, + self.status == "Fulfilled" and table.unpack(self._valueList) or self.exception + ) + end + end) + + return self +end + +--[[ + Constructor for the future object. +]] +function Future.Interface.new(callback: (...any) -> ...T?, ...): Future + local selfArguments = table.pack(...) + local self = setmetatable( + { + _callback = callback, + + _catchCallback = {}, + _yieldingThreads = {}, + _afterCallbacks = {}, + _valueList = {}, + + status = "Pending" :: FutureStatus, + } :: Future, + { __index = Future.Prototype } + ) + + task.defer(function() + self:execute(table.unpack(selfArguments)) + end) + + return self +end + +export type FutureStatus = "Pending" | "Fulfilled" | "Rejected" +export type Future = typeof(Future.Prototype) & { + _callback: (...T) -> ...any, + + _catchCallback: { (string) -> () }, + _yieldingThreads: { thread }, + _afterCallbacks: { (...T) -> () }, + _valueList: { T }, + + status: FutureStatus, + exception: string?, +} + +return Future.Interface diff --git a/packages/vendor/src/logger/init.luau b/packages/vendor/src/logger/init.luau new file mode 100644 index 0000000..6bc4e10 --- /dev/null +++ b/packages/vendor/src/logger/init.luau @@ -0,0 +1,124 @@ +--[[ + Logger, a simple logging implementation for Discord Luau. + + This implementation is based on the following document: + - https://www.sumologic.com/glossary/log-levels/ + + Please defer to the above document for when it is appropriate to use each log level. +]] + +local stdio = require("@std-polyfills/stdio") +local task = require("@std-polyfills/task") + +local LOGGER_LOG_LEVELS = table.freeze({ + "Debug", + "Info", + "Notice", + "Warn", + "Error", + "Critical", + "Alert", + "Emergency", +}) + +local LOGGER_LOG_FORMATS = table.freeze({ + ["Debug"] = `{stdio.style("dim")}{stdio.color("reset")}[%s][DEBUG]: %s`, + ["Info"] = `{stdio.color("cyan")}[%s][INFO]: %s`, + ["Notice"] = `{stdio.color("blue")}[%s][NOTICE]: %s`, + ["Warn"] = `{stdio.color("yellow")}[%s][WARN]: %s`, + ["Error"] = `{stdio.color("red")}[%s][ERROR]: %s`, + ["Critical"] = `{stdio.color("red")}[%s][CRITICAL]: %s`, + ["Alert"] = `{stdio.style("bold")}{stdio.color("red")}[%s][DEBUG]: %s`, + ["Emergency"] = `{stdio.style("bold")}{stdio.color("red")}[%s][DEBUG]: %s`, +}) + +local Logger = {} + +Logger.Prototype = {} +Logger.Interface = {} + +--[[ + Function responsible for parsing a varadic list of arguments into a string. +]] +local function parseVaradic(args: { any }): string + local source = "" + + for index, object in args do + source ..= `{index ~= 1 and ", " or ""}{stdio.format(object)}` + end + + return source +end + +--[[ + Function responsible for for setting the log level of the logger, for example - if the log level + is set to "Debug", then all log levels below, and "Debug" will be ignored. +]] +function Logger.Prototype.setLogLevel(self: Logger, logLevel: LogLevel) + local logLevelIndex = table.find(LOGGER_LOG_LEVELS, logLevel) + + assert(logLevelIndex, `Invalid log level: {logLevel}`) + + self.logLevel = logLevelIndex +end + +--[[ + Constructor for the Logger object. +]] +function Logger.Interface.new(name: string, logLevel: LogLevel?): Logger + local logLevelIndex = logLevel and table.find(LOGGER_LOG_LEVELS, logLevel) + + local self = setmetatable( + { + logLevel = logLevelIndex or 1, + } :: Logger, + { __index = Logger.Prototype } + ) + + -- this block of code will generate a function for each log level, and set it to the Logger object. + for logValue, logType in LOGGER_LOG_LEVELS do + local camelCaseMethod = `{string.lower(string.sub(logType, 1, 1))}{string.sub(logType, 2, #logType)}`; + + (self :: {})[camelCaseMethod] = function(_, ...) + if logValue < self.logLevel then + return + end + + local logMessage = parseVaradic({ ... }) + + print( + `{string.format(LOGGER_LOG_FORMATS[logType], name, logMessage)}{stdio.style("reset")}{stdio.color( + "reset" + )}` + ) + + if logValue >= 5 then + local thread = coroutine.running() + + task.defer(function() + task.cancel(thread) + end) + + coroutine.yield() + end + end + end + + return self +end + +export type LogLevel = "Debug" | "Info" | "Notice" | "Warn" | "Error" | "Critical" | "Alert" | "Emergency" +export type Logger = typeof(Logger.Prototype) & { + logLevel: number, + + debug: (self: Logger, ...any) -> (), + info: (self: Logger, ...any) -> (), + notice: (self: Logger, ...any) -> (), + warn: (self: Logger, ...any) -> (), + error: (self: Logger, ...any) -> (), + critical: (self: Logger, ...any) -> (), + alert: (self: Logger, ...any) -> (), + emergency: (self: Logger, ...any) -> (), +} + +return Logger.Interface diff --git a/packages/vendor/src/stream/init.luau b/packages/vendor/src/stream/init.luau new file mode 100644 index 0000000..a1b5dff --- /dev/null +++ b/packages/vendor/src/stream/init.luau @@ -0,0 +1,114 @@ +--[[ + A Stream is a type of object that can be used to navigate through a string safely, this is used in obscure + operations such as parsing FormData, JSON and other things. + + Streams are not essential, but they do make things easier as we can avoid tracking the position of a string + and instead rely on the Stream to peek, advance and so forth. +]] + +local Stream = {} + +Stream.Interface = {} +Stream.Prototype = {} + +--[[ + Responsible for peeking further ahead into the stream, without incrementing the cursor position. +]] +function Stream.Prototype.peek(self: Stream, length: number?): string? + if self.cursorPosition + 1 > #self.textSource then + return nil + end + + return string.sub( + self.textSource, + self.cursorPosition + 1, + length and self.cursorPosition + length or self.cursorPosition + 1 + ) +end + +--[[ + Responsible for advancing the cursor position of the stream, this will increment the cursor position by the + amount specified. (defaults to 1) +]] +function Stream.Prototype.advance(self: Stream, length: number?): string? + local absoluteLength = length or 1 + + if self.cursorPosition + absoluteLength > #self.textSource then + return nil + end + + local blockSource = string.sub(self.textSource, self.cursorPosition + 1, self.cursorPosition + absoluteLength) + + self.cursorPosition += absoluteLength + + return blockSource +end + +--[[ + Responsible for advancing the cursor position of the stream until a predicate is met, this will increment the + cursor position by the amount specified. +]] +function Stream.Prototype.advanceUntil(self: Stream, predicate: (char: string) -> ()): string? + local internalCursor = self.cursorPosition + 1 + local char = string.sub(self.textSource, internalCursor, internalCursor) + + while char and not predicate(char) do + internalCursor += 1 + + if internalCursor > #self.textSource then + return nil + end + + char = string.sub(self.textSource, internalCursor, internalCursor) + end + + local blockSource = string.sub(self.textSource, self.cursorPosition + 1, internalCursor) + + self.cursorPosition = internalCursor + + return blockSource +end + +--[[ + Will read the stream until the end, this will return the remaining text source. +]] +function Stream.Prototype.readUntilEnd(self: Stream): string + local blockSource = string.sub(self.textSource, self.cursorPosition + 1, #self.textSource) + + return blockSource +end + +--[[ + Trims all whitespaces from the current strean position and onwards +]] +function Stream.Prototype.trim(self: Stream) + local peekValue = self:peek() + + while peekValue and string.match(peekValue, "%s") do + self:advance() + + peekValue = self:peek() + end +end + +--[[ + Constructor for the Stream object, will return a new instance of the Stream object. +]] +function Stream.Interface.new(textSource: string): Stream + return setmetatable( + { + textSource = textSource, + cursorPosition = 0, + } :: Stream, + { + __index = Stream.Prototype, + } + ) +end + +export type Stream = typeof(Stream.Prototype) & { + textSource: string, + cursorPosition: number, +} + +return Stream.Interface diff --git a/packages/voice/README.md b/packages/voice/README.md new file mode 100644 index 0000000..08a9f79 --- /dev/null +++ b/packages/voice/README.md @@ -0,0 +1,9 @@ +
+

+ discord-luau +

+
+ +## DiscordLuau - Voice + +This package is responsible for implementing voice support in DiscordLuau. One of the difficulties of this package will be overcoming the 64 bit encrypted voice packets that are sent by Discord. diff --git a/packages/voice/src/.gitkeep b/packages/voice/src/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/websocket/README.md b/packages/websocket/README.md new file mode 100644 index 0000000..bec4fd5 --- /dev/null +++ b/packages/websocket/README.md @@ -0,0 +1,9 @@ +
+

+ discord-luau +

+
+ +## DiscordLuau - Websocket + +This package is responsible for implementing a websocket client for DiscordLuau. diff --git a/packages/websocket/src/manager.luau b/packages/websocket/src/manager.luau new file mode 100644 index 0000000..1a45384 --- /dev/null +++ b/packages/websocket/src/manager.luau @@ -0,0 +1,172 @@ +local task = require("@std-polyfills/task") + +local emitter = require("@vendor/emitter") +local future = require("@vendor/future") +local logger = require("@vendor/logger") + +local gatewayTypes = require("@api-types/gateway/types") +local restTypes = require("@api-types/rest/types") + +local secret = require("@classes/secret") + +local receiveEvents = require("@api-types/gateway/receiveEvents") + +local shard = require("@websocket/shard") + +local CONCURRENT_IDENTIFY_YIELD = 5 + +local Manager = {} + +Manager.Interface = {} +Manager.Prototype = {} + +function Manager.Prototype.connectAsync(self: Manager, gatewayInformation: restTypes.GetGatewayBotResponse) + return future.new(function() + local maxConcurrency = gatewayInformation.session_start_limit.max_concurrency + + local websocketUrl = gatewayInformation.url + local shardCount = gatewayInformation.shards + + local readyShards = {} + + self.logger:debug(`Creating all Shards required for Manager..`) + + for shardId = 0, shardCount - 1 do + self.logger:debug(`Creating new Shard '{shardId}'`) + + table.insert(self.shardIds, shardId) + table.insert( + self.shards, + shard.new({ + token = self.token.value, + intents = self.intents, + largeThreshold = self.largeThreshold, + + shardId = shardId, + shardCount = shardCount, + }) + ) + end + + self.logger:debug(`Connecting all Shards to the Discord Gateway..`) + + for shardIndex, shardInstance in next, self.shards do + if shardIndex ~= 1 and shardIndex % maxConcurrency == 0 then + task.wait(CONCURRENT_IDENTIFY_YIELD) + end + + self.logger:debug(`Connecting Shard '{shardIndex - 1}' to Discord Gateway`) + + shardInstance:connectAsync(websocketUrl, self.webSocketVersion):await() + shardInstance.onSocketDispatch:listen(function(event) + self.onDispatch:invoke({ + shardId = shardIndex - 1, + event = event.t, + payload = event, + }) + + if event.t == receiveEvents.Ready then + table.insert(readyShards, shardIndex - 1) + + if #readyShards == shardCount then + self.onAllShardsReady:invoke({ + shardId = shardIndex - 1, + payload = event, + }) + end + end + end) + + shardInstance.onSocketHeartbeat:listen(function(ping: number?) + self.onHeartbeat:invoke({ + shardId = shardIndex - 1, + latency = ping, + }) + end) + + shardInstance.onSocketConnected:listen(function() + self.onConnected:invoke({ + shardId = shardIndex - 1, + }) + end) + + shardInstance.onSocketReconnected:listen(function() + self.onReconnected:invoke({ + shardId = shardIndex - 1, + }) + end) + + shardInstance.onSocketDisconnected:listen(function(code: number?) + self.onDisconnected:invoke({ + shardId = shardIndex - 1, + errorCode = code, + }) + end) + end + end) +end + +function Manager.Prototype.send() end + +function Manager.Interface.new(settings: { + token: secret.Secret, + intents: number, + largeThreshold: number?, + + webSocketVersion: number, +}): Manager + return setmetatable( + { + token = settings.token, + intents = settings.intents, + webSocketVersion = settings.webSocketVersion, + largeThreshold = settings.largeThreshold, + + shardIds = {}, + shards = {}, + + logger = logger.new("Manager"), + + onDisconnected = emitter.new(), + onReconnected = emitter.new(), + onConnected = emitter.new(), + onHeartbeat = emitter.new(), + onDispatch = emitter.new(), + + onAllShardsReady = emitter.new(), + } :: Manager, + { __index = Manager.Prototype } + ) +end + +export type ShardId = number +export type ShardPayload = { shardId: number } & DATA + +export type Manager = typeof(Manager.Prototype) & { + token: secret.Secret, + intents: number, + webSocketVersion: number, + largeThreshold: number?, + + shardIds: { ShardId }, + shards: { [ShardId]: shard.Shard }, + + logger: logger.Logger, + + onDisconnected: emitter.Emitter>, + onReconnected: emitter.Emitter>, + onConnected: emitter.Emitter>, + onHeartbeat: emitter.Emitter>, + onDispatch: emitter.Emitter, + }>>, + + onAllShardsReady: emitter.Emitter>, +} + +return Manager.Interface diff --git a/packages/websocket/src/shard.luau b/packages/websocket/src/shard.luau new file mode 100644 index 0000000..8345ad2 --- /dev/null +++ b/packages/websocket/src/shard.luau @@ -0,0 +1,631 @@ +local net = require("@std-polyfills/net") +local task = require("@std-polyfills/task") +local serde = require("@std-polyfills/serde") +local process = require("@std-polyfills/process") + +local logger = require("@vendor/logger") +local future = require("@vendor/future") +local buffer = require("@vendor/buffer") +local emitter = require("@vendor/emitter") + +local receiveEvents = require("@api-types/gateway/receiveEvents") +local opcodes = require("@api-types/gateway/opcodes") +local closeCodes = require("@api-types/gateway/closeCodes") +local gatewayTypes = require("@api-types/gateway/types") + +-- selene: allow(bad_string_escape) +local ZLIB_HEADER = "\120\156\141\84" +local LIBRARY_IDENTIFIER = "DiscordLuau" + +--[[ + Shard is responsible for managing a single WebSocket connection to Discord's Gateway API. + Its key responsibilities include: + + 1. Establishing and maintaining a WebSocket connection + 2. Handling incoming messages: + - Decompressing ZLIB-compressed messages + - Decoding JSON payloads + - Invoking callbacks for processed messages + 3. Sending outgoing messages, including heartbeats + 4. Managing the heartbeat mechanism: + - Sending periodic heartbeats to keep the connection alive + - Tracking heartbeat acknowledgements + - Initiating reconnection if heartbeats are not acknowledged + 5. Handling reconnection logic when the connection becomes unstable or "zombified" + 6. Providing an interface for other parts of the application to interact with the WebSocket connection + + The Shard module is crucial for maintaining real-time communication with Discord's Gateway, + ensuring the bot stays connected and can send and receive updates efficiently. +]] +local Shard = {} + +Shard.Interface = {} +Shard.Prototype = {} + +--[[ + Function responsible for handling incoming messages from the WebSocket connection. + + It performs the following steps: + 1. Decompresses the incoming message if it's ZLIB-compressed + i. It's worth noting that payloads may not be complete, meaning we should add data to the + buffer until we have a complete message + 2. Decodes the JSON payload + 3. Invokes the appropriate callback based on the received event type + + Roughly based on the following documentation: + https://discord.com/developers/docs/topics/gateway#zlibstream +]] +function Shard.Prototype._handleMessage(self: Shard, message: string): () + local isJson, jsonContent = pcall(serde.decode, "json" :: any, message) + + if isJson then + self.logger:debug(`Shard '{self.shardId}' received message: #{string.len(message)}`) + + self.onSocketRawMessage:invoke(jsonContent) + else + self.zlibBuffer:write(message) + + if string.len(message) < 4 or string.sub(message, 1, 4) ~= ZLIB_HEADER then + self.logger:debug(`Shard '{self.shardId}' received partial message: #{string.len(message)}`) + + return + end + + self.logger:debug(`Shard '{self.shardId}' received complete message: #{string.len(message)}, decompressing...`) + + local compressedData: string = self.zlibBuffer:flush() + local success0, response0 = pcall(serde.decompress, "zlib" :: any, compressedData) + + if success0 then + self.logger:debug(`Shard '{self.shardId}' decompressed Shard message: #{string.len(response0)}`) + + local success1, response1 = pcall(serde.decode, "json" :: any, response0) + + if success1 then + self.onSocketRawMessage:invoke(response1) + else + self.logger:error(`Shard '{self.shardId}' failed to decode Shard JSON message: {response1}`) + end + else + self.logger:error(`Shard '{self.shardId}' failed to decompress Shard ZLIB message: {response0}`) + end + end +end + +--[[ + Sends a heartbeat to the Discord Gateway API. +]] +function Shard.Prototype.heartbeatAsync(self: Shard, requested: boolean?): future.Future + return future.new(function() + if not self.heartbeatAcknowledged and not requested then + self.logger:warn(`Shard '{self.shardId}' discord WebSocket state has become Zombified, reconnecting...`) + + self:reconnectAsync():await() + end + + self.heartbeatClockTime = os.clock() + self.heartbeatAcknowledged = false + + self.logger:debug(`Shard '{self.shardId}' sending heartbeat request`) + + self:sendAsync(net.jsonEncode({ + ["op"] = opcodes.Heartbeat, + ["d"] = self.lastSequence or false, + }, true)):await() + end) +end + +--[[ + Identifies the current shard with the Discord Gateway. +]] +function Shard.Prototype.identifyAsync(self: Shard): future.Future + return future.new(function() + self.logger:debug(`Shard '{self.shardId}' sending identify request`) + + self:sendAsync(net.jsonEncode({ + ["op"] = opcodes.Identify, + ["d"] = { + ["token"] = self.token, + ["intents"] = self.intents, + ["compress"] = true, + ["large_threshold"] = self.largeThreshold or 250, + ["shard"] = { + self.shardId, + self.shardCount, + }, + ["properties"] = { + ["os"] = process.os, + ["browser"] = LIBRARY_IDENTIFIER, + ["device"] = LIBRARY_IDENTIFIER, + }, + }, + }, true)):await() + end) +end + +--[[ + Disconnects the Shard from the Discord Gateway. + + Optionally, a WebSocket close code can be provided, websocket close code spec can be found here: + https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent/code +]] +function Shard.Prototype.disconnectAsync(self: Shard, code: number?): future.Future + return future.new(function() + self.logger:debug(`Shard '{self.shardId}' requesting disconnect: {code or 0}`) + + self.onSocketDisconnected:invoke(code) + + if self.socketInstance then + self.socketInstance.close(code) + end + + if self.socketThread then + task.cancel(self.socketThread) + end + + self.socketActive = false + self.socketInstance = nil + + self.logger:debug(`Shard '{self.shardId}' disconnected.`) + end) +end + +--[[ + Connects the shard to a Discord Gateway. + + Expects both a Socket URL, and Socket Version to be provided. It will prepend the socket version + to the socket URL, and then connect to the socket. + + Also responsible for spawning the thread that will be responsible for + receiving messages from the socket. +]] +function Shard.Prototype.connectAsync(self: Shard, socketUrl: string, socketVersion: number): future.Future + return future.new(function() + assert(not self.socketActive, `Shard '{self.shardId}' already connected to a socket.`) + + self.logger:debug(`Shard '{self.shardId}' connecting to: {`{socketUrl}/?v={socketVersion}`}`) + + local success0, response0 = pcall(net.socket, `{socketUrl}/?v={socketVersion}`) + + assert(success0, response0) + + if not self.socketVersion then + self.socketVersion = socketVersion + end + + if not self.socketUrl then + self.socketUrl = socketUrl + end + + self.socketInstance = response0 + self.socketActive = true + + self.logger:debug(`Shard '{self.shardId}' has connected to: {socketUrl}, starting receive thread.`) + + self.socketThread = task.defer(function() + self.logger:debug(`Shard '{self.shardId}' Receive thread started, awaiting messages.`) + + self.onSocketConnected:invoke() + + while self.socketActive do + assert(self.socketInstance, `Socket instance is nil.`) + + if self.socketInstance.closeCode then + self.logger:warn(`Shard '{self.shardId}' closed from server: {self.socketInstance.closeCode}`) + + self.onSocketDisconnected:invoke(self.socketInstance.closeCode) + + self.socketActive = false + self.socketInstance = nil + else + local success, response = pcall(self.socketInstance.next) + + if success and response then + self:_handleMessage(response :: string) + else + self.logger:warn( + `Shard '{self.shardId}' error during receive: {response and response or "No error provided, see close code"}` + ) + + self.onSocketDisconnected:invoke(self.socketInstance.closeCode) + + self.socketActive = false + self.socketInstance = nil + end + end + end + + self.socketThread = nil + self.logger:debug(`Shard '{self.shardId}' socket has disconnected, receive thread stopped.`) + end) + end) +end + +--[[ + Resumes a shard session if for some reason the session has either become + zombified, or the session has been invalidated by Discord. +]] +function Shard.Prototype.resumeAsync(self: Shard): future.Future + return future.new(function() + self:connectAsync(self.sessionGateway :: string, self.socketVersion :: number) + :after(function() + self.logger:debug(`Shard '{self.shardId}' attempting to resume..`) + + self:sendAsync(net.jsonEncode({ + ["op"] = opcodes.Resume, + ["d"] = { + ["token"] = self.token, + ["session_id"] = self.sessionId, + ["seq"] = self.lastSequence, + }, + }, true)):after(function() + self.logger:debug(`Shard '{self.shardId}' has resumed!`) + end) + end) + :catch(function(exception) + self.logger:critical(`Shard '{self.shardId}' failed to resume session: {exception}`) + end) + :await() + end) +end + +--[[ + Closes the websocket connection and reconnects the shard. + This is not a re-instantiation of the shard, but a re-connection of the shard. +]] +function Shard.Prototype.reconnectAsync(self: Shard): future.Future + return future.new(function() + if self.sessionGateway then + self.logger:debug(`Shard '{self.shardId}' reconnection requested, reconnecting..`) + + self:disconnectAsync(1012) + :after(function() + task.wait(math.random()) + + self:resumeAsync() + :after(function() + self.logger:debug(`Shard '{self.shardId}' reconnection successful`) + end) + :await() + end) + :await() + else + self.logger:warn(`Shard '{self.shardId}' failed to reconnect! Re-instantiating instead!`) + + self:reinstantiateAsync():await() + end + end) +end + +--[[ + Sends a message through the WebSocket to Discords Gateway. + + NOTE: Messages are expected to be JSON encoded strings. +]] +function Shard.Prototype.sendAsync(self: Shard, data: string): future.Future + local debugTraceback = debug.traceback() + + return future.new(function() + assert(self.socketActive and self.socketInstance, `Shard '{self.shardId}' not connected to a socket.`) + + self.logger:debug(`Shard '{self.shardId}' sending message of length: {string.len(data)}`) + + local success, response: string? = pcall(self.socketInstance.send, data, true) + + if not success then + self.socketActive = false + self.socketInstance = nil + + self.logger:warn(`Shard '{self.shardId}' error during send: {response}\nTraceback: {debugTraceback}`) + + self.onSocketDisconnected:invoke(self.socketInstance.closeCode) + end + end) +end + +--[[ + Disconnects the Shard, and re-instantiats it. + + Re-instantiation is the reconnection of the shard to the original socket passed, and not the + session URL/ID that was passed. + + Any events between the initial disconnection and reconnection will not be recovered. If this is + what you intend to do, use `Shard:reconnectAsync` instead. +]] +function Shard.Prototype.reinstantiateAsync(self: Shard): future.Future + return future.new(function() + self.logger:debug(`Shard '{self.shardId}' re-instantiation requested..`) + + self:disconnectAsync(1012) + :after(function() + task.wait(math.random()) + + self:connectAsync(self.socketUrl :: string, self.socketVersion :: number) + :after(function() + self.logger:warn( + `Shard '{self.shardId}' re-instantiation successful, shard {self.shardId} has recovered.` + ) + end) + :catch(function(exception1) + self.logger:emergency( + `Shard '{self.shardId}' exception during re-instantiation, shard {self.shardId} is now dead! \n{exception1}` + ) + end) + end) + :await() + end) +end + +--[[ + Sends a heartbeat after `milliseconds` milliseconds, looping and continuing to heartbeat + until the shard is disconnected. +]] +function Shard.Prototype.heartbeatIn(self: Shard, milliseconds: number): () + if self.heartbeatThread then + task.cancel(self.heartbeatThread) + end + + self.logger:debug(`Shard '{self.shardId}' to heartbeat in '{milliseconds}' ms`) + + self.heartbeatThread = task.delay(milliseconds / 1000, function() + self.heartbeatThread = nil + + self:heartbeatAsync() + :after(function() + self:heartbeatIn(milliseconds) + end) + :catch(function(exception) + self:disconnectAsync(1011):after(function() + self.logger:critical(`Shard '{self.shardId}' exception during heartbeat: {exception}`) + end) + end) + end) +end + +--[[ + Constructor function for Shard. +]] +function Shard.Interface.new(settings: { + token: string, + + intents: number, + shardId: number, + shardCount: number, + + largeThreshold: number?, +}): Shard + local self = setmetatable( + { + token = settings.token, + intents = settings.intents, + shardId = settings.shardId, + shardCount = settings.shardCount, + + largeThreshold = settings.largeThreshold, + + zlibBuffer = buffer.new(), + + onSocketConnected = emitter.new(), + onSocketReconnected = emitter.new(), + onSocketDisconnected = emitter.new(), + + onSocketHeartbeat = emitter.new(), + + onSocketDispatch = emitter.new(), + onSocketRawMessage = emitter.new(), + + logger = logger.new(`Shard<{settings.shardId}>`), + + socketActive = false, + } :: Shard, + { __index = Shard.Prototype } + ) + + -- The following handles "raw" payload/messages from the Discord Websocket. + self.onSocketRawMessage:listen(function(payload) + if payload.op == opcodes.Dispatch then + -- https://discord.com/developers/docs/topics/gateway#dispatch-events + + self.logger:debug(`Shard '{self.shardId}' received dispatch message: {payload.t}`) + + self.lastSequence = payload.s + + self.onSocketDispatch:invoke(payload) + elseif payload.op == opcodes.Heartbeat then + -- https://discord.com/developers/docs/topics/gateway-events#heartbeat + + self.logger:debug(`Shard '{self.shardId}' received heartbeat request, responding.`) + self:heartbeatAsync(true) + elseif payload.op == opcodes.Reconnect then + -- https://discord.com/developers/docs/topics/gateway-events#reconnect + + self:reconnectAsync() + elseif payload.op == opcodes.InvalidSession then + -- https://discord.com/developers/docs/topics/gateway-events#invalid-session + + local messageObject = payload :: gatewayTypes.InvalidSessionPayload + + self.logger:warn(`Shard '{self.shardId}' discord returned invalid session, reconnecting..`) + + if messageObject.d then + self.logger:warn(`Shard '{self.shardId}' discord invalid session is recoverable, resuming session..`) + + self:reconnectAsync():catch(function(exception0) + self.logger:warn( + `Shard '{self.shardId}' unrecoverable error during reconnection, re-instantiating shard.. \n{exception0}` + ) + + self:reinstantiateAsync() + end) + else + self.logger:warn( + `Shard '{self.shardId}' unrecoverable error during connection, re-instantiating shard..` + ) + + self:reinstantiateAsync() + end + elseif payload.op == opcodes.Hello then + -- https://discord.com/developers/docs/topics/gateway-events#hello + + self.logger:debug(`Shard '{self.shardId}' received hello message, starting heartbeat..`) + + local messageObject = payload :: gatewayTypes.HelloPayload + + self.heartbeatInterval = messageObject.d.heartbeat_interval + + task.wait(math.random()) + + self:heartbeatAsync(true) + :after(function() + self:heartbeatIn(self.heartbeatInterval :: number) + end) + :catch(function(exception) + self:disconnectAsync(1011):after(function() + self.logger:critical(`Exception during initial heartbeat: {exception}`) + end) + end) + elseif payload.op == opcodes.HeartbeatACK then + -- https://discord.com/developers/docs/topics/gateway#heartbeat-interval + + self.logger:debug(`Shard '{self.shardId}' received heartbeat acknowledgement.`) + + self.heartbeatAcknowledged = true + self.heartbeatPing = os.clock() - self.heartbeatClockTime :: number + + self.onSocketHeartbeat:invoke(self.heartbeatPing) + + if not self.hasIdentified then + self.hasIdentified = true + + self.logger:debug(`Shard '{self.shardId}' hasn't identified yet, identifying..`) + + self:identifyAsync():catch(function(exception) + self.hasIdentified = nil + + self.logger:critical(`Shard '{self.shardId}' exception during initial identify: {exception}`) + end) + end + end + end) + + --[[ + The following handles events from the Discord Websocket, we're specifically awaiting the 'Ready' + event, which is emitted when the shard has identified. + + The Ready event contains the resume gateway url, and the session id, which is key for us + in order to resume the session. + ]] + self.onSocketDispatch:listen(function(payload) + if payload.t ~= receiveEvents.Ready then + return + end + + local message = payload :: gatewayTypes.ReadyPayload + + self.sessionGateway = message.d.resume_gateway_url + self.sessionId = message.d.session_id + + self.logger:debug( + `Shard '{self.shardId}' has identified as {message.d.user.username}#{message.d.user.discriminator}` + ) + end) + + self.onSocketDisconnected:listen(function(code) + if self.heartbeatThread then + task.cancel(self.heartbeatThread) + end + + if code == closeCodes.UnknownError then + self:reconnectAsync() + elseif code == closeCodes.UnknownOpcode then + self:reconnectAsync() + elseif code == closeCodes.DecodeError then + self:reconnectAsync() + elseif code == closeCodes.NotAuthenticated then + self.logger:error( + `Shard '{self.shardId}', Something has gone terribly wrong with Discord-Luau.. please create an issue! [{code}]` + ) + elseif code == closeCodes.AuthenticationFailed then + self.logger:warn( + `Shard '{self.shardId}', Failed to authenticate with the Discord API, is your token correct?` + ) + elseif code == closeCodes.AlreadyAuthenticated then + self:reconnectAsync() + elseif code == closeCodes.InvalidSeq then + self:reconnectAsync() + elseif code == closeCodes.Ratelimited then + self:reconnectAsync() + elseif code == closeCodes.SessionTimedOut then + self:reconnectAsync() + elseif code == closeCodes.InvalidShard then + self.logger:error( + `Shard '{self.shardId}', Something has gone terribly wrong with Discord-Luau.. please create an issue! [{code}]` + ) + elseif code == closeCodes.InvalidAPIVersion then + self.logger:error( + `Shard '{self.shardId}', Something has gone terribly wrong with Discord-Luau.. please create an issue! [{code}]` + ) + elseif code == closeCodes.InvalidIntents then + self.logger:error( + `Shard '{self.shardId}', Something has gone terribly wrong with Discord-Luau.. please create an issue! [{code}]` + ) + elseif code == closeCodes.DisallowedIntents then + self.logger:warn( + `Shard '{self.shardId}', intent '{self.intents}' are invalid, are you sure the bot has the chosen intents enabled?` + ) + else + self.logger:warn(`Unknown close code: '{code}' - will attempt to resume Shard '{self.shardId}'`) + self.logger:info( + `If you have the time, please create an issue on the Discord-Luau repository! This is not normal behaviour!` + ) + + self:reconnectAsync() + end + end) + + return self +end + +export type Shard = typeof(Shard.Prototype) & { + token: string, + intents: number, + shardId: number, + shardCount: number, + + hasIdentified: boolean?, + + largeThreshold: number?, + + zlibBuffer: buffer.Buffer, + + onSocketConnected: emitter.Emitter<()>, + onSocketReconnected: emitter.Emitter, + onSocketDisconnected: emitter.Emitter, + + onSocketHeartbeat: emitter.Emitter, + + onSocketDispatch: emitter.Emitter>, + onSocketRawMessage: emitter.Emitter>, + + heartbeatAcknowledged: boolean?, + heartbeatClockTime: number?, + heartbeatInterval: number?, + heartbeatThread: thread?, + heartbeatPing: number?, + + lastSequence: number?, + + logger: logger.Logger, + + socketActive: boolean, + + sessionId: string?, + sessionGateway: string?, + + socketVersion: number?, + socketUrl: string?, + socketInstance: net.WebSocket?, + socketThread: thread?, +} + +return Shard.Interface diff --git a/selene.toml b/selene.toml index fd96537..3e4c832 100644 --- a/selene.toml +++ b/selene.toml @@ -1,7 +1,6 @@ -std = "luau" - -exclude = ["luneTypes.d.luau"] +std = "lune" [lints] -high_cyclomatic_complexity = "warn" -if_same_then_else = "allow" \ No newline at end of file +high_cyclomatic_complexity = "allow" +if_same_then_else = "allow" +shadowing = "allow" \ No newline at end of file diff --git a/tests/builders/channelBuilder.spec.luau b/tests/builders/channelBuilder.spec.luau new file mode 100644 index 0000000..87c658f --- /dev/null +++ b/tests/builders/channelBuilder.spec.luau @@ -0,0 +1,29 @@ +local builder = require("@builders/channel") +local frktest = require("@frktest/frktest") + +local test = frktest.test +local check = frktest.assert.check + +return function() + test.case("ChannelBuilder creates a channel with correct properties", function() + local builder = builder.new() + + local channel = + builder:setName("test-channel"):setType("GuildText"):setTopic("Test topic"):setPosition(1):build() + + check.equal(channel.name, "test-channel") + check.equal(channel.type, 0) + check.equal(channel.topic, "Test topic") + check.equal(channel.position, 1) + end) + + test.case("ChannelBuilder allows chaining of methods", function() + local builder = builder.new() + + check.should_not_error(function() + builder:setName("chained-channel"):setType("GuildText"):setTopic("Chained topic"):setPosition(2) + + return builder + end) + end) +end diff --git a/tests/vendor/bit64.spec.luau b/tests/vendor/bit64.spec.luau new file mode 100644 index 0000000..b5797c5 --- /dev/null +++ b/tests/vendor/bit64.spec.luau @@ -0,0 +1,34 @@ +local frktest = require("@frktest/frktest") + +local bit = require("@vendor/bit") + +local BIT64_NUMBER = 2147483647 + 1 +local DISCORD_ID = 685566749516628033 + +local test = frktest.test +local check = frktest.assert.check + +return function() + -- https://circuitdigest.com/calculators/bit-shift-calculator + test.case("is able to SHIFT a 64bit int", function() + check.equal(bit.shift(BIT64_NUMBER, -2), 8589934592) + check.equal(bit.shift(BIT64_NUMBER, 2), 536870912) + end) + + -- https://dqydj.com/bitwise-not-calculator/#Bitwise_Not_Calculator + test.case("is able to NOT a 64bit int", function() + check.equal(bit.bnot(BIT64_NUMBER), 18446744071562068000) + end) + + -- https://miniwebtool.com/bitwise-calculator/ + test.case("is able to OR a 64bit int", function() + check.equal(bit.bor(BIT64_NUMBER, 0), BIT64_NUMBER) + check.equal(bit.bor(BIT64_NUMBER, 2), 2147483650) + end) + + -- https://miniwebtool.com/bitwise-calculator/ + test.case("is able to AND a 64bit int", function() + check.equal(bit.band(BIT64_NUMBER, BIT64_NUMBER), BIT64_NUMBER) + check.equal(bit.band(DISCORD_ID, 0x3E0000), 131072) + end) +end diff --git a/tests/vendor/emitter.spec.luau b/tests/vendor/emitter.spec.luau new file mode 100644 index 0000000..152d390 --- /dev/null +++ b/tests/vendor/emitter.spec.luau @@ -0,0 +1,125 @@ +local frktest = require("@frktest/frktest") + +local emitter = require("@vendor/emitter") + +local test = frktest.test +local check = frktest.assert.check + +return function() + test.case("is able to construct an event emitter instance", function() + local object = emitter.new() + + check.not_nil(object) + end) + + test.suite("listening to the event emitter", function() + test.case("listener is invoked", function() + local object = emitter.new() + local variable = 0 + + object:listenOnce(function() + variable += 1 + end) + + object:invoke() + + check.equal(variable, 1) + end) + + test.case("listener handles multiple invocations", function() + local object = emitter.new() + local variable = 0 + + local disconnect = object:listen(function() + variable += 1 + end) + + for _ = 1, 1000 do + object:invoke() + end + + disconnect() + + check.equal(variable, 1000) + end) + + test.case("listener can be disconnected", function() + local object = emitter.new() + local variable = 0 + + local disconnect = object:listen(function() + variable += 1 + end) + + object:invoke() + + check.equal(variable, 1) + + disconnect() + + object:invoke() + + check.equal(variable, 1) + end) + + test.case("listenOnce only stays connected for one invocation", function() + local object = emitter.new() + local variable = 0 + + object:listenOnce(function() + variable += 1 + end) + + object:invoke() + + check.equal(variable, 1) + + object:invoke() + + check.equal(variable, 1) + end) + + test.case("premature disconnection for listenOnce is accepted", function() + local object = emitter.new() + local variable = 0 + + local disconnect = object:listenOnce(function() + variable += 1 + end) + + disconnect() + + object:invoke() + + check.equal(variable, 0) + end) + end) + + test.case("is able to wait for an event emitter to be invoked", function() + local object = emitter.new() + + local variable + + coroutine.wrap(function() + variable = object:wait() + end)() + + object:invoke("Hello, World!") + + check.equal(variable, "Hello, World!") + end) + + test.case("is able to invoke an emitter with varadics", function() + local object = emitter.new() + + local variable + + object:listenOnce(function(a) + variable = a + end) + + object:invoke("Hello, World!") + + check.equal(variable, "Hello, World!") + end) +end diff --git a/tests/vendor/formdata.spec.luau b/tests/vendor/formdata.spec.luau new file mode 100644 index 0000000..04b0bf8 --- /dev/null +++ b/tests/vendor/formdata.spec.luau @@ -0,0 +1,75 @@ +local frktest = require("@frktest/frktest") + +local formdata = require("@vendor/formdata") + +local RAW_FORM_DATA_CASE = + '--TEST-CASE\r\nContent-Disposition: form-data; name="example"\r\nContent-Type: application/octet-stream\r\n\r\nvalue\r\n--TEST-CASE--\r\n' + +local test = frktest.test +local check = frktest.assert.check + +return function() + test.case("is able to construct an formdata object", function() + local object = formdata.new() + + check.not_nil(object) + end) + + test.case("is able to add data to the formdata object", function() + local object = formdata.new() + + object:append("example", "value") + + check.greater(#object.entries, 0) + check.is_true(object:has("example")) + end) + + test.case("is able to construct the raw formdata implementation", function() + local object = formdata.new() + + object.boundary = "TEST-CASE" + + object:append("example", "value") + + local bodyImplementation = object:getBody() + local headerImplementation = object:getHeader() + + check.truthy(bodyImplementation) + check.truthy(headerImplementation) + + check.equal(bodyImplementation, RAW_FORM_DATA_CASE) + end) + + test.case("is able to parse raw formdata into a formdata object", function() + local object = formdata.parse(RAW_FORM_DATA_CASE, "multipart/form-data; boundary=TEST-CASE") + + check.truthy(object) + check.table.contains(object.entries, { + name = "example", + value = "value", + }) + end) + + test.case("is able to parse a newly created formdata object", function() + local formdataObject = formdata.new() + + for index = 1, 50 do + formdataObject:append("example", "value", `example{index}.txt`) + end + + local rawFormData = formdataObject:getBody() + local rawFormHeader = formdataObject:getHeader() + + local parsedFormData = formdata.parse(rawFormData, rawFormHeader) + + check.truthy(parsedFormData) + + for index = 1, 50 do + check.table.contains(parsedFormData.entries, { + name = "example", + value = "value", + filename = `example{index}.txt`, + }) + end + end) +end diff --git a/tests/vendor/future.spec.luau b/tests/vendor/future.spec.luau new file mode 100644 index 0000000..f88d9d9 --- /dev/null +++ b/tests/vendor/future.spec.luau @@ -0,0 +1,33 @@ +-- NOTE: we're using the `Lune` runtime in the CI/CD environment, so we're not going to be using the `@std-polyfills` +-- package that the discord-luau package uses. + +local task = require("@lune/task") + +local frktest = require("@frktest/frktest") + +local future = require("@vendor/future") + +local test = frktest.test +local check = frktest.assert.check + +return function() + test.case("is able to construct an future object", function() + local object = future.new(function() end) + + check.not_nil(object) + end) + + test.case("is able to resolve future objects", function() + local variable + + local object = future.new(function() + task.wait() + + variable = "Hello World" + end) + + object:await() + + check.equal(variable, "Hello World") + end) +end diff --git a/tests/vendor/stream.spec.luau b/tests/vendor/stream.spec.luau new file mode 100644 index 0000000..891a631 --- /dev/null +++ b/tests/vendor/stream.spec.luau @@ -0,0 +1,66 @@ +local frktest = require("@frktest/frktest") + +local stream = require("@vendor/stream") + +local test = frktest.test +local check = frktest.assert.check + +return function() + test.case("is able to construct an stream object", function() + local object = stream.new("example") + + check.not_nil(object) + check.equal(object.textSource, "example") + end) + + test.suite("advancing an stream object", function() + test.case("advancing in a stream respects order", function() + local object = stream.new("abcdef") + + check.equal(object:advance(2), "ab") + check.equal(object:advance(2), "cd") + check.equal(object:advance(2), "ef") + end) + + test.case("is able to advance an stream object", function() + local object = stream.new("example") + + check.equal(object.cursorPosition, 0) + + local char = object:advance() + + check.equal(char, "e") + check.equal(object.cursorPosition, 1) + + object:advance(2) + + check.equal(object.cursorPosition, 3) + end) + + test.case("is able to advance multiple characters", function() + local object = stream.new("example") + + check.equal(object.cursorPosition, 0) + + local response = object:advanceUntil(function(char) + return char == "a" + end) + + check.equal(response, "exa") + check.equal(object.cursorPosition, 3) + + object:advance(2) + + check.equal(object.cursorPosition, 5) + end) + end) + + test.case("is able to peek an stream object", function() + local object = stream.new("example") + + local char = object:peek() + + check.equal(char, "e") + check.equal(object.cursorPosition, 0) + end) +end