diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 23875c9c8..41bf7abcd 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,5 +1,8 @@ github: - Lulalaby patreon: aiko_it_systems -#custom: -# - https://paypal.me/aitsys +community_bridge: ba6d3dfc-e110-483d-803b-572da206c4ff +custom: + - https://paypal.me/aitsys + - https://paypal.me/Geferon +# - mortAlice diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 000000000..56e3a7d18 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,54 @@ +name: "DisCatSharp Docs" + +on: + push: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + with: + path: DisCatSharp + - uses: actions/checkout@v2 + with: + repository: Aiko-IT-Systems/DisCatSharp.Docs + path: DisCatSharp.Docs + token: ${{ secrets.DOCS_TOKEN }} + + - name: Get SSH + uses: webfactory/ssh-agent@v0.5.3 + with: + ssh-private-key: ${{ secrets.AITSYS_SSH }} + + - name: Build Docs + working-directory: ./DisCatSharp + shell: pwsh + run: | + ./rebuild-docs.ps1 -DocsPath "./DisCatSharp.Docs" -Output ".." -PackageName "dcs-docs" + + - name: Purge old docs + working-directory: ./DisCatSharp.Docs + run: | + shopt -s extglob + rm -rf !(.git|.gitignore) + + - name: Extract new docs + run: | + tar -xf dcs-docs.tar.xz -C ./DisCatSharp.Docs + + - name: Commit and push changes + uses: EndBug/add-and-commit@master + with: + cwd: ./DisCatSharp.Docs + default_author: github_actions + author_name: DisCatSharp + author_email: discatsharp@aitsys.dev + message: 'Docs update for commit ${{ github.repository }} (${{ github.sha }})' + pull: 'NO-PULL' + + - name: Publish to Prod + run: | + ssh -o StrictHostKeyChecking=no -T root@80.153.182.68 -f 'cd /var/www/dcs/docs && git pull -f' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 48094d5fc..8714df07a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,116 +1,153 @@ # Contributing to DisCatSharp We're really happy to accept contributions. However we also ask that you follow several rules when doing so. # Proper base When opening a PR, please make sure your branch targets the latest release branch, in this case it would be `main`. Also make sure your branch is even with the target branch, to avoid unnecessary surprises. # Versioning We follow [SemVer](https://semver.org/) versioning when it comes to pushing stable releases. Ideally, this means you should only be creating PRs for `patch` and `minor` changes. If you wish to introduce a `major` (breaking) change, please discuss it beforehand so we can determine how to integrate it into our next major version. If this involves removing a public facing property/method, mark it with the `Obsolete` attribute instead on the latest release branch. # Proper titles When opening issues, make sure the title reflects the purpose of the issue or the pull request. Prefer past tense, and be brief. Further description belongs inside the issue or PR. # Descriptive changes We require the commits describe the change made. It can be a short description. If you fixed or resolved an open issue, please reference it by using the # notation. Examples of good commit messages: * `Fixed a potential memory leak with cache entities. Fixes #142.` * `Implemented new command extension. Resolves #169.` * `Changed message cache behaviour. It's now global instead of per-channel.` * `Fixed a potential NRE.` * ``` Changed message cache behaviour: - Messages are now stored globally. - Cache now deletes messages when they are deleted from discord. - Cache itself is now a ring buffer. ``` # Code style We use [Microsoft C# Coding Conventions](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/inside-a-program/coding-conventions) throughout the repository, with several exceptions: * Preference of `this`. While this one is not required, it's ill-advised to remove the existing instances thereof. * When working with async code, and your method consists of a single `await` statement not in any `if`, `while`, etc. blocks, pass the task through instead of awaiting it. For example: ```cs public Task DoSomethingAsync() => this.DoAnotherThingAsync(); public Task DoAnotherThingAsync() { Console.WriteLine("42"); return this.DoYetAnotherThingAsync(42); } public async Task DoYetAnotherThingAsync(int num) { if (num == 42) await SuperAwesomeMethodAsync(); } ``` In addition to these, we also have several preferences: * Use initializer syntax when possible: ```cs var a = new Class { StringNumber = "fourty-two", Number = 42 }; var b = new Dictionary() { ["fourty-two"] = 42, ["sixty-nine"] = 69 }; var c = new List() { 42, 69 }; var d = new[] { 42, 69 }; ``` * Inline `out` declarations when possible: `SomeOutMethod(42, out var stringified);` * Members in classes should be ordered as follows (with few exceptions): * Public `const` fields. * Non-public `const` fields. * Public static properties. * Public static fields. * Non-public static properties. * Non-public static fields. * Public properties. * Public fields. * Private properties. * Private fields. * Static constructor. * Public constructors. * Non-public constructors. * Public methods (with the exception of methods overriden from `System.Object`). * Non-public methods. * Methods overriden from `System.Object`. * Public static methods. * Non-public static methods. * Operator overloads. * Public events. * Non-public events. # Code changes One of our requirements is that all code change commits must build successfully. This is verified by our CI. When you open a pull request, AppVeyor will start a build. You can view its summary by visiting it from the checks section on the PR overview page. PRs that do not build will not be accepted. Furthermore we require that methods you implement on Discord entities have a reflection in the Discord API. In the event your code change is a style change, XML doc change, or otherwise does not change how the code works, tag the commit with `[ci skip]`. # Non-code changes In the event you change something outside of code (i.e. a meta-change or documentation), you must tag your commit with `[ci skip]`. + +# Developer Certificate of Origin (DCO) +``` +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. +``` diff --git a/DisCatSharp.Docs/articles/beyond_basics/components/select_menus.md b/DisCatSharp.Docs/articles/beyond_basics/components/select_menus.md index 05ad111a3..db5e619d3 100644 --- a/DisCatSharp.Docs/articles/beyond_basics/components/select_menus.md +++ b/DisCatSharp.Docs/articles/beyond_basics/components/select_menus.md @@ -1,139 +1,139 @@ --- uid: advanced_topics_select_menus title: Select Menus --- # Introduction The select menus, like the [buttons](xref:advanced_topics_buttons), are message components. You will want to familarize yourself with the [message builder](xref:beyond_basics_messagebuilder) as it and similar builder objects will be used throughout this article. A row can only have one select menu. An row containing a select menu cannot also contain buttons. Since a message can have up to 5 rows, you can add up to 5 select menus to a message. # Select Menus > [!WARNING] > Component Ids and option values should be unique, as this is what's sent back when a user selects one (or more) option. Select menus consist of five parts: - Id - Placeholder - Options - MinOptions - MaxOptions - Disabled The id of the select menu is a settable string, and is specified by the developer. Discord sends this id back in the [interaction object](https://discord.dev/interactions/slash-commands#interaction). **Placeholder** is a settable string that appears in the select menu when nothing is selected. **Options** is an array of options for the user to select. Their maximum number in one select menu is 25. You can let users choose 1 or more options using **MinOptions** and **MaxOptions**. Options consist of five parts: - Label - Value - Description - IsDefault - Emoji Menu creation, for easier understanding, can be divided into two stages: ```cs // First, create an array of options. var options = new DiscordSelectComponentOption[] { new DiscordSelectComponentOption("First option", "first_option", "This is the first option, you can add your description of it here.", false, new DiscordComponentEmoji("😀")), new DiscordSelectComponentOption("Second option", "second_option", "This is the second option, you can add your description of it here.", false, new DiscordComponentEmoji("😎")) }; // Now let's create a select menu with the options created above. var selectMenu = new DiscordSelectComponent("my_select_menu", "Please select one of the options", options); ``` This will create a select menu with two options and the text "Please select one of the options". When a user select **one** option, `"my_select_menu"` will be sent back as the `Id` property on the event. -This is expanded on in the [how to respond to select menus](#responding-to-options-selected). +This is expanded on in the [how to respond to select menus](#responding-to-select-menus). You can increase the maximum/minimum number of selections in the select menu constructor. You can also block the select menu, or options. Description and emoji of options are optional. The label, value and description can be up to 100 characters in length. The emoji of a option is a [partial emoji object](https://discord.dev/interactions/message-components#component-object), which means that **any valid emoji is usable**, even if your bot does not have access to it's origin server. # Adding Select Menu Adding a selec menu is no different than adding a button. We have already created the select menu above, now we will just create a new message builder add the select menu to it. ```cs var builder = new DiscordMessageBuilder() .WithContent("This message has select menu! Pretty neat innit?") .AddComponents(selectMenu); ``` Now you have a message with a select menu. Congratulations! It's important to note that `.AddComponents()` will create a new row with each call, so **add everything you want on one row in one call!** Lets also add a second row with select menu with the ability to choose any number of options. ```cs var secondOptions = new DiscordSelectComponentOption[] { new DiscordSelectComponentOption("First option", "first_option", "This is the first option, you can add your description of it here.", false, new DiscordComponentEmoji("😀")), new DiscordSelectComponentOption("Second option", "second_option", "This is the second option, you can add your description of it here.", false, new DiscordComponentEmoji("😎")) new DiscordSelectComponentOption("Third option", "third_option", "This is the third option, you can add your description of it here.", false, new DiscordComponentEmoji("😘")) }; var secondSelectMenu = new DiscordSelectComponent("my_second_select_menu", "Please select up to 3 options", secondOptions, 1, 3); builder.AddComponents(secondSelectMenu); ``` And you're done! The select menu will now be sent when the user closes the select menu with 1 to 3 options selected. # Responding to select menus When any select menu is pressed, it will fire the [ComponentInteractionCreated](xref:DisCatSharp.DiscordClient#DisCatSharp_DiscordClient_ComponentInteractionCreated) event. In the event args, `Id` will be the id of the select menu you specified. There's also an `Interaction` property, which contains the interaction the event created. It's important to respond to an interaction within 3 seconds, or it will time out. Responding after this period will throw a `NotFoundException`. With select menus, there are two new response types: `DefferedMessageUpdate` and `UpdateMessage`. Using `DeferredMessageUpdate` lets you create followup messages via the [followup message builder](xref:DisCatSharp.Entities.DiscordFollowupMessageBuilder). You have 15 minutes from that point to make followup messages. Responding to that interaction looks like this: ```cs client.ComponentInteractionCreated += async (s, e) => { await e.Interaction.CreateResponseAsync(InteractionResponseType.DefferedMessageUpdate); // Do things.. // } ``` If you would like to update the message when an select menu option selected, however, you'd use `UpdateMessage` instead, and pass a `DiscordInteractionResponseBuilder` with the new content you'd like. ```cs client.ComponentInteractionCreated += async (s, e) => { await e.Interaction.CreateResponseAsync(InteractionResponseType.UpdateMessage, new DiscordInteractionResponseBuilder().WithContent("No more select menu for you >:)")); } ``` This will update the message, and without the infamous (edited) next to it. Nice. # Interactivity Along with the typical `WaitForMessageAsync` and `WaitForReactionAsync` methods provided by interactivity, there are also select menus implementations as well. More information about how interactivity works can be found in [the interactivity article](xref:interactivity) Since select menus create interactions, there are also two additional properties in the configuration: - RepsonseBehavior - ResponseMessage ResponseBehavior is what interactivity will do when handling something that isn't a valid select menu, in the context of waiting for a specific select menu. It defaults to `Ignore`, which will cause the interaction fail. Alternatively, setting it to `Ack` will acknowledge the select menu, and continue waiting. Respond will reply with an ephemeral message with the aforementioned response message. ResponseBehavior only applies to the overload accepting a string id of the select menu to wait for. diff --git a/DisCatSharp.Docs/articles/important_changes/ac.md b/DisCatSharp.Docs/articles/important_changes/9_8_2.md similarity index 98% rename from DisCatSharp.Docs/articles/important_changes/ac.md rename to DisCatSharp.Docs/articles/important_changes/9_8_2.md index 8fe7f91e5..7a7b13c38 100644 --- a/DisCatSharp.Docs/articles/important_changes/ac.md +++ b/DisCatSharp.Docs/articles/important_changes/9_8_2.md @@ -1,135 +1,135 @@ --- -uid: important_changes_ac -title: Application Commands +uid: important_changes_9_8_2 +title: Version 9.8.2 --- # What changed? Discord [changed](https://discord.com/developers/docs/interactions/slash-commands) **SlashCommands** into **Application Commands** in favour of the new context menu stuff. You can read about it [here](https://discord.com/developers/docs/interactions/application-commands). ## Upgrade from **DisCatSharp.SlashCommands** to **DisCatSharp.ApplicationCommands** In **DisCatSharp.SlashCommands** you used Application Commands like this: ```cs // Usage directives using DisCatSharp.SlashCommands; using DisCatSharp.SlashCommands.EventArgs; // Extension public static SlashCommandsExtension Slash; // Injecting SlashCommands Client.UseSlashCommands(); Slash = Client.GetSlashCommands(); // Registration of commands and events Slash.SlashCommandErrored += Slash_SlashCommandErrored; Slash.SlashCommandExecuted += Slash_SlashCommandExecuted; Slash.RegisterCommands(); // Events public static Task Slash_SlashCommandExecuted(SlashCommandsExtension sender, SlashCommandExecutedEventArgs e) { Console.WriteLine($"Slash/Info: {e.Context.CommandName}"); return Task.CompletedTask; } public static Task Slash_SlashCommandErrored(SlashCommandsExtension sender, SlashCommandErrorEventArgs e) { Console.WriteLine($"Slash/Error: {e.Exception.Message} | CN: {e.Context.CommandName} | IID: {e.Context.InteractionId}"); return Task.CompletedTask; } // Commands using DisCatSharp; using DisCatSharp.Entities; using DisCatSharp.SlashCommands; using DisCatSharp.SlashCommands.Attributes; namespace TestBot.SlashCommands { internal class Main : SlashCommandModule { [SlashCommand("test", "A slash command made to test the DisCatSharp library!")] public static async Task TestCommand(InteractionContext ctx) { await ctx.CreateResponseAsync(InteractionResponseType.DeferredChannelMessageWithSource); await Task.Delay(5000); await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent("5 second delay complete!")); } } } ``` In **DisCatSharp.ApplicationCommands** you use them like this: ```cs // Usage directives using DisCatSharp.ApplicationCommands; using DisCatSharp.ApplicationCommands.EventArgs; // Extension public static ApplicationCommandsExtension ApplicationCommands; // Injecting SlashCommands Client.UseApplicationCommands(); ApplicationCommands = Client.GetApplicationCommands(); // Registration of commands and events ApplicationCommands.SlashCommandExecuted += Ac_SlashCommandExecuted; ApplicationCommands.SlashCommandErrored += Ac_SlashCommandErrored; /* New stuff - context menu */ ApplicationCommands.ContextMenuExecuted += Ac_ContextMenuExecuted; ApplicationCommands.ContextMenuErrored += Ac_ContextMenuErrored; ApplicationCommands.RegisterCommands(); // Events public static Task Ac_SlashCommandExecuted(ApplicationCommandsExtension sender, SlashCommandExecutedEventArgs e) { Console.WriteLine($"Slash/Info: {e.Context.CommandName}"); return Task.CompletedTask; } public static Task Ac_SlashCommandErrored(ApplicationCommandsExtension sender, SlashCommandErrorEventArgs e) { Console.WriteLine($"Slash/Error: {e.Exception.Message} | CN: {e.Context.CommandName} | IID: {e.Context.InteractionId}"); return Task.CompletedTask; } public static Task Ac_ContextMenuExecuted(ApplicationCommandsExtension sender, ContextMenuExecutedEventArgs e) { Console.WriteLine($"Slash/Info: {e.Context.CommandName}"); return Task.CompletedTask; } public static Task Ac_ContextMenuErrored(ApplicationCommandsExtension sender, ContextMenuErrorEventArgs e) { Console.WriteLine($"Slash/Error: {e.Exception.Message} | CN: {e.Context.CommandName} | IID: {e.Context.InteractionId}"); return Task.CompletedTask; } // Commands using DisCatSharp; using DisCatSharp.Entities; using DisCatSharp.Enums; using DisCatSharp.ApplicationCommands; using DisCatSharp.ApplicationCommands.Attributes; namespace TestBot.ApplicationCommands { internal class Main : ApplicationCommandsModule { [SlashCommand("test", "A slash command made to test the DisCatSharp library!")] public static async Task TestCommand(InteractionContext ctx) { await ctx.CreateResponseAsync(InteractionResponseType.DeferredChannelMessageWithSource); await Task.Delay(5000); await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent("5 second delay complete!")); } } } ``` diff --git a/DisCatSharp.Docs/articles/important_changes/9_8_3.md b/DisCatSharp.Docs/articles/important_changes/9_8_3.md new file mode 100644 index 000000000..bfbc555cc --- /dev/null +++ b/DisCatSharp.Docs/articles/important_changes/9_8_3.md @@ -0,0 +1,74 @@ +--- +uid: important_changes_9_8_3 +title: Version 9.8.3 +--- + +# What changed? + +We [changed](https://canary.discord.com/channels/858089281214087179/858099438580006913/890973133148926004) the option to restrict channel types in Slash Commands to an extra Attribute named `ChannelTypes` in favour of the application command autocompletion. +You can read about it [here](https://github.com/discord/discord-api-docs/pull/3849). + +## Upgrade from **9.8.2** to **9.8.3** + +In **DisCatSharp.ApplicationComands** you restricted channel types like this: +```cs +[SlashCommand("openstage", "Opens a stage")] +public static async Task OpenStageAsync(InteractionContext ctx, + [Option("stage", "Stage Channel", ChannelType.Stage)] DiscordChannel stage, + [Option("topic", "Topic of stage")] string topic, + [Option("notify", "Whether to notify people")] bool notify = false, + [Option("make_public", "Whether the stage channel will be public")] bool make_public = false) +{ + try + { + await ctx.CreateResponseAsync(InteractionResponseType.DeferredChannelMessageWithSource, new DiscordInteractionResponseBuilder().WithContent("Opening stage").AsEphemeral(true)); + await stage.OpenStageAsync(topic, notify, make_public ? StagePrivacyLevel.PUBLIC : StagePrivacyLevel.GUILD_ONLY); + await ctx.EditResponseAsync(new() + { + Content = "Stage channel has been successfully opened." + }); + } + catch (Exception ex) + { + await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent(ex.Message + " " + ex.StackTrace)); + } +} +``` + +In **DisCatSharp.ApplicationComands** you restrict channel types now like this: +```cs +[SlashCommand("openstage", "Opens a stage")] +public static async Task OpenStageAsync(InteractionContext ctx, + [Option("stage", "Stage Channel"), ChannelTypes(ChannelType.Stage)] DiscordChannel stage, + [Option("topic", "Topic of stage")] string topic, + [Option("notify", "Whether to notify people")] bool notify = false, + [Option("make_public", "Whether the stage channel will be public")] bool make_public = false) +{ + try + { + await ctx.CreateResponseAsync(InteractionResponseType.DeferredChannelMessageWithSource, new DiscordInteractionResponseBuilder().WithContent("Opening stage").AsEphemeral(true)); + await stage.OpenStageAsync(topic, notify, make_public ? StagePrivacyLevel.PUBLIC : StagePrivacyLevel.GUILD_ONLY); + await ctx.EditResponseAsync(new() + { + Content = "Stage channel has been successfully opened." + }); + } + catch (Exception ex) + { + await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent(ex.Message + " " + ex.StackTrace)); + } +} +``` + +## New function: Application Command Autocompletion: + +Examples: +> Autocomplete Option: https://github.com/Aiko-IT-Systems/DisCatSharp.Support/blob/main/Commands/Tasks/ConduitTasks.cs#L169 + +> Autocomplete Provider: https://github.com/Aiko-IT-Systems/DisCatSharp.Support/blob/main/Providers/ConduitProvider.cs#L106 + +Docs: +https://docs.dcs.aitsys.dev/api/DisCatSharp.ApplicationCommands.Attributes.AutocompleteAttribute.html +https://docs.dcs.aitsys.dev/api/DisCatSharp.ApplicationCommands.Attributes.IAutocompleteProvider.html +https://docs.dcs.aitsys.dev/api/DisCatSharp.Entities.DiscordApplicationCommandAutocompleteChoice.html +https://docs.dcs.aitsys.dev/api/DisCatSharp.ApplicationCommands.OptionAttribute.html#DisCatSharp_ApplicationCommands_OptionAttribute__ctor_System_String_System_String_System_Boolean_ diff --git a/DisCatSharp.Docs/articles/toc.yml b/DisCatSharp.Docs/articles/toc.yml index 867df8aa8..d59c77dde 100644 --- a/DisCatSharp.Docs/articles/toc.yml +++ b/DisCatSharp.Docs/articles/toc.yml @@ -1,89 +1,91 @@ - name: Preamble href: preamble.md +- name: Important Changes + items: + - name: Version 9.8.3 + href: important_changes/9_8_3.md + - name: Version 9.8.2 + href: important_changes/9_8_2.md - name: The Basics items: - name: Creating a Bot Account href: basics/bot_account.md - name: Writing Your First Bot href: basics/first_bot.md - name: Beyond Basics items: - name: Events href: beyond_basics/events.md - name: Logging href: beyond_basics/logging/default.md items: - name: The Default Logger href: beyond_basics/logging/default.md - name: Third Party Loggers href: beyond_basics/logging/third_party.md - name: Intents href: beyond_basics/intents.md - name: Sharding href: beyond_basics/sharding.md - name: Message Builder href: beyond_basics/messagebuilder.md - name: Components items: - name: Buttons href: beyond_basics/components/buttons.md - name: Select Menu href: beyond_basics/components/select_menus.md - name: Workarounds href: beyond_basics/workarounds.md - name: Application Commands items: - name: Introduction href: application_commands/intro.md - name: Options href: application_commands/options.md - name: Events href: application_commands/events.md - name: Commands items: - name: Introduction href: commands/intro.md - name: Command Attributes href: commands/command_attributes.md - name: Dependency Injection href: commands/dependency_injection.md - name: Customization items: - name: Help Formatter href: commands/help_formatter.md - name: Argument Converters href: commands/argument_converters.md - name: Command Handler href: commands/command_handler.md - name: Audio items: - name: Lavalink items: - name: Setup href: audio/lavalink/setup.md - name: Configuration href: audio/lavalink/configuration.md - name: Music Commands href: audio/lavalink/music_commands.md - name: VoiceNext items: - name: Prerequisites href: audio/voicenext/prerequisites.md - name: Transmitting href: audio/voicenext/transmit.md - name: Receiving href: audio/voicenext/receive.md - name: Interactivity href: interactivity.md -- name: Important Changes - items: - - name: Application Commands - href: important_changes/ac.md - name: Hosting href: hosting.md - name: Miscellaneous items: - name: Nightly Builds href: misc/nightly_builds.md - name: Reporting Issues href: misc/reporting_issues.md diff --git a/DisCatSharp.Docs/images/basics_first_bot_06.png b/DisCatSharp.Docs/images/basics_first_bot_06.png index 3495ce83d..49403b7b3 100644 Binary files a/DisCatSharp.Docs/images/basics_first_bot_06.png and b/DisCatSharp.Docs/images/basics_first_bot_06.png differ diff --git a/DisCatSharp.Docs/images/basics_first_bot_07.png b/DisCatSharp.Docs/images/basics_first_bot_07.png index aff0a1005..bbcdc53f1 100644 Binary files a/DisCatSharp.Docs/images/basics_first_bot_07.png and b/DisCatSharp.Docs/images/basics_first_bot_07.png differ diff --git a/DisCatSharp.Docs/index.md b/DisCatSharp.Docs/index.md index 3cf09879c..0b901c721 100644 --- a/DisCatSharp.Docs/index.md +++ b/DisCatSharp.Docs/index.md @@ -1,19 +1,19 @@ --- uid: index title: DisCatSharp 9.X Documentation ---

DisCatSharp 9.X Documentation

![DisCatSharp Logo](/logobig.png "DisCatSharp Documentation")

## DisCatSharp 9.X Documentation [DisCatSharp](https://github.com/Aiko-IT-Systems/DisCatSharp) (DCS) is an unofficial .NET wrapper for the [Discord API](https://discordapp.com/developers/docs/intro "Discord API") which was originally a fork of [DSharpPlus](https://github.com/DSharpPlus/DSharpPlus).
The library has since been rewritten to fit quality and API standards as well as target wider range of .NET implementations. Furthermore this lib will be the most up-to-date. ## Getting Started New users will want to take a look through the [articles](xref:preamble) for quick start guides, tutorials, and examples of use.
Once you've gotten through the articles, head on over to the [API Documentation](/api/index.html) for all classes and methods provided by this library. ## Source and Contributors DisCatSharp is licensed under MIT License, as detailed in the [license](https://github.com/Aiko-IT-Systems/DisCatSharp/blob/master/LICENSE.md) found in the repository.
The repository containing the source code for this library can be found [here](https://github.com/Aiko-IT-Systems/DisCatSharp). Contributions are welcomed.
-[Join our guild](https://discord.gg/discatsharp). +You can find our official guild [here](https://discord.gg/discatsharp). diff --git a/DisCatSharp/Entities/Guild/DiscordGuild.cs b/DisCatSharp/Entities/Guild/DiscordGuild.cs index e19e65be1..8c771fe74 100644 --- a/DisCatSharp/Entities/Guild/DiscordGuild.cs +++ b/DisCatSharp/Entities/Guild/DiscordGuild.cs @@ -1,3310 +1,3324 @@ // This file is part of the DisCatSharp project. // // Copyright (c) 2021 AITSYS // // 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. #pragma warning disable CS0618 using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Threading.Tasks; using DisCatSharp.Enums.Discord; using DisCatSharp.EventArgs; using DisCatSharp.Exceptions; using DisCatSharp.Net; using DisCatSharp.Net.Abstractions; using DisCatSharp.Net.Models; using DisCatSharp.Net.Serialization; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace DisCatSharp.Entities { /// /// Represents a Discord guild. /// public class DiscordGuild : SnowflakeObject, IEquatable { /// /// Gets the guild's name. /// [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] public string Name { get; internal set; } /// /// Gets the guild icon's hash. /// [JsonProperty("icon", NullValueHandling = NullValueHandling.Ignore)] public string IconHash { get; internal set; } /// /// Gets the guild icon's url. /// [JsonIgnore] public string IconUrl => !string.IsNullOrWhiteSpace(this.IconHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.ICONS}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.IconHash}.{(this.IconHash.StartsWith("a_") ? "gif" : "png")}?size=1024" : null; /// /// Gets the guild splash's hash. /// [JsonProperty("splash", NullValueHandling = NullValueHandling.Ignore)] public string SplashHash { get; internal set; } /// /// Gets the guild splash's url. /// [JsonIgnore] public string SplashUrl => !string.IsNullOrWhiteSpace(this.SplashHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.SPLASHES}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.SplashHash}.png?size=1024" : null; /// /// Gets the guild discovery splash's hash. /// [JsonProperty("discovery_splash", NullValueHandling = NullValueHandling.Ignore)] public string DiscoverySplashHash { get; internal set; } /// /// Gets the guild discovery splash's url. /// [JsonIgnore] public string DiscoverySplashUrl => !string.IsNullOrWhiteSpace(this.DiscoverySplashHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.GUILD_DISCOVERY_SPLASHES}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.DiscoverySplashHash}.png?size=1024" : null; /// /// Gets the preferred locale of this guild. /// This is used for server discovery and notices from Discord. Defaults to en-US. /// [JsonProperty("preferred_locale", NullValueHandling = NullValueHandling.Ignore)] public string PreferredLocale { get; internal set; } /// /// Gets the ID of the guild's owner. /// [JsonProperty("owner_id", NullValueHandling = NullValueHandling.Ignore)] public ulong OwnerId { get; internal set; } /// /// Gets permissions for the user in the guild (does not include channel overrides) /// [JsonProperty("permissions", NullValueHandling = NullValueHandling.Ignore)] public Permissions? Permissions { get; set; } /// /// Gets the guild's owner. /// [JsonIgnore] public DiscordMember Owner => this.Members.TryGetValue(this.OwnerId, out var owner) ? owner : this.Discord.ApiClient.GetGuildMemberAsync(this.Id, this.OwnerId).ConfigureAwait(false).GetAwaiter().GetResult(); /// /// Gets the guild's voice region ID. /// [JsonProperty("region", NullValueHandling = NullValueHandling.Ignore)] internal string VoiceRegionId { get; set; } /// /// Gets the guild's voice region. /// [JsonIgnore] public DiscordVoiceRegion VoiceRegion => this.Discord.VoiceRegions[this.VoiceRegionId]; /// /// Gets the guild's AFK voice channel ID. /// [JsonProperty("afk_channel_id", NullValueHandling = NullValueHandling.Ignore)] internal ulong AfkChannelId { get; set; } = 0; /// /// Gets the guild's AFK voice channel. /// [JsonIgnore] public DiscordChannel AfkChannel => this.GetChannel(this.AfkChannelId); /// /// Gets the guild's AFK timeout. /// [JsonProperty("afk_timeout", NullValueHandling = NullValueHandling.Ignore)] public int AfkTimeout { get; internal set; } /// /// Gets the guild's verification level. /// [JsonProperty("verification_level", NullValueHandling = NullValueHandling.Ignore)] public VerificationLevel VerificationLevel { get; internal set; } /// /// Gets the guild's default notification settings. /// [JsonProperty("default_message_notifications", NullValueHandling = NullValueHandling.Ignore)] public DefaultMessageNotifications DefaultMessageNotifications { get; internal set; } /// /// Gets the guild's explicit content filter settings. /// [JsonProperty("explicit_content_filter")] public ExplicitContentFilter ExplicitContentFilter { get; internal set; } /// /// Gets the guild's nsfw level. /// [JsonProperty("nsfw_level")] public NsfwLevel NsfwLevel { get; internal set; } /// /// Gets the system channel id. /// [JsonProperty("system_channel_id", NullValueHandling = NullValueHandling.Include)] internal ulong? SystemChannelId { get; set; } /// /// Gets the channel where system messages (such as boost and welcome messages) are sent. /// [JsonIgnore] public DiscordChannel SystemChannel => this.SystemChannelId.HasValue ? this.GetChannel(this.SystemChannelId.Value) : null; /// /// Gets the settings for this guild's system channel. /// [JsonProperty("system_channel_flags")] public SystemChannelFlags SystemChannelFlags { get; internal set; } /// /// Gets whether this guild's widget is enabled. /// [JsonProperty("widget_enabled", NullValueHandling = NullValueHandling.Ignore)] public bool? WidgetEnabled { get; internal set; } /// /// Gets the widget channel id. /// [JsonProperty("widget_channel_id", NullValueHandling = NullValueHandling.Ignore)] internal ulong? WidgetChannelId { get; set; } /// /// Gets the widget channel for this guild. /// [JsonIgnore] public DiscordChannel WidgetChannel => this.WidgetChannelId.HasValue ? this.GetChannel(this.WidgetChannelId.Value) : null; /// /// Gets the rules channel id. /// [JsonProperty("rules_channel_id")] internal ulong? RulesChannelId { get; set; } /// /// Gets the rules channel for this guild. /// This is only available if the guild is considered "discoverable". /// [JsonIgnore] public DiscordChannel RulesChannel => this.RulesChannelId.HasValue ? this.GetChannel(this.RulesChannelId.Value) : null; /// /// Gets the public updates channel id. /// [JsonProperty("public_updates_channel_id")] internal ulong? PublicUpdatesChannelId { get; set; } /// /// Gets the public updates channel (where admins and moderators receive messages from Discord) for this guild. /// This is only available if the guild is considered "discoverable". /// [JsonIgnore] public DiscordChannel PublicUpdatesChannel => this.PublicUpdatesChannelId.HasValue ? this.GetChannel(this.PublicUpdatesChannelId.Value) : null; /// /// Gets the application id of this guild if it is bot created. /// [JsonProperty("application_id")] public ulong? ApplicationId { get; internal set; } /// /// Gets a collection of this guild's roles. /// [JsonIgnore] public IReadOnlyDictionary Roles => new ReadOnlyConcurrentDictionary(this._roles); [JsonProperty("roles", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))] internal ConcurrentDictionary _roles; /// /// Gets a collection of this guild's stickers. /// [JsonIgnore] public IReadOnlyDictionary Stickers => new ReadOnlyConcurrentDictionary(this._stickers); [JsonProperty("stickers", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))] internal ConcurrentDictionary _stickers; /// /// Gets a collection of this guild's emojis. /// [JsonIgnore] public IReadOnlyDictionary Emojis => new ReadOnlyConcurrentDictionary(this._emojis); [JsonProperty("emojis", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))] internal ConcurrentDictionary _emojis; /// /// Gets a collection of this guild's features. /// [JsonProperty("features", NullValueHandling = NullValueHandling.Ignore)] public IReadOnlyList RawFeatures { get; internal set; } /// /// Gets the guild's features. /// [JsonIgnore] public GuildFeatures Features => new(this); /// /// Gets the required multi-factor authentication level for this guild. /// [JsonProperty("mfa_level", NullValueHandling = NullValueHandling.Ignore)] public MfaLevel MfaLevel { get; internal set; } /// /// Gets this guild's join date. /// [JsonProperty("joined_at", NullValueHandling = NullValueHandling.Ignore)] public DateTimeOffset JoinedAt { get; internal set; } /// /// Gets whether this guild is considered to be a large guild. /// [JsonProperty("large", NullValueHandling = NullValueHandling.Ignore)] public bool IsLarge { get; internal set; } /// /// Gets whether this guild is unavailable. /// [JsonProperty("unavailable", NullValueHandling = NullValueHandling.Ignore)] public bool IsUnavailable { get; internal set; } /// /// Gets the total number of members in this guild. /// [JsonProperty("member_count", NullValueHandling = NullValueHandling.Ignore)] public int MemberCount { get; internal set; } /// /// Gets the maximum amount of members allowed for this guild. /// [JsonProperty("max_members")] public int? MaxMembers { get; internal set; } /// /// Gets the maximum amount of presences allowed for this guild. /// [JsonProperty("max_presences")] public int? MaxPresences { get; internal set; } #pragma warning disable CS1734 /// /// Gets the approximate number of members in this guild, when using and having set to true. /// [JsonProperty("approximate_member_count", NullValueHandling = NullValueHandling.Ignore)] public int? ApproximateMemberCount { get; internal set; } /// /// Gets the approximate number of presences in this guild, when using and having set to true. /// [JsonProperty("approximate_presence_count", NullValueHandling = NullValueHandling.Ignore)] public int? ApproximatePresenceCount { get; internal set; } #pragma warning restore CS1734 /// /// Gets the maximum amount of users allowed per video channel. /// [JsonProperty("max_video_channel_users", NullValueHandling = NullValueHandling.Ignore)] public int? MaxVideoChannelUsers { get; internal set; } /// /// Gets a dictionary of all the voice states for this guilds. The key for this dictionary is the ID of the user /// the voice state corresponds to. /// [JsonIgnore] public IReadOnlyDictionary VoiceStates => new ReadOnlyConcurrentDictionary(this._voiceStates); [JsonProperty("voice_states", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))] internal ConcurrentDictionary _voiceStates; /// /// Gets a dictionary of all the members that belong to this guild. The dictionary's key is the member ID. /// [JsonIgnore] // TODO overhead of => vs Lazy? it's a struct public IReadOnlyDictionary Members => new ReadOnlyConcurrentDictionary(this._members); [JsonProperty("members", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))] internal ConcurrentDictionary _members; /// /// Gets a dictionary of all the channels associated with this guild. The dictionary's key is the channel ID. /// [JsonIgnore] public IReadOnlyDictionary Channels => new ReadOnlyConcurrentDictionary(this._channels); [JsonProperty("channels", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))] internal ConcurrentDictionary _channels; internal ConcurrentDictionary _invites; /// /// Gets a dictionary of all the active threads associated with this guild the user has permission to view. The dictionary's key is the channel ID. /// [JsonIgnore] public IReadOnlyDictionary Threads { get; internal set; } [JsonProperty("threads", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))] internal ConcurrentDictionary _threads = new(); /// /// Gets a dictionary of all active stage instances. The dictionary's key is the stage ID. /// [JsonIgnore] public IReadOnlyDictionary StageInstances { get; internal set; } [JsonProperty("stage_instances", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))] internal ConcurrentDictionary _stageInstances = new(); /// /// Gets a dictionary of all sheduled events. /// [JsonIgnore] public IReadOnlyDictionary SheduledEvents { get; internal set; } [JsonProperty("events", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))] internal ConcurrentDictionary _sheduledEvents = new(); /// /// Gets the guild member for current user. /// [JsonIgnore] public DiscordMember CurrentMember => this._current_member_lazy.Value; [JsonIgnore] private readonly Lazy _current_member_lazy; /// /// Gets the @everyone role for this guild. /// [JsonIgnore] public DiscordRole EveryoneRole => this.GetRole(this.Id); [JsonIgnore] internal bool _isOwner; /// /// Gets whether the current user is the guild's owner. /// [JsonProperty("owner", NullValueHandling = NullValueHandling.Ignore)] public bool IsOwner { get => this._isOwner || this.OwnerId == this.Discord.CurrentUser.Id; internal set => this._isOwner = value; } /// /// Gets the vanity URL code for this guild, when applicable. /// [JsonProperty("vanity_url_code")] public string VanityUrlCode { get; internal set; } /// /// Gets the guild description, when applicable. /// [JsonProperty("description")] public string Description { get; internal set; } /// /// Gets this guild's banner hash, when applicable. /// [JsonProperty("banner")] public string BannerHash { get; internal set; } /// /// Gets this guild's banner in url form. /// [JsonIgnore] public string BannerUrl => !string.IsNullOrWhiteSpace(this.BannerHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Uri}{Endpoints.BANNERS}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.BannerHash}.png" : null; /// /// Whether this guild has the community feature enabled. /// [JsonIgnore] public bool IsCommunity => this.Features.HasCommunityEnabled; /// /// Whether this guild has enabled the welcome screen. /// [JsonIgnore] public bool HasWelcomeScreen => this.Features.HasWelcomeScreenEnabled; /// /// Whether this guild has enabled membership screening. /// [JsonIgnore] public bool HasMemberVerificationGate => this.Features.HasMembershipScreeningEnabled; /// /// Gets this guild's premium tier (Nitro boosting). /// [JsonProperty("premium_tier")] public PremiumTier PremiumTier { get; internal set; } /// /// Gets the amount of members that boosted this guild. /// [JsonProperty("premium_subscription_count", NullValueHandling = NullValueHandling.Ignore)] public int? PremiumSubscriptionCount { get; internal set; } /// /// Gets whether this guild is designated as NSFW. /// [JsonProperty("nsfw", NullValueHandling = NullValueHandling.Ignore)] public bool IsNSFW { get; internal set; } /// /// Gets a dictionary of all by position ordered channels associated with this guild. The dictionary's key is the channel ID. /// [JsonIgnore] public IReadOnlyDictionary OrderedChannels => new ReadOnlyDictionary(this.InternalSortChannels()); /// /// Sorts the channels. /// private Dictionary InternalSortChannels() { Dictionary keyValuePairs = new(); var ochannels = this.GetOrderedChannels(); foreach (var ochan in ochannels) { if (ochan.Key != 0) keyValuePairs.Add(ochan.Key, this.GetChannel(ochan.Key)); foreach (var chan in ochan.Value) keyValuePairs.Add(chan.Id, chan); } return keyValuePairs; } /// /// Gets an ordered list out of the channel cache. /// Returns a Dictionary where the key is an ulong and can be mapped to s. /// Ignore the 0 key here, because that indicates that this is the "has no category" list. /// Each value contains a ordered list of text/news and voice/stage channels as . /// /// A ordered list of categories with its channels public Dictionary> GetOrderedChannels() { IReadOnlyList raw_channels = this._channels.Values.ToList(); Dictionary> ordered_channels = new(); ordered_channels.Add(0, new List()); foreach (var channel in raw_channels.Where(c => c.Type == ChannelType.Category).OrderBy(c => c.Position)) { ordered_channels.Add(channel.Id, new List()); } foreach (var channel in raw_channels.Where(c => c.ParentId.HasValue && (c.Type == ChannelType.Text || c.Type == ChannelType.News)).OrderBy(c => c.Position)) { ordered_channels[channel.ParentId.Value].Add(channel); } foreach (var channel in raw_channels.Where(c => c.ParentId.HasValue && (c.Type == ChannelType.Voice || c.Type == ChannelType.Stage)).OrderBy(c => c.Position)) { ordered_channels[channel.ParentId.Value].Add(channel); } foreach (var channel in raw_channels.Where(c => !c.ParentId.HasValue && c.Type != ChannelType.Category && (c.Type == ChannelType.Text || c.Type == ChannelType.News)).OrderBy(c => c.Position)) { ordered_channels[0].Add(channel); } foreach (var channel in raw_channels.Where(c => !c.ParentId.HasValue && c.Type != ChannelType.Category && (c.Type == ChannelType.Voice || c.Type == ChannelType.Stage)).OrderBy(c => c.Position)) { ordered_channels[0].Add(channel); } return ordered_channels; } /// /// Gets an ordered list. /// Returns a Dictionary where the key is an ulong and can be mapped to s. /// Ignore the 0 key here, because that indicates that this is the "has no category" list. /// Each value contains a ordered list of text/news and voice/stage channels as . /// /// A ordered list of categories with its channels public async Task>> GetOrderedChannelsAsync() { var raw_channels = await this.Discord.ApiClient.GetGuildChannelsAsync(this.Id); Dictionary> ordered_channels = new(); ordered_channels.Add(0, new List()); foreach (var channel in raw_channels.Where(c => c.Type == ChannelType.Category).OrderBy(c => c.Position)) { ordered_channels.Add(channel.Id, new List()); } foreach (var channel in raw_channels.Where(c => c.ParentId.HasValue && (c.Type == ChannelType.Text || c.Type == ChannelType.News)).OrderBy(c => c.Position)) { ordered_channels[channel.ParentId.Value].Add(channel); } foreach (var channel in raw_channels.Where(c => c.ParentId.HasValue && (c.Type == ChannelType.Voice || c.Type == ChannelType.Stage)).OrderBy(c => c.Position)) { ordered_channels[channel.ParentId.Value].Add(channel); } foreach (var channel in raw_channels.Where(c => !c.ParentId.HasValue && c.Type != ChannelType.Category && (c.Type == ChannelType.Text || c.Type == ChannelType.News)).OrderBy(c => c.Position)) { ordered_channels[0].Add(channel); } foreach (var channel in raw_channels.Where(c => !c.ParentId.HasValue && c.Type != ChannelType.Category && (c.Type == ChannelType.Voice || c.Type == ChannelType.Stage)).OrderBy(c => c.Position)) { ordered_channels[0].Add(channel); } return ordered_channels; } /// /// Whether it is synced. /// [JsonIgnore] internal bool IsSynced { get; set; } /// /// Initializes a new instance of the class. /// internal DiscordGuild() { this._current_member_lazy = new Lazy(() => (this._members != null && this._members.TryGetValue(this.Discord.CurrentUser.Id, out var member)) ? member : null); this._invites = new ConcurrentDictionary(); this.Threads = new ReadOnlyConcurrentDictionary(this._threads); this.StageInstances = new ReadOnlyConcurrentDictionary(this._stageInstances); } #region Guild Methods /// /// Searches the current guild for members who's display name start with the specified name. /// /// The name to search for. /// The maximum amount of members to return. Max 1000. Defaults to 1. /// The members found, if any. public Task> SearchMembersAsync(string name, int? limit = 1) => this.Discord.ApiClient.SearchMembersAsync(this.Id, name, limit); /// /// Adds a new member to this guild /// /// User to add /// User's access token (OAuth2) /// new nickname /// new roles /// whether this user has to be muted /// whether this user has to be deafened /// /// Thrown when the client does not have the permission. /// Thrown when the or is not found. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task AddMemberAsync(DiscordUser user, string access_token, string nickname = null, IEnumerable roles = null, bool muted = false, bool deaf = false) => this.Discord.ApiClient.AddGuildMemberAsync(this.Id, user.Id, access_token, nickname, roles, muted, deaf); /// /// Deletes this guild. Requires the caller to be the owner of the guild. /// /// /// Thrown when the client is not the owner of the guild. /// Thrown when Discord is unable to process the request. public Task DeleteAsync() => this.Discord.ApiClient.DeleteGuildAsync(this.Id); /// /// Modifies this guild. /// /// Action to perform on this guild.. /// The modified guild object. /// Thrown when the client does not have the permission. /// Thrown when the guild does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task ModifyAsync(Action action) { var mdl = new GuildEditModel(); action(mdl); var afkChannelId = Optional.FromNoValue(); if (mdl.AfkChannel.HasValue && mdl.AfkChannel.Value.Type != ChannelType.Voice && mdl.AfkChannel.Value != null) throw new ArgumentException("AFK channel needs to be a voice channel."); else if (mdl.AfkChannel.HasValue && mdl.AfkChannel.Value != null) afkChannelId = mdl.AfkChannel.Value.Id; else if (mdl.AfkChannel.HasValue) afkChannelId = null; var rulesChannelId = Optional.FromNoValue(); if (mdl.RulesChannel.HasValue && mdl.RulesChannel.Value != null && mdl.RulesChannel.Value.Type != ChannelType.Text && mdl.RulesChannel.Value.Type != ChannelType.News ) throw new ArgumentException("Rules channel needs to be a text channel."); else if (mdl.RulesChannel.HasValue && mdl.RulesChannel.Value != null) rulesChannelId = mdl.RulesChannel.Value.Id; else if (mdl.RulesChannel.HasValue) rulesChannelId = null; var publicUpdatesChannelId = Optional.FromNoValue(); if (mdl.PublicUpdatesChannel.HasValue && mdl.PublicUpdatesChannel.Value != null && mdl.PublicUpdatesChannel.Value.Type != ChannelType.Text && mdl.PublicUpdatesChannel.Value.Type != ChannelType.News) throw new ArgumentException("Public updates channel needs to be a text channel."); else if (mdl.PublicUpdatesChannel.HasValue && mdl.PublicUpdatesChannel.Value != null) publicUpdatesChannelId = mdl.PublicUpdatesChannel.Value.Id; else if (mdl.PublicUpdatesChannel.HasValue) publicUpdatesChannelId = null; var systemChannelId = Optional.FromNoValue(); if (mdl.SystemChannel.HasValue && mdl.SystemChannel.Value != null && mdl.SystemChannel.Value.Type != ChannelType.Text && mdl.SystemChannel.Value.Type != ChannelType.News) throw new ArgumentException("Public updates channel needs to be a text channel."); else if (mdl.SystemChannel.HasValue && mdl.SystemChannel.Value != null) systemChannelId = mdl.SystemChannel.Value.Id; else if (mdl.SystemChannel.HasValue) systemChannelId = null; var iconb64 = Optional.FromNoValue(); if (mdl.Icon.HasValue && mdl.Icon.Value != null) using (var imgtool = new ImageTool(mdl.Icon.Value)) iconb64 = imgtool.GetBase64(); else if (mdl.Icon.HasValue) iconb64 = null; var splashb64 = Optional.FromNoValue(); if (mdl.Splash.HasValue && mdl.Splash.Value != null) using (var imgtool = new ImageTool(mdl.Splash.Value)) splashb64 = imgtool.GetBase64(); else if (mdl.Splash.HasValue) splashb64 = null; var bannerb64 = Optional.FromNoValue(); if (mdl.Banner.HasValue && mdl.Banner.Value != null) using (var imgtool = new ImageTool(mdl.Banner.Value)) bannerb64 = imgtool.GetBase64(); else if (mdl.Banner.HasValue) bannerb64 = null; var discoverySplash64 = Optional.FromNoValue(); if (mdl.DiscoverySplash.HasValue && mdl.DiscoverySplash.Value != null) using (var imgtool = new ImageTool(mdl.DiscoverySplash.Value)) discoverySplash64 = imgtool.GetBase64(); else if (mdl.DiscoverySplash.HasValue) discoverySplash64 = null; var description = Optional.FromNoValue(); if (mdl.Description.HasValue && mdl.Description.Value != null) description = mdl.Description; else if (mdl.Description.HasValue) description = null; return await this.Discord.ApiClient.ModifyGuildAsync(this.Id, mdl.Name, mdl.Region.IfPresent(e => e.Id), mdl.VerificationLevel, mdl.DefaultMessageNotifications, mdl.MfaLevel, mdl.ExplicitContentFilter, afkChannelId, mdl.AfkTimeout, iconb64, mdl.Owner.IfPresent(e => e.Id), splashb64, systemChannelId, mdl.SystemChannelFlags, publicUpdatesChannelId, rulesChannelId, description, bannerb64, discoverySplash64, mdl.PreferredLocale, mdl.AuditLogReason).ConfigureAwait(false); } /// /// Modifies the community settings async. /// This sets if not highest and . /// /// If true, enable . /// The rules channel. /// The public updates channel. /// The preferred locale. Defaults to en-US. /// The description. /// The default message notifications. Defaults to /// The auditlog reason. /// Thrown when the client does not have the permission. /// Thrown when the guild does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task ModifyCommunitySettingsAsync(bool enabled, DiscordChannel rulesChannel = null, DiscordChannel publicUpdatesChannel = null, string preferredLocale = "en-US", string description = null, DefaultMessageNotifications defaultMessageNotifications = DefaultMessageNotifications.MentionsOnly, string reason = null) { var verificationLevel = this.VerificationLevel; if(this.VerificationLevel != VerificationLevel.Highest) { verificationLevel = VerificationLevel.High; } var explicitContentFilter = ExplicitContentFilter.AllMembers; var rulesChannelId = Optional.FromNoValue(); if (rulesChannel != null && rulesChannel.Type != ChannelType.Text && rulesChannel.Type != ChannelType.News) throw new ArgumentException("Rules channel needs to be a text channel."); else if (rulesChannel != null) rulesChannelId = rulesChannel.Id; else if (rulesChannel == null) rulesChannelId = null; var publicUpdatesChannelId = Optional.FromNoValue(); if (publicUpdatesChannel != null && publicUpdatesChannel.Type != ChannelType.Text && publicUpdatesChannel.Type != ChannelType.News) throw new ArgumentException("Public updates channel needs to be a text channel."); else if (publicUpdatesChannel != null) publicUpdatesChannelId = publicUpdatesChannel.Id; else if (publicUpdatesChannel == null) publicUpdatesChannelId = null; List features = new(); var rfeatures = this.RawFeatures.ToList(); if (this.RawFeatures.Contains("COMMUNITY") && enabled) { features = rfeatures; } else if(!this.RawFeatures.Contains("COMMUNITY") && enabled) { rfeatures.Add("COMMUNITY"); features = rfeatures; } else if (this.RawFeatures.Contains("COMMUNITY") && !enabled) { rfeatures.Remove("COMMUNITY"); features = rfeatures; } else if(!this.RawFeatures.Contains("COMMUNITY") && !enabled) { features = rfeatures; } return await this.Discord.ApiClient.ModifyGuildCommunitySettingsAsync(this.Id, features, rulesChannelId, publicUpdatesChannelId, preferredLocale, description, defaultMessageNotifications, explicitContentFilter, verificationLevel, reason).ConfigureAwait(false); } /// /// Bans a specified member from this guild. /// /// Member to ban. /// How many days to remove messages from. /// Reason for audit logs. /// /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task BanMemberAsync(DiscordMember member, int delete_message_days = 0, string reason = null) => this.Discord.ApiClient.CreateGuildBanAsync(this.Id, member.Id, delete_message_days, reason); /// /// Bans a specified user by ID. This doesn't require the user to be in this guild. /// /// ID of the user to ban. /// How many days to remove messages from. /// Reason for audit logs. /// /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task BanMemberAsync(ulong user_id, int delete_message_days = 0, string reason = null) => this.Discord.ApiClient.CreateGuildBanAsync(this.Id, user_id, delete_message_days, reason); /// /// Unbans a user from this guild. /// /// User to unban. /// Reason for audit logs. /// /// Thrown when the client does not have the permission. /// Thrown when the user does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task UnbanMemberAsync(DiscordUser user, string reason = null) => this.Discord.ApiClient.RemoveGuildBanAsync(this.Id, user.Id, reason); /// /// Unbans a user by ID. /// /// ID of the user to unban. /// Reason for audit logs. /// /// Thrown when the client does not have the permission. /// Thrown when the user does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task UnbanMemberAsync(ulong user_id, string reason = null) => this.Discord.ApiClient.RemoveGuildBanAsync(this.Id, user_id, reason); /// /// Leaves this guild. /// /// /// Thrown when Discord is unable to process the request. public Task LeaveAsync() => this.Discord.ApiClient.LeaveGuildAsync(this.Id); /// /// Gets the bans for this guild. /// /// Collection of bans in this guild. /// Thrown when the client does not have the permission. /// Thrown when Discord is unable to process the request. public Task> GetBansAsync() => this.Discord.ApiClient.GetGuildBansAsync(this.Id); /// /// Gets a ban for a specific user. /// /// The Id of the user to get the ban for. /// Thrown when the specified user is not banned. /// The requested ban object. public Task GetBanAsync(ulong userId) => this.Discord.ApiClient.GetGuildBanAsync(this.Id, userId); /// /// Gets a ban for a specific user. /// /// The user to get the ban for. /// Thrown when the specified user is not banned. /// The requested ban object. public Task GetBanAsync(DiscordUser user) => this.GetBanAsync(user.Id); /// /// Creates a new text channel in this guild. /// /// Name of the new channel. /// Category to put this channel in. /// Topic of the channel. /// Permission overwrites for this channel. /// Whether the channel is to be flagged as not safe for work. /// Reason for audit logs. /// Slow mode timeout for users. /// The newly-created channel. /// Thrown when the client does not have the permission. /// Thrown when the guild does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task CreateTextChannelAsync(string name, DiscordChannel parent = null, Optional topic = default, IEnumerable overwrites = null, bool? nsfw = null, Optional perUserRateLimit = default, string reason = null) => this.CreateChannelAsync(name, ChannelType.Text, parent, topic, null, null, overwrites, nsfw, perUserRateLimit, null, reason); /// /// Creates a new channel category in this guild. /// /// Name of the new category. /// Permission overwrites for this category. /// Reason for audit logs. /// The newly-created channel category. /// Thrown when the client does not have the permission. /// Thrown when the guild does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task CreateChannelCategoryAsync(string name, IEnumerable overwrites = null, string reason = null) => this.CreateChannelAsync(name, ChannelType.Category, null, Optional.FromNoValue(), null, null, overwrites, null, Optional.FromNoValue(), null, reason); /// /// Creates a new stage channel in this guild. /// /// Name of the new stage channel. /// Permission overwrites for this stage channel. /// Reason for audit logs. /// The newly-created stage channel. /// Thrown when the client does not have the . /// Thrown when the guild does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. /// Thrown when the guilds has not enabled community. public Task CreateStageChannelAsync(string name, IEnumerable overwrites = null, string reason = null) => this.Features.HasCommunityEnabled ? this.CreateChannelAsync(name, ChannelType.Stage, null, Optional.FromNoValue(), null, null, overwrites, null, Optional.FromNoValue(), null, reason) : throw new NotSupportedException("Guild has not enabled community. Can not create a stage channel."); /// /// Creates a new news channel in this guild. /// /// Name of the new stage channel. /// Permission overwrites for this news channel. /// Reason for audit logs. /// The newly-created news channel. /// Thrown when the client does not have the . /// Thrown when the guild does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. /// Thrown when the guilds has not enabled community. public Task CreateNewsChannelAsync(string name, IEnumerable overwrites = null, string reason = null) => this.Features.HasCommunityEnabled ? this.CreateChannelAsync(name, ChannelType.News, null, Optional.FromNoValue(), null, null, overwrites, null, Optional.FromNoValue(), null, reason) : throw new NotSupportedException("Guild has not enabled community. Can not create a news channel."); /// /// Creates a new voice channel in this guild. /// /// Name of the new channel. /// Category to put this channel in. /// Bitrate of the channel. /// Maximum number of users in the channel. /// Permission overwrites for this channel. /// Video quality mode of the channel. /// Reason for audit logs. /// The newly-created channel. /// Thrown when the client does not have the permission. /// Thrown when the guild does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task CreateVoiceChannelAsync(string name, DiscordChannel parent = null, int? bitrate = null, int? user_limit = null, IEnumerable overwrites = null, VideoQualityMode? qualityMode = null, string reason = null) => this.CreateChannelAsync(name, ChannelType.Voice, parent, Optional.FromNoValue(), bitrate, user_limit, overwrites, null, Optional.FromNoValue(), qualityMode, reason); /// /// Creates a new channel in this guild. /// /// Name of the new channel. /// Type of the new channel. /// Category to put this channel in. /// Topic of the channel. /// Bitrate of the channel. Applies to voice only. /// Maximum number of users in the channel. Applies to voice only. /// Permission overwrites for this channel. /// Whether the channel is to be flagged as not safe for work. Applies to text only. /// Slow mode timeout for users. /// Video quality mode of the channel. Applies to voice only. /// Reason for audit logs. /// The newly-created channel. /// Thrown when the client does not have the permission. /// Thrown when the guild does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task CreateChannelAsync(string name, ChannelType type, DiscordChannel parent = null, Optional topic = default, int? bitrate = null, int? userLimit = null, IEnumerable overwrites = null, bool? nsfw = null, Optional perUserRateLimit = default, VideoQualityMode? qualityMode = null, string reason = null) { // technically you can create news/store channels but not always return type != ChannelType.Text && type != ChannelType.Voice && type != ChannelType.Category && type != ChannelType.News && type != ChannelType.Store && type != ChannelType.Stage ? throw new ArgumentException("Channel type must be text, voice, stage, or category.", nameof(type)) : type == ChannelType.Category && parent != null ? throw new ArgumentException("Cannot specify parent of a channel category.", nameof(parent)) : this.Discord.ApiClient.CreateGuildChannelAsync(this.Id, name, type, parent?.Id, topic, bitrate, userLimit, overwrites, nsfw, perUserRateLimit, qualityMode, reason); } /// /// Gets active threads. Can contain more threads. /// If the result's value 'HasMore' is true, you need to recall this function to get older threads. /// /// Thrown when the thread does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task GetActiveThreadsAsync() => this.Discord.ApiClient.GetActiveThreadsAsync(this.Id); // this is to commemorate the Great DAPI Channel Massacre of 2017-11-19. /// /// Deletes all channels in this guild. /// Note that this is irreversible. Use carefully! /// /// public Task DeleteAllChannelsAsync() { var tasks = this.Channels.Values.Select(xc => xc.DeleteAsync()); return Task.WhenAll(tasks); } /// /// Estimates the number of users to be pruned. /// /// Minimum number of inactivity days required for users to be pruned. Defaults to 7. /// The roles to be included in the prune. /// Number of users that will be pruned. /// Thrown when the client does not have the permission. /// Thrown when the guild does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task GetPruneCountAsync(int days = 7, IEnumerable includedRoles = null) { if (includedRoles != null) { includedRoles = includedRoles.Where(r => r != null); var roleCount = includedRoles.Count(); var roleArr = includedRoles.ToArray(); var rawRoleIds = new List(); for (var i = 0; i < roleCount; i++) { if (this._roles.ContainsKey(roleArr[i].Id)) rawRoleIds.Add(roleArr[i].Id); } return this.Discord.ApiClient.GetGuildPruneCountAsync(this.Id, days, rawRoleIds); } return this.Discord.ApiClient.GetGuildPruneCountAsync(this.Id, days, null); } /// /// Prunes inactive users from this guild. /// /// Minimum number of inactivity days required for users to be pruned. Defaults to 7. /// Whether to return the prune count after this method completes. This is discouraged for larger guilds. /// The roles to be included in the prune. /// Reason for audit logs. /// Number of users pruned. /// Thrown when the client does not have the permission. /// Thrown when the guild does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task PruneAsync(int days = 7, bool computePruneCount = true, IEnumerable includedRoles = null, string reason = null) { if (includedRoles != null) { includedRoles = includedRoles.Where(r => r != null); var roleCount = includedRoles.Count(); var roleArr = includedRoles.ToArray(); var rawRoleIds = new List(); for (var i = 0; i < roleCount; i++) { if (this._roles.ContainsKey(roleArr[i].Id)) rawRoleIds.Add(roleArr[i].Id); } return this.Discord.ApiClient.BeginGuildPruneAsync(this.Id, days, computePruneCount, rawRoleIds, reason); } return this.Discord.ApiClient.BeginGuildPruneAsync(this.Id, days, computePruneCount, null, reason); } /// /// Gets integrations attached to this guild. /// /// Collection of integrations attached to this guild. /// Thrown when the client does not have the permission. /// Thrown when the guild does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task> GetIntegrationsAsync() => this.Discord.ApiClient.GetGuildIntegrationsAsync(this.Id); /// /// Attaches an integration from current user to this guild. /// /// Integration to attach. /// The integration after being attached to the guild. /// Thrown when the client does not have the permission. /// Thrown when the guild does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task AttachUserIntegrationAsync(DiscordIntegration integration) => this.Discord.ApiClient.CreateGuildIntegrationAsync(this.Id, integration.Type, integration.Id); /// /// Modifies an integration in this guild. /// /// Integration to modify. /// Number of days after which the integration expires. /// Length of grace period which allows for renewing the integration. /// Whether emotes should be synced from this integration. /// The modified integration. /// Thrown when the client does not have the permission. /// Thrown when the guild does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task ModifyIntegrationAsync(DiscordIntegration integration, int expire_behaviour, int expire_grace_period, bool enable_emoticons) => this.Discord.ApiClient.ModifyGuildIntegrationAsync(this.Id, integration.Id, expire_behaviour, expire_grace_period, enable_emoticons); /// /// Removes an integration from this guild. /// /// Integration to remove. /// /// Thrown when the client does not have the permission. /// Thrown when the guild does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task DeleteIntegrationAsync(DiscordIntegration integration) => this.Discord.ApiClient.DeleteGuildIntegrationAsync(this.Id, integration); /// /// Forces re-synchronization of an integration for this guild. /// /// Integration to synchronize. /// /// Thrown when the client does not have the permission. /// Thrown when the guild does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task SyncIntegrationAsync(DiscordIntegration integration) => this.Discord.ApiClient.SyncGuildIntegrationAsync(this.Id, integration.Id); /// /// Gets the voice regions for this guild. /// /// Voice regions available for this guild. /// Thrown when Discord is unable to process the request. public async Task> ListVoiceRegionsAsync() { var vrs = await this.Discord.ApiClient.GetGuildVoiceRegionsAsync(this.Id).ConfigureAwait(false); foreach (var xvr in vrs) this.Discord.InternalVoiceRegions.TryAdd(xvr.Id, xvr); return vrs; } /// /// Gets an invite from this guild from an invite code. /// /// The invite code /// An invite, or null if not in cache. public DiscordInvite GetInvite(string code) => this._invites.TryGetValue(code, out var invite) ? invite : null; /// /// Gets all the invites created for all the channels in this guild. /// /// A collection of invites. /// Thrown when Discord is unable to process the request. public async Task> GetInvitesAsync() { var res = await this.Discord.ApiClient.GetGuildInvitesAsync(this.Id).ConfigureAwait(false); var intents = this.Discord.Configuration.Intents; if (!intents.HasIntent(DiscordIntents.GuildInvites)) { for (var i = 0; i < res.Count; i++) this._invites[res[i].Code] = res[i]; } return res; } /// /// Gets the vanity invite for this guild. /// /// A partial vanity invite. /// Thrown when the client does not have the permission. /// Thrown when Discord is unable to process the request. public Task GetVanityInviteAsync() => this.Discord.ApiClient.GetGuildVanityUrlAsync(this.Id); /// /// Gets all the webhooks created for all the channels in this guild. /// /// A collection of webhooks this guild has. /// Thrown when the client does not have the permission. /// Thrown when Discord is unable to process the request. public Task> GetWebhooksAsync() => this.Discord.ApiClient.GetGuildWebhooksAsync(this.Id); /// /// Gets this guild's widget image. /// /// The format of the widget. /// The URL of the widget image. public string GetWidgetImage(WidgetType bannerType = WidgetType.Shield) { var param = bannerType switch { WidgetType.Banner1 => "banner1", WidgetType.Banner2 => "banner2", WidgetType.Banner3 => "banner3", WidgetType.Banner4 => "banner4", _ => "shield", }; return $"{Endpoints.BASE_URI}{Endpoints.GUILDS}/{this.Id}{Endpoints.WIDGET_PNG}?style={param}"; } /// /// Gets a member of this guild by their user ID. /// /// ID of the member to get. /// The requested member. /// Thrown when Discord is unable to process the request. public async Task GetMemberAsync(ulong userId) { if (this._members != null && this._members.TryGetValue(userId, out var mbr)) return mbr; mbr = await this.Discord.ApiClient.GetGuildMemberAsync(this.Id, userId).ConfigureAwait(false); var intents = this.Discord.Configuration.Intents; if (intents.HasIntent(DiscordIntents.GuildMembers)) { if (this._members != null) { this._members[userId] = mbr; } } return mbr; } /// /// Retrieves a full list of members from Discord. This method will bypass cache. /// /// A collection of all members in this guild. /// Thrown when Discord is unable to process the request. public async Task> GetAllMembersAsync() { var recmbr = new HashSet(); var recd = 1000; var last = 0ul; while (recd > 0) { var tms = await this.Discord.ApiClient.ListGuildMembersAsync(this.Id, 1000, last == 0 ? null : (ulong?)last).ConfigureAwait(false); recd = tms.Count; foreach (var xtm in tms) { var usr = new DiscordUser(xtm.User) { Discord = this.Discord }; usr = this.Discord.UserCache.AddOrUpdate(xtm.User.Id, usr, (id, old) => { old.Username = usr.Username; old.Discord = usr.Discord; old.AvatarHash = usr.AvatarHash; return old; }); recmbr.Add(new DiscordMember(xtm) { Discord = this.Discord, _guild_id = this.Id }); } var tm = tms.LastOrDefault(); last = tm?.User.Id ?? 0; } return new ReadOnlySet(recmbr); } /// /// Requests that Discord send a list of guild members based on the specified arguments. This method will fire the event. /// If no arguments aside from and are specified, this will request all guild members. /// /// Filters the returned members based on what the username starts with. Either this or must not be null. /// The must also be greater than 0 if this is specified. /// Total number of members to request. This must be greater than 0 if is specified. /// Whether to include the associated with the fetched members. /// Whether to limit the request to the specified user ids. Either this or must not be null. /// The unique string to identify the response. public async Task RequestMembersAsync(string query = "", int limit = 0, bool? presences = null, IEnumerable userIds = null, string nonce = null) { if (this.Discord is not DiscordClient client) throw new InvalidOperationException("This operation is only valid for regular Discord clients."); if (query == null && userIds == null) throw new ArgumentException("The query and user IDs cannot both be null."); if (query != null && userIds != null) query = null; var grgm = new GatewayRequestGuildMembers(this) { Query = query, Limit = limit >= 0 ? limit : 0, Presences = presences, UserIds = userIds, Nonce = nonce }; var payload = new GatewayPayload { OpCode = GatewayOpCode.RequestGuildMembers, Data = grgm }; var payloadStr = JsonConvert.SerializeObject(payload, Formatting.None); await client.WsSendAsync(payloadStr).ConfigureAwait(false); } /// /// Gets all the channels this guild has. /// /// A collection of this guild's channels. /// Thrown when Discord is unable to process the request. public Task> GetChannelsAsync() => this.Discord.ApiClient.GetGuildChannelsAsync(this.Id); /// /// Creates a new role in this guild. /// /// Name of the role. /// Permissions for the role. /// Color for the role. /// Whether the role is to be hoisted. /// Whether the role is to be mentionable. /// Reason for audit logs. /// The newly-created role. /// Thrown when the client does not have the permission. /// Thrown when Discord is unable to process the request. public Task CreateRoleAsync(string name = null, Permissions? permissions = null, DiscordColor? color = null, bool? hoist = null, bool? mentionable = null, string reason = null) => this.Discord.ApiClient.CreateGuildRoleAsync(this.Id, name, permissions, color?.Value, hoist, mentionable, reason); /// /// Gets a role from this guild by its ID. /// /// ID of the role to get. /// Requested role. /// Thrown when Discord is unable to process the request. public DiscordRole GetRole(ulong id) => this._roles.TryGetValue(id, out var role) ? role : null; /// /// Gets a channel from this guild by its ID. /// /// ID of the channel to get. /// Requested channel. /// Thrown when Discord is unable to process the request. public DiscordChannel GetChannel(ulong id) => (this._channels != null && this._channels.TryGetValue(id, out var channel)) ? channel : null; /// /// Gets a thread from this guild by its ID. /// /// ID of the thread to get. /// Requested thread. /// Thrown when Discord is unable to process the request. public DiscordThreadChannel GetThread(ulong id) => (this._threads != null && this._threads.TryGetValue(id, out var thread)) ? thread : null; /// /// Gets audit log entries for this guild. /// /// Maximum number of entries to fetch. /// Filter by member responsible. /// Filter by action type. /// A collection of requested audit log entries. /// Thrown when the client does not have the permission. /// Thrown when Discord is unable to process the request. public async Task> GetAuditLogsAsync(int? limit = null, DiscordMember by_member = null, AuditLogActionType? action_type = null) { var alrs = new List(); int ac = 1, tc = 0, rmn = 100; var last = 0ul; while (ac > 0) { rmn = limit != null ? limit.Value - tc : 100; rmn = Math.Min(100, rmn); if (rmn <= 0) break; var alr = await this.Discord.ApiClient.GetAuditLogsAsync(this.Id, rmn, null, last == 0 ? null : (ulong?)last, by_member?.Id, (int?)action_type).ConfigureAwait(false); ac = alr.Entries.Count(); tc += ac; if (ac > 0) { last = alr.Entries.Last().Id; alrs.Add(alr); } } var amr = alrs.SelectMany(xa => xa.Users) .GroupBy(xu => xu.Id) .Select(xgu => xgu.First()); foreach (var xau in amr) { if (this.Discord.UserCache.ContainsKey(xau.Id)) continue; var xtu = new TransportUser { Id = xau.Id, Username = xau.Username, Discriminator = xau.Discriminator, AvatarHash = xau.AvatarHash }; var xu = new DiscordUser(xtu) { Discord = this.Discord }; xu = this.Discord.UserCache.AddOrUpdate(xu.Id, xu, (id, old) => { old.Username = xu.Username; old.Discriminator = xu.Discriminator; old.AvatarHash = xu.AvatarHash; return old; }); } var ahr = alrs.SelectMany(xa => xa.Webhooks) .GroupBy(xh => xh.Id) .Select(xgh => xgh.First()); var ams = amr.Select(xau => (this._members != null && this._members.TryGetValue(xau.Id, out var member)) ? member : new DiscordMember { Discord = this.Discord, Id = xau.Id, _guild_id = this.Id }); var amd = ams.ToDictionary(xm => xm.Id, xm => xm); Dictionary ahd = null; if (ahr.Any()) { var whr = await this.GetWebhooksAsync().ConfigureAwait(false); var whs = whr.ToDictionary(xh => xh.Id, xh => xh); var amh = ahr.Select(xah => whs.TryGetValue(xah.Id, out var webhook) ? webhook : new DiscordWebhook { Discord = this.Discord, Name = xah.Name, Id = xah.Id, AvatarHash = xah.AvatarHash, ChannelId = xah.ChannelId, GuildId = xah.GuildId, Token = xah.Token }); ahd = amh.ToDictionary(xh => xh.Id, xh => xh); } var acs = alrs.SelectMany(xa => xa.Entries).OrderByDescending(xa => xa.Id); var entries = new List(); foreach (var xac in acs) { DiscordAuditLogEntry entry = null; ulong t1, t2; int t3, t4; long t5, t6; bool p1, p2; switch (xac.ActionType) { case AuditLogActionType.GuildUpdate: entry = new DiscordAuditLogGuildEntry { Target = this }; var entrygld = entry as DiscordAuditLogGuildEntry; foreach (var xc in xac.Changes) { switch (xc.Key.ToLowerInvariant()) { case "name": entrygld.NameChange = new PropertyChange { Before = xc.OldValueString, After = xc.NewValueString }; break; case "owner_id": entrygld.OwnerChange = new PropertyChange { Before = (this._members != null && this._members.TryGetValue(xc.OldValueUlong, out var oldMember)) ? oldMember : await this.GetMemberAsync(xc.OldValueUlong).ConfigureAwait(false), After = (this._members != null && this._members.TryGetValue(xc.NewValueUlong, out var newMember)) ? newMember : await this.GetMemberAsync(xc.NewValueUlong).ConfigureAwait(false) }; break; case "icon_hash": entrygld.IconChange = new PropertyChange { Before = xc.OldValueString != null ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.ICONS}/{this.Id}/{xc.OldValueString}.webp" : null, After = xc.OldValueString != null ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.ICONS}/{this.Id}/{xc.NewValueString}.webp" : null }; break; case "verification_level": entrygld.VerificationLevelChange = new PropertyChange { Before = (VerificationLevel)(long)xc.OldValue, After = (VerificationLevel)(long)xc.NewValue }; break; case "afk_channel_id": ulong.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1); ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2); entrygld.AfkChannelChange = new PropertyChange { Before = this.GetChannel(t1) ?? new DiscordChannel { Id = t1, Discord = this.Discord, GuildId = this.Id }, After = this.GetChannel(t2) ?? new DiscordChannel { Id = t1, Discord = this.Discord, GuildId = this.Id } }; break; case "widget_channel_id": ulong.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1); ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2); entrygld.EmbedChannelChange = new PropertyChange { Before = this.GetChannel(t1) ?? new DiscordChannel { Id = t1, Discord = this.Discord, GuildId = this.Id }, After = this.GetChannel(t2) ?? new DiscordChannel { Id = t1, Discord = this.Discord, GuildId = this.Id } }; break; case "splash_hash": entrygld.SplashChange = new PropertyChange { Before = xc.OldValueString != null ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.SPLASHES}/{this.Id}/{xc.OldValueString}.webp?size=2048" : null, After = xc.NewValueString != null ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.SPLASHES}/{this.Id}/{xc.NewValueString}.webp?size=2048" : null }; break; case "default_message_notifications": entrygld.NotificationSettingsChange = new PropertyChange { Before = (DefaultMessageNotifications)(long)xc.OldValue, After = (DefaultMessageNotifications)(long)xc.NewValue }; break; case "system_channel_id": ulong.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1); ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2); entrygld.SystemChannelChange = new PropertyChange { Before = this.GetChannel(t1) ?? new DiscordChannel { Id = t1, Discord = this.Discord, GuildId = this.Id }, After = this.GetChannel(t2) ?? new DiscordChannel { Id = t1, Discord = this.Discord, GuildId = this.Id } }; break; case "explicit_content_filter": entrygld.ExplicitContentFilterChange = new PropertyChange { Before = (ExplicitContentFilter)(long)xc.OldValue, After = (ExplicitContentFilter)(long)xc.NewValue }; break; case "mfa_level": entrygld.MfaLevelChange = new PropertyChange { Before = (MfaLevel)(long)xc.OldValue, After = (MfaLevel)(long)xc.NewValue }; break; case "region": entrygld.RegionChange = new PropertyChange { Before = xc.OldValueString, After = xc.NewValueString }; break; default: this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in guild update: {0} - this should be reported to library developers", xc.Key); break; } } break; case AuditLogActionType.ChannelCreate: case AuditLogActionType.ChannelDelete: case AuditLogActionType.ChannelUpdate: entry = new DiscordAuditLogChannelEntry { Target = this.GetChannel(xac.TargetId.Value) ?? new DiscordChannel { Id = xac.TargetId.Value, Discord = this.Discord, GuildId = this.Id } }; var entrychn = entry as DiscordAuditLogChannelEntry; foreach (var xc in xac.Changes) { switch (xc.Key.ToLowerInvariant()) { case "name": entrychn.NameChange = new PropertyChange { Before = xc.OldValue != null ? xc.OldValueString : null, After = xc.NewValue != null ? xc.NewValueString : null }; break; case "type": p1 = ulong.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1); p2 = ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2); entrychn.TypeChange = new PropertyChange { Before = p1 ? (ChannelType?)t1 : null, After = p2 ? (ChannelType?)t2 : null }; break; case "permission_overwrites": var olds = xc.OldValues?.OfType() ?.Select(xjo => xjo.ToObject()) ?.Select(xo => { xo.Discord = this.Discord; return xo; }); var news = xc.NewValues?.OfType() ?.Select(xjo => xjo.ToObject()) ?.Select(xo => { xo.Discord = this.Discord; return xo; }); entrychn.OverwriteChange = new PropertyChange> { Before = olds != null ? new ReadOnlyCollection(new List(olds)) : null, After = news != null ? new ReadOnlyCollection(new List(news)) : null }; break; case "topic": entrychn.TopicChange = new PropertyChange { Before = xc.OldValueString, After = xc.NewValueString }; break; case "nsfw": entrychn.NsfwChange = new PropertyChange { Before = (bool?)xc.OldValue, After = (bool?)xc.NewValue }; break; case "bitrate": entrychn.BitrateChange = new PropertyChange { Before = (int?)(long?)xc.OldValue, After = (int?)(long?)xc.NewValue }; break; case "rate_limit_per_user": entrychn.PerUserRateLimitChange = new PropertyChange { Before = (int?)(long?)xc.OldValue, After = (int?)(long?)xc.NewValue }; break; default: this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in channel update: {0} - this should be reported to library developers", xc.Key); break; } } break; case AuditLogActionType.OverwriteCreate: case AuditLogActionType.OverwriteDelete: case AuditLogActionType.OverwriteUpdate: entry = new DiscordAuditLogOverwriteEntry { Target = this.GetChannel(xac.TargetId.Value)?.PermissionOverwrites.FirstOrDefault(xo => xo.Id == xac.Options.Id), Channel = this.GetChannel(xac.TargetId.Value) }; var entryovr = entry as DiscordAuditLogOverwriteEntry; foreach (var xc in xac.Changes) { switch (xc.Key.ToLowerInvariant()) { case "deny": p1 = ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1); p2 = ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2); entryovr.DenyChange = new PropertyChange { Before = p1 ? (Permissions?)t1 : null, After = p2 ? (Permissions?)t2 : null }; break; case "allow": p1 = ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1); p2 = ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2); entryovr.AllowChange = new PropertyChange { Before = p1 ? (Permissions?)t1 : null, After = p2 ? (Permissions?)t2 : null }; break; case "type": entryovr.TypeChange = new PropertyChange { Before = xc.OldValueString, After = xc.NewValueString }; break; case "id": p1 = ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1); p2 = ulong.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2); entryovr.TargetIdChange = new PropertyChange { Before = p1 ? (ulong?)t1 : null, After = p2 ? (ulong?)t2 : null }; break; default: this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in overwrite update: {0} - this should be reported to library developers", xc.Key); break; } } break; case AuditLogActionType.Kick: entry = new DiscordAuditLogKickEntry { Target = amd.TryGetValue(xac.TargetId.Value, out var kickMember) ? kickMember : new DiscordMember { Id = xac.TargetId.Value, Discord = this.Discord, _guild_id = this.Id } }; break; case AuditLogActionType.Prune: entry = new DiscordAuditLogPruneEntry { Days = xac.Options.DeleteMemberDays, Toll = xac.Options.MembersRemoved }; break; case AuditLogActionType.Ban: case AuditLogActionType.Unban: entry = new DiscordAuditLogBanEntry { Target = amd.TryGetValue(xac.TargetId.Value, out var unbanMember) ? unbanMember : new DiscordMember { Id = xac.TargetId.Value, Discord = this.Discord, _guild_id = this.Id } }; break; case AuditLogActionType.MemberUpdate: case AuditLogActionType.MemberRoleUpdate: entry = new DiscordAuditLogMemberUpdateEntry { Target = amd.TryGetValue(xac.TargetId.Value, out var roleUpdMember) ? roleUpdMember : new DiscordMember { Id = xac.TargetId.Value, Discord = this.Discord, _guild_id = this.Id } }; var entrymbu = entry as DiscordAuditLogMemberUpdateEntry; foreach (var xc in xac.Changes) { switch (xc.Key.ToLowerInvariant()) { case "nick": entrymbu.NicknameChange = new PropertyChange { Before = xc.OldValueString, After = xc.NewValueString }; break; case "deaf": entrymbu.DeafenChange = new PropertyChange { Before = (bool?)xc.OldValue, After = (bool?)xc.NewValue }; break; case "mute": entrymbu.MuteChange = new PropertyChange { Before = (bool?)xc.OldValue, After = (bool?)xc.NewValue }; break; case "$add": entrymbu.AddedRoles = new ReadOnlyCollection(xc.NewValues.Select(xo => (ulong)xo["id"]).Select(this.GetRole).ToList()); break; case "$remove": entrymbu.RemovedRoles = new ReadOnlyCollection(xc.NewValues.Select(xo => (ulong)xo["id"]).Select(this.GetRole).ToList()); break; default: this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in member update: {0} - this should be reported to library developers", xc.Key); break; } } break; case AuditLogActionType.RoleCreate: case AuditLogActionType.RoleDelete: case AuditLogActionType.RoleUpdate: entry = new DiscordAuditLogRoleUpdateEntry { Target = this.GetRole(xac.TargetId.Value) ?? new DiscordRole { Id = xac.TargetId.Value, Discord = this.Discord } }; var entryrol = entry as DiscordAuditLogRoleUpdateEntry; foreach (var xc in xac.Changes) { switch (xc.Key.ToLowerInvariant()) { case "name": entryrol.NameChange = new PropertyChange { Before = xc.OldValueString, After = xc.NewValueString }; break; case "color": p1 = int.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t3); p2 = int.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t4); entryrol.ColorChange = new PropertyChange { Before = p1 ? (int?)t3 : null, After = p2 ? (int?)t4 : null }; break; case "permissions": entryrol.PermissionChange = new PropertyChange { Before = xc.OldValue != null ? (Permissions?)long.Parse((string)xc.OldValue) : null, After = xc.NewValue != null ? (Permissions?)long.Parse((string)xc.NewValue) : null }; break; case "position": entryrol.PositionChange = new PropertyChange { Before = xc.OldValue != null ? (int?)(long)xc.OldValue : null, After = xc.NewValue != null ? (int?)(long)xc.NewValue : null, }; break; case "mentionable": entryrol.MentionableChange = new PropertyChange { Before = xc.OldValue != null ? (bool?)xc.OldValue : null, After = xc.NewValue != null ? (bool?)xc.NewValue : null }; break; case "hoist": entryrol.HoistChange = new PropertyChange { Before = (bool?)xc.OldValue, After = (bool?)xc.NewValue }; break; default: this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in role update: {0} - this should be reported to library developers", xc.Key); break; } } break; case AuditLogActionType.InviteCreate: case AuditLogActionType.InviteDelete: case AuditLogActionType.InviteUpdate: entry = new DiscordAuditLogInviteEntry(); var inv = new DiscordInvite { Discord = this.Discord, Guild = new DiscordInviteGuild { Discord = this.Discord, Id = this.Id, Name = this.Name, SplashHash = this.SplashHash } }; var entryinv = entry as DiscordAuditLogInviteEntry; foreach (var xc in xac.Changes) { switch (xc.Key.ToLowerInvariant()) { case "max_age": p1 = int.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t3); p2 = int.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t4); entryinv.MaxAgeChange = new PropertyChange { Before = p1 ? (int?)t3 : null, After = p2 ? (int?)t4 : null }; break; case "code": inv.Code = xc.OldValueString ?? xc.NewValueString; entryinv.CodeChange = new PropertyChange { Before = xc.OldValueString, After = xc.NewValueString }; break; case "temporary": entryinv.TemporaryChange = new PropertyChange { Before = xc.OldValue != null ? (bool?)xc.OldValue : null, After = xc.NewValue != null ? (bool?)xc.NewValue : null }; break; case "inviter_id": p1 = ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1); p2 = ulong.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2); entryinv.InviterChange = new PropertyChange { Before = amd.TryGetValue(t1, out var propBeforeMember) ? propBeforeMember : new DiscordMember { Id = t1, Discord = this.Discord, _guild_id = this.Id }, After = amd.TryGetValue(t2, out var propAfterMember) ? propAfterMember : new DiscordMember { Id = t1, Discord = this.Discord, _guild_id = this.Id }, }; break; case "channel_id": p1 = ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1); p2 = ulong.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2); entryinv.ChannelChange = new PropertyChange { Before = p1 ? this.GetChannel(t1) ?? new DiscordChannel { Id = t1, Discord = this.Discord, GuildId = this.Id } : null, After = p2 ? this.GetChannel(t2) ?? new DiscordChannel { Id = t1, Discord = this.Discord, GuildId = this.Id } : null }; var ch = entryinv.ChannelChange.Before ?? entryinv.ChannelChange.After; var cht = ch?.Type; inv.Channel = new DiscordInviteChannel { Discord = this.Discord, Id = p1 ? t1 : t2, Name = ch?.Name, Type = cht != null ? cht.Value : ChannelType.Unknown }; break; case "uses": p1 = int.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t3); p2 = int.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t4); entryinv.UsesChange = new PropertyChange { Before = p1 ? (int?)t3 : null, After = p2 ? (int?)t4 : null }; break; case "max_uses": p1 = int.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t3); p2 = int.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t4); entryinv.MaxUsesChange = new PropertyChange { Before = p1 ? (int?)t3 : null, After = p2 ? (int?)t4 : null }; break; default: this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in invite update: {0} - this should be reported to library developers", xc.Key); break; } } entryinv.Target = inv; break; case AuditLogActionType.WebhookCreate: case AuditLogActionType.WebhookDelete: case AuditLogActionType.WebhookUpdate: entry = new DiscordAuditLogWebhookEntry { Target = ahd.TryGetValue(xac.TargetId.Value, out var webhook) ? webhook : new DiscordWebhook { Id = xac.TargetId.Value, Discord = this.Discord } }; var entrywhk = entry as DiscordAuditLogWebhookEntry; foreach (var xc in xac.Changes) { switch (xc.Key.ToLowerInvariant()) { case "name": entrywhk.NameChange = new PropertyChange { Before = xc.OldValueString, After = xc.NewValueString }; break; case "channel_id": p1 = ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1); p2 = ulong.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2); entrywhk.ChannelChange = new PropertyChange { Before = p1 ? this.GetChannel(t1) ?? new DiscordChannel { Id = t1, Discord = this.Discord, GuildId = this.Id } : null, After = p2 ? this.GetChannel(t2) ?? new DiscordChannel { Id = t1, Discord = this.Discord, GuildId = this.Id } : null }; break; case "type": // ??? p1 = int.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t3); p2 = int.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t4); entrywhk.TypeChange = new PropertyChange { Before = p1 ? (int?)t3 : null, After = p2 ? (int?)t4 : null }; break; case "avatar_hash": entrywhk.AvatarHashChange = new PropertyChange { Before = xc.OldValueString, After = xc.NewValueString }; break; default: this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in webhook update: {0} - this should be reported to library developers", xc.Key); break; } } break; case AuditLogActionType.EmojiCreate: case AuditLogActionType.EmojiDelete: case AuditLogActionType.EmojiUpdate: entry = new DiscordAuditLogEmojiEntry { Target = this._emojis.TryGetValue(xac.TargetId.Value, out var target) ? target : new DiscordEmoji { Id = xac.TargetId.Value, Discord = this.Discord } }; var entryemo = entry as DiscordAuditLogEmojiEntry; foreach (var xc in xac.Changes) { switch (xc.Key.ToLowerInvariant()) { case "name": entryemo.NameChange = new PropertyChange { Before = xc.OldValueString, After = xc.NewValueString }; break; default: this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in emote update: {0} - this should be reported to library developers", xc.Key); break; } } break; case AuditLogActionType.StageInstanceCreate: case AuditLogActionType.StageInstanceDelete: case AuditLogActionType.StageInstanceUpdate: entry = new DiscordAuditLogStageEntry { Target = this._stageInstances.TryGetValue(xac.TargetId.Value, out var stage) ? stage : new DiscordStageInstance { Id = xac.TargetId.Value, Discord = this.Discord } }; var entrysta = entry as DiscordAuditLogStageEntry; foreach (var xc in xac.Changes) { switch (xc.Key.ToLowerInvariant()) { case "topic": entrysta.TopicChange = new PropertyChange { Before = xc.OldValueString, After = xc.NewValueString }; break; case "privacy_level": entrysta.PrivacyLevelChange = new PropertyChange { Before = long.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t5) ? (StagePrivacyLevel?)t5 : null, After = long.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t6) ? (StagePrivacyLevel?)t6 : null, }; break; default: this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in stage instance update: {0} - this should be reported to library developers", xc.Key); break; } } break; case AuditLogActionType.StickerCreate: case AuditLogActionType.StickerDelete: case AuditLogActionType.StickerUpdate: entry = new DiscordAuditLogStickerEntry { Target = this._stickers.TryGetValue(xac.TargetId.Value, out var sticker) ? sticker : new DiscordSticker { Id = xac.TargetId.Value, Discord = this.Discord } }; var entrysti = entry as DiscordAuditLogStickerEntry; foreach (var xc in xac.Changes) { switch (xc.Key.ToLowerInvariant()) { case "name": entrysti.NameChange = new PropertyChange { Before = xc.OldValueString, After = xc.NewValueString }; break; case "description": entrysti.DescriptionChange = new PropertyChange { Before = xc.OldValueString, After = xc.NewValueString }; break; case "tags": entrysti.TagsChange = new PropertyChange { Before = xc.OldValueString, After = xc.NewValueString }; break; case "guild_id": entrysti.GuildIdChange = new PropertyChange { Before = ulong.TryParse(xc.OldValueString, out var ogid) ? ogid : null, After = ulong.TryParse(xc.NewValueString, out var ngid) ? ngid : null }; break; case "available": entrysti.AvailabilityChange = new PropertyChange { Before = (bool?)xc.OldValue, After = (bool?)xc.NewValue, }; break; case "asset": entrysti.AssetChange = new PropertyChange { Before = xc.OldValueString, After = xc.NewValueString }; break; case "id": entrysti.IdChange = new PropertyChange { Before = ulong.TryParse(xc.OldValueString, out var oid) ? oid : null, After = ulong.TryParse(xc.NewValueString, out var nid) ? nid : null }; break; case "type": p1 = long.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t5); p2 = long.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t6); entrysti.TypeChange = new PropertyChange { Before = p1 ? (StickerType?)t5 : null, After = p2 ? (StickerType?)t6 : null }; break; case "format_type": p1 = long.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t5); p2 = long.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t6); entrysti.FormatChange = new PropertyChange { Before = p1 ? (StickerFormat?)t5 : null, After = p2 ? (StickerFormat?)t6 : null }; break; default: this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in sticker update: {0} - this should be reported to library developers", xc.Key); break; } } break; case AuditLogActionType.MessageDelete: case AuditLogActionType.MessageBulkDelete: { entry = new DiscordAuditLogMessageEntry(); var entrymsg = entry as DiscordAuditLogMessageEntry; if (xac.Options != null) { entrymsg.Channel = this.GetChannel(xac.Options.ChannelId) ?? new DiscordChannel { Id = xac.Options.ChannelId, Discord = this.Discord, GuildId = this.Id }; entrymsg.MessageCount = xac.Options.Count; } if (entrymsg.Channel != null) { entrymsg.Target = this.Discord is DiscordClient dc && dc.MessageCache != null && dc.MessageCache.TryGet(xm => xm.Id == xac.TargetId.Value && xm.ChannelId == entrymsg.Channel.Id, out var msg) ? msg : new DiscordMessage { Discord = this.Discord, Id = xac.TargetId.Value }; } break; } case AuditLogActionType.MessagePin: case AuditLogActionType.MessageUnpin: { entry = new DiscordAuditLogMessagePinEntry(); var entrypin = entry as DiscordAuditLogMessagePinEntry; if (this.Discord is not DiscordClient dc) { break; } if (xac.Options != null) { DiscordMessage message = default; dc.MessageCache?.TryGet(x => x.Id == xac.Options.MessageId && x.ChannelId == xac.Options.ChannelId, out message); entrypin.Channel = this.GetChannel(xac.Options.ChannelId) ?? new DiscordChannel { Id = xac.Options.ChannelId, Discord = this.Discord, GuildId = this.Id }; entrypin.Message = message ?? new DiscordMessage { Id = xac.Options.MessageId, Discord = this.Discord }; } if (xac.TargetId.HasValue) { dc.UserCache.TryGetValue(xac.TargetId.Value, out var user); entrypin.Target = user ?? new DiscordUser { Id = user.Id, Discord = this.Discord }; } break; } case AuditLogActionType.BotAdd: { entry = new DiscordAuditLogBotAddEntry(); if (!(this.Discord is DiscordClient dc && xac.TargetId.HasValue)) { break; } dc.UserCache.TryGetValue(xac.TargetId.Value, out var bot); (entry as DiscordAuditLogBotAddEntry).TargetBot = bot ?? new DiscordUser { Id = xac.TargetId.Value, Discord = this.Discord }; break; } case AuditLogActionType.MemberMove: entry = new DiscordAuditLogMemberMoveEntry(); if (xac.Options == null) { break; } var moveentry = entry as DiscordAuditLogMemberMoveEntry; moveentry.UserCount = xac.Options.Count; moveentry.Channel = this.GetChannel(xac.Options.ChannelId) ?? new DiscordChannel { Id = xac.Options.ChannelId, Discord = this.Discord, GuildId = this.Id }; break; case AuditLogActionType.MemberDisconnect: entry = new DiscordAuditLogMemberDisconnectEntry { UserCount = xac.Options?.Count ?? 0 }; break; case AuditLogActionType.IntegrationCreate: case AuditLogActionType.IntegrationDelete: case AuditLogActionType.IntegrationUpdate: entry = new DiscordAuditLogIntegrationEntry(); var integentry = entry as DiscordAuditLogIntegrationEntry; foreach (var xc in xac.Changes) { switch (xc.Key.ToLowerInvariant()) { case "enable_emoticons": integentry.EnableEmoticons = new PropertyChange { Before = (bool?)xc.OldValue, After = (bool?)xc.NewValue }; break; case "expire_behavior": integentry.ExpireBehavior = new PropertyChange { Before = (int?)xc.OldValue, After = (int?)xc.NewValue }; break; case "expire_grace_period": integentry.ExpireBehavior = new PropertyChange { Before = (int?)xc.OldValue, After = (int?)xc.NewValue }; break; default: this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in integration update: {0} - this should be reported to library developers", xc.Key); break; } } break; case AuditLogActionType.ThreadCreate: case AuditLogActionType.ThreadDelete: case AuditLogActionType.ThreadUpdate: entry = new DiscordAuditLogThreadEntry { Target = this._threads.TryGetValue(xac.TargetId.Value, out var thread) ? thread : new DiscordThreadChannel { Id = xac.TargetId.Value, Discord = this.Discord } }; var entrythr = entry as DiscordAuditLogThreadEntry; foreach (var xc in xac.Changes) { switch (xc.Key.ToLowerInvariant()) { case "name": entrythr.NameChange = new PropertyChange { Before = xc.OldValue != null ? xc.OldValueString : null, After = xc.NewValue != null ? xc.NewValueString : null }; break; case "type": p1 = ulong.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1); p2 = ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2); entrythr.TypeChange = new PropertyChange { Before = p1 ? (ChannelType?)t1 : null, After = p2 ? (ChannelType?)t2 : null }; break; case "archived": entrythr.ArchivedChange = new PropertyChange { Before = xc.OldValue != null ? (bool?)xc.OldValue : null, After = xc.NewValue != null ? (bool?)xc.NewValue : null }; break; case "locked": entrythr.LockedChange = new PropertyChange { Before = xc.OldValue != null ? (bool?)xc.OldValue : null, After = xc.NewValue != null ? (bool?)xc.NewValue : null }; break; case "auto_archive_duration": p1 = long.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t5); p2 = long.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t6); entrythr.AutoArchiveDurationChange = new PropertyChange { Before = p1 ? (ThreadAutoArchiveDuration?)t5 : null, After = p2 ? (ThreadAutoArchiveDuration?)t6 : null }; break; case "rate_limit_per_user": entrythr.PerUserRateLimitChange = new PropertyChange { Before = (int?)(long?)xc.OldValue, After = (int?)(long?)xc.NewValue }; break; default: this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in thread update: {0} - this should be reported to library developers", xc.Key); break; } } break; case AuditLogActionType.SheduledEventCreate: case AuditLogActionType.SheduledEventDelete: case AuditLogActionType.SheduledEventUpdate: entry = new DiscordAuditLogSheduledEventEntry { //Target = this._events.TryGetValue(xac.TargetId.Value, out var sheduled_event) ? sheduled_event : new DiscordEvent { Id = xac.TargetId.Value, Discord = this.Discord } }; var entryse = entry as DiscordAuditLogSheduledEventEntry; foreach (var xc in xac.Changes) { switch (xc.Key.ToLowerInvariant()) { case "channel_id": entryse.ChannelIdChange = new PropertyChange { Before = ulong.TryParse(xc.OldValueString, out var ogid) ? ogid : null, After = ulong.TryParse(xc.NewValueString, out var ngid) ? ngid : null }; break; case "description": entryse.DescriptionChange = new PropertyChange { Before = xc.OldValue != null ? xc.OldValueString : null, After = xc.NewValue != null ? xc.NewValueString : null }; break; case "privacy_level": p1 = long.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t5); p2 = long.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t6); entryse.PrivacyLevelChange = new PropertyChange { Before = p1 ? (StagePrivacyLevel?)t5 : null, After = p2 ? (StagePrivacyLevel?)t6 : null }; break; case "status": p1 = long.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t5); p2 = long.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t6); entryse.StatusChange = new PropertyChange { Before = p1 ? (EventStatus?)t5 : null, After = p2 ? (EventStatus?)t6 : null }; break; default: this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in sheduled event update: {0} - this should be reported to library developers", xc.Key); break; } } break; default: this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown audit log action type: {0} - this should be reported to library developers", (int)xac.ActionType); break; } if (entry == null) continue; entry.ActionCategory = xac.ActionType switch { AuditLogActionType.ChannelCreate or AuditLogActionType.EmojiCreate or AuditLogActionType.InviteCreate or AuditLogActionType.OverwriteCreate or AuditLogActionType.RoleCreate or AuditLogActionType.WebhookCreate or AuditLogActionType.IntegrationCreate or AuditLogActionType.StickerCreate or AuditLogActionType.StageInstanceCreate or AuditLogActionType.ThreadCreate or AuditLogActionType.SheduledEventCreate => AuditLogActionCategory.Create, AuditLogActionType.ChannelDelete or AuditLogActionType.EmojiDelete or AuditLogActionType.InviteDelete or AuditLogActionType.MessageDelete or AuditLogActionType.MessageBulkDelete or AuditLogActionType.OverwriteDelete or AuditLogActionType.RoleDelete or AuditLogActionType.WebhookDelete or AuditLogActionType.IntegrationDelete or AuditLogActionType.StickerDelete or AuditLogActionType.StageInstanceDelete or AuditLogActionType.ThreadDelete or AuditLogActionType.SheduledEventDelete => AuditLogActionCategory.Delete, AuditLogActionType.ChannelUpdate or AuditLogActionType.EmojiUpdate or AuditLogActionType.InviteUpdate or AuditLogActionType.MemberRoleUpdate or AuditLogActionType.MemberUpdate or AuditLogActionType.OverwriteUpdate or AuditLogActionType.RoleUpdate or AuditLogActionType.WebhookUpdate or AuditLogActionType.IntegrationUpdate or AuditLogActionType.StickerUpdate or AuditLogActionType.StageInstanceUpdate or AuditLogActionType.ThreadUpdate or AuditLogActionType.SheduledEventUpdate => AuditLogActionCategory.Update, _ => AuditLogActionCategory.Other, }; entry.Discord = this.Discord; entry.ActionType = xac.ActionType; entry.Id = xac.Id; entry.Reason = xac.Reason; entry.UserResponsible = amd[xac.UserId]; entries.Add(entry); } return new ReadOnlyCollection(entries); } /// /// Gets all of this guild's custom emojis. /// /// All of this guild's custom emojis. /// Thrown when Discord is unable to process the request. public Task> GetEmojisAsync() => this.Discord.ApiClient.GetGuildEmojisAsync(this.Id); /// /// Gets this guild's specified custom emoji. /// /// ID of the emoji to get. /// The requested custom emoji. /// Thrown when Discord is unable to process the request. public Task GetEmojiAsync(ulong id) => this.Discord.ApiClient.GetGuildEmojiAsync(this.Id, id); /// /// Creates a new custom emoji for this guild. /// /// Name of the new emoji. /// Image to use as the emoji. /// Roles for which the emoji will be available. This works only if your application is whitelisted as integration. /// Reason for audit log. /// The newly-created emoji. /// Thrown when the client does not have the permission. /// Thrown when Discord is unable to process the request. public Task CreateEmojiAsync(string name, Stream image, IEnumerable roles = null, string reason = null) { if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name)); name = name.Trim(); if (name.Length < 2 || name.Length > 50) throw new ArgumentException("Emoji name needs to be between 2 and 50 characters long."); if (image == null) throw new ArgumentNullException(nameof(image)); string image64 = null; using (var imgtool = new ImageTool(image)) image64 = imgtool.GetBase64(); return this.Discord.ApiClient.CreateGuildEmojiAsync(this.Id, name, image64, roles?.Select(xr => xr.Id), reason); } /// /// Modifies a this guild's custom emoji. /// /// Emoji to modify. /// New name for the emoji. /// Roles for which the emoji will be available. This works only if your application is whitelisted as integration. /// Reason for audit log. /// The modified emoji. /// Thrown when the client does not have the permission. /// Thrown when Discord is unable to process the request. public Task ModifyEmojiAsync(DiscordGuildEmoji emoji, string name, IEnumerable roles = null, string reason = null) { if (emoji == null) throw new ArgumentNullException(nameof(emoji)); if (emoji.Guild.Id != this.Id) throw new ArgumentException("This emoji does not belong to this guild."); if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name)); name = name.Trim(); return name.Length < 2 || name.Length > 50 ? throw new ArgumentException("Emoji name needs to be between 2 and 50 characters long.") : this.Discord.ApiClient.ModifyGuildEmojiAsync(this.Id, emoji.Id, name, roles?.Select(xr => xr.Id), reason); } /// /// Deletes this guild's custom emoji. /// /// Emoji to delete. /// Reason for audit log. /// /// Thrown when the client does not have the permission. /// Thrown when Discord is unable to process the request. public Task DeleteEmojiAsync(DiscordGuildEmoji emoji, string reason = null) { return emoji == null ? throw new ArgumentNullException(nameof(emoji)) : emoji.Guild.Id != this.Id ? throw new ArgumentException("This emoji does not belong to this guild.") : this.Discord.ApiClient.DeleteGuildEmojiAsync(this.Id, emoji.Id, reason); } /// /// Gets all of this guild's custom stickers. /// /// All of this guild's custom stickers. /// Thrown when Discord is unable to process the request. public async Task> GetStickersAsync() { var stickers = await this.Discord.ApiClient.GetGuildStickersAsync(this.Id); foreach (var xstr in stickers) { this._stickers.AddOrUpdate(xstr.Id, xstr, (id, old) => { old.Name = xstr.Name; old.Description = xstr.Description; old._internalTags = xstr._internalTags; return old; }); } return stickers; } /// /// Gets a sticker /// /// Thrown when the sticker could not be found. /// Thrown when the client does not have the permission. /// Thrown when Discord is unable to process the request. /// Sticker does not belong to a guild. public Task GetStickerAsync(ulong sticker_id) => this.Discord.ApiClient.GetGuildStickerAsync(this.Id, sticker_id); /// /// Creates a sticker /// /// The name of the sticker. /// The optional description of the sticker. /// The emoji to associate the sticker with. /// The file format the sticker is written in. /// The sticker. /// Audit log reason /// Thrown when the client does not have the permission. /// Thrown when Discord is unable to process the request. public Task CreateStickerAsync(string name, string description, DiscordEmoji emoji, Stream file, StickerFormat format, string reason = null) { var fileExt = format switch { StickerFormat.PNG => "png", StickerFormat.APNG => "png", StickerFormat.LOTTIE => "json", _ => throw new InvalidOperationException("This format is not supported.") }; var contentType = format switch { StickerFormat.PNG => "image/png", StickerFormat.APNG => "image/png", StickerFormat.LOTTIE => "application/json", _ => throw new InvalidOperationException("This format is not supported.") }; return emoji.Id is not 0 ? throw new InvalidOperationException("Only unicode emoji can be used for stickers.") : name.Length < 2 || name.Length > 30 ? throw new ArgumentOutOfRangeException(nameof(name), "Sticker name needs to be between 2 and 30 characters long.") : description.Length < 1 || description.Length > 100 ? throw new ArgumentOutOfRangeException(nameof(description), "Sticker description needs to be between 1 and 100 characters long.") : this.Discord.ApiClient.CreateGuildStickerAsync(this.Id, name, description, emoji.GetDiscordName().Replace(":", ""), new("sticker", file, null, fileExt, contentType), reason); } /// /// Modifies a sticker /// /// The id of the sticker to modify /// The name of the sticker /// The description of the sticker /// The emoji to associate with this sticker. /// Audit log reason /// A sticker object /// Thrown when the sticker could not be found. /// Thrown when the client does not have the permission. /// Thrown when Discord is unable to process the request. /// Sticker does not belong to a guild. public async Task ModifyStickerAsync(ulong sticker, Optional name, Optional description, Optional emoji, string reason = null) { string uemoji = null; if (!this._stickers.TryGetValue(sticker, out var stickerobj) || stickerobj.Guild.Id != this.Id) throw new ArgumentException("This sticker does not belong to this guild."); if (name.HasValue && (name.Value.Length < 2 || name.Value.Length > 30)) throw new ArgumentException("Sticker name needs to be between 2 and 30 characters long."); if (description.HasValue && (description.Value.Length < 1 || description.Value.Length > 100)) throw new ArgumentException("Sticker description needs to be between 1 and 100 characters long."); if (emoji.HasValue && emoji.Value.Id > 0) throw new ArgumentException("Only unicode emojis can be used with stickers."); else if (emoji.HasValue) uemoji = emoji.Value.GetDiscordName().Replace(":", ""); var usticker = await this.Discord.ApiClient.ModifyGuildStickerAsync(this.Id, sticker, name, description, uemoji, reason).ConfigureAwait(false); if (this._stickers.TryGetValue(usticker.Id, out var old)) this._stickers.TryUpdate(usticker.Id, usticker, old); return usticker; } /// /// Modifies a sticker /// /// The sticker to modify /// The name of the sticker /// The description of the sticker /// The emoji to associate with this sticker. /// Audit log reason /// A sticker object /// Thrown when the sticker could not be found. /// Thrown when the client does not have the permission. /// Thrown when Discord is unable to process the request. /// Sticker does not belong to a guild. public Task ModifyStickerAsync(DiscordSticker sticker, Optional name, Optional description, Optional emoji, string reason = null) => this.ModifyStickerAsync(sticker.Id, name, description, emoji, reason); /// /// Deletes a sticker /// /// Id of sticker to delete /// Audit log reason /// Thrown when the sticker could not be found. /// Thrown when the client does not have the permission. /// Thrown when Discord is unable to process the request. /// Sticker does not belong to a guild. public Task DeleteStickerAsync(ulong sticker, string reason = null) { return !this._stickers.TryGetValue(sticker, out var stickerobj) ? throw new ArgumentNullException(nameof(sticker)) : stickerobj.Guild.Id != this.Id ? throw new ArgumentException("This sticker does not belong to this guild.") : this.Discord.ApiClient.DeleteGuildStickerAsync(this.Id, sticker, reason); } /// /// Deletes a sticker /// /// Sticker to delete /// Audit log reason /// Thrown when the sticker could not be found. /// Thrown when the client does not have the permission. /// Thrown when Discord is unable to process the request. /// Sticker does not belong to a guild. public Task DeleteStickerAsync(DiscordSticker sticker, string reason = null) => this.DeleteStickerAsync(sticker.Id, reason); /// /// Gets the default channel for this guild. /// Default channel is the first channel current member can see. /// /// This member's default guild. /// Thrown when Discord is unable to process the request. public DiscordChannel GetDefaultChannel() { return this._channels?.Values.Where(xc => xc.Type == ChannelType.Text) .OrderBy(xc => xc.Position) .FirstOrDefault(xc => (xc.PermissionsFor(this.CurrentMember) & DisCatSharp.Permissions.AccessChannels) == DisCatSharp.Permissions.AccessChannels); } /// /// Gets the guild's widget /// /// The guild's widget public Task GetWidgetAsync() => this.Discord.ApiClient.GetGuildWidgetAsync(this.Id); /// /// Gets the guild's widget settings /// /// The guild's widget settings public Task GetWidgetSettingsAsync() => this.Discord.ApiClient.GetGuildWidgetSettingsAsync(this.Id); /// /// Modifies the guild's widget settings /// /// If the widget is enabled or not /// Widget channel /// Reason the widget settings were modified /// The newly modified widget settings public Task ModifyWidgetSettingsAsync(bool? isEnabled = null, DiscordChannel channel = null, string reason = null) => this.Discord.ApiClient.ModifyGuildWidgetSettingsAsync(this.Id, isEnabled, channel?.Id, reason); /// /// Gets all of this guild's templates. /// /// All of the guild's templates. /// Throws when the client does not have the permission. /// Thrown when Discord is unable to process the request. public Task> GetTemplatesAsync() => this.Discord.ApiClient.GetGuildTemplatesAsync(this.Id); /// /// Creates a guild template. /// /// Name of the template. /// Description of the template. /// The template created. /// Throws when a template already exists for the guild or a null parameter is provided for the name. /// Throws when the client does not have the permission. /// Thrown when Discord is unable to process the request. public Task CreateTemplateAsync(string name, string description = null) => this.Discord.ApiClient.CreateGuildTemplateAsync(this.Id, name, description); /// /// Syncs the template to the current guild's state. /// /// The code of the template to sync. /// The template synced. /// Throws when the template for the code cannot be found /// Throws when the client does not have the permission. /// Thrown when Discord is unable to process the request. public Task SyncTemplateAsync(string code) => this.Discord.ApiClient.SyncGuildTemplateAsync(this.Id, code); /// /// Modifies the template's metadata. /// /// The template's code. /// Name of the template. /// Description of the template. /// The template modified. /// Throws when the template for the code cannot be found /// Throws when the client does not have the permission. /// Thrown when Discord is unable to process the request. public Task ModifyTemplateAsync(string code, string name = null, string description = null) => this.Discord.ApiClient.ModifyGuildTemplateAsync(this.Id, code, name, description); /// /// Deletes the template. /// /// The code of the template to delete. /// The deleted template. /// Throws when the template for the code cannot be found /// Throws when the client does not have the permission. /// Thrown when Discord is unable to process the request. public Task DeleteTemplateAsync(string code) => this.Discord.ApiClient.DeleteGuildTemplateAsync(this.Id, code); /// /// Gets this guild's membership screening form. /// /// This guild's membership screening form. /// Thrown when Discord is unable to process the request. public Task GetMembershipScreeningFormAsync() => this.Discord.ApiClient.GetGuildMembershipScreeningFormAsync(this.Id); /// /// Modifies this guild's membership screening form. /// /// Action to perform /// The modified screening form. /// Thrown when the client doesn't have the permission, or community is not enabled on this guild. /// Thrown when Discord is unable to process the request. public async Task ModifyMembershipScreeningFormAsync(Action action) { var mdl = new MembershipScreeningEditModel(); action(mdl); return await this.Discord.ApiClient.ModifyGuildMembershipScreeningFormAsync(this.Id, mdl.Enabled, mdl.Fields, mdl.Description); } /// /// Gets all the application commands in this guild. /// /// A list of application commands in this guild. public Task> GetApplicationCommandsAsync() => this.Discord.ApiClient.GetGuildApplicationCommandsAsync(this.Discord.CurrentApplication.Id, this.Id); /// /// Overwrites the existing application commands in this guild. New commands are automatically created and missing commands are automatically delete /// /// The list of commands to overwrite with. /// The list of guild commands public Task> BulkOverwriteApplicationCommandsAsync(IEnumerable commands) => this.Discord.ApiClient.BulkOverwriteGuildApplicationCommandsAsync(this.Discord.CurrentApplication.Id, this.Id, commands); /// /// Creates or overwrites a application command in this guild. /// /// The command to create. /// The created command. public Task CreateApplicationCommandAsync(DiscordApplicationCommand command) => this.Discord.ApiClient.CreateGuildApplicationCommandAsync(this.Discord.CurrentApplication.Id, this.Id, command); /// /// Edits a application command in this guild. /// /// The id of the command to edit. /// Action to perform. /// The edit command. public async Task EditApplicationCommandAsync(ulong commandId, Action action) { var mdl = new ApplicationCommandEditModel(); action(mdl); return await this.Discord.ApiClient.EditGuildApplicationCommandAsync(this.Discord.CurrentApplication.Id, this.Id, commandId, mdl.Name, mdl.Description, mdl.Options, mdl.DefaultPermission).ConfigureAwait(false); } /// /// Gets this guild's welcome screen. /// /// This guild's welcome screen object. /// Thrown when Discord is unable to process the request. public Task GetWelcomeScreenAsync() => this.Discord.ApiClient.GetGuildWelcomeScreenAsync(this.Id); /// /// Modifies this guild's welcome screen. /// /// Action to perform. /// The modified welcome screen. /// Thrown when the client doesn't have the permission, or community is not enabled on this guild. /// Thrown when Discord is unable to process the request. public async Task ModifyWelcomeScreenAsync(Action action) { var mdl = new WelcomeScreenEditModel(); action(mdl); return await this.Discord.ApiClient.ModifyGuildWelcomeScreenAsync(this.Id, mdl.Enabled, mdl.WelcomeChannels, mdl.Description).ConfigureAwait(false); } #endregion /// /// Returns a string representation of this guild. /// /// String representation of this guild. public override string ToString() => $"Guild {this.Id}; {this.Name}"; /// /// Checks whether this is equal to another object. /// /// Object to compare to. /// Whether the object is equal to this . public override bool Equals(object obj) => this.Equals(obj as DiscordGuild); /// /// Checks whether this is equal to another . /// /// to compare to. /// Whether the is equal to this . public bool Equals(DiscordGuild e) => e is not null && (ReferenceEquals(this, e) || this.Id == e.Id); /// /// Gets the hash code for this . /// /// The hash code for this . public override int GetHashCode() => this.Id.GetHashCode(); /// /// Gets whether the two objects are equal. /// /// First guild to compare. /// Second guild to compare. /// Whether the two guilds are equal. public static bool operator ==(DiscordGuild e1, DiscordGuild e2) { var o1 = e1 as object; var o2 = e2 as object; return (o1 != null || o2 == null) && (o1 == null || o2 != null) && ((o1 == null && o2 == null) || e1.Id == e2.Id); } /// /// Gets whether the two objects are not equal. /// /// First guild to compare. /// Second guild to compare. /// Whether the two guilds are not equal. public static bool operator !=(DiscordGuild e1, DiscordGuild e2) => !(e1 == e2); } /// /// Represents guild verification level. /// public enum VerificationLevel : int { /// /// No verification. Anyone can join and chat right away. /// None = 0, /// /// Low verification level. Users are required to have a verified email attached to their account in order to be able to chat. /// Low = 1, /// /// Medium verification level. Users are required to have a verified email attached to their account, and account age need to be at least 5 minutes in order to be able to chat. /// Medium = 2, /// /// High verification level. Users are required to have a verified email attached to their account, account age need to be at least 5 minutes, and they need to be in the server for at least 10 minutes in order to be able to chat. /// High = 3, /// /// Highest verification level. Users are required to have a verified phone number attached to their account. /// Highest = 4 } /// /// Represents default notification level for a guild. /// public enum DefaultMessageNotifications : int { /// /// All messages will trigger push notifications. /// AllMessages = 0, /// /// Only messages that mention the user (or a role he's in) will trigger push notifications. /// MentionsOnly = 1 } /// /// Represents multi-factor authentication level required by a guild to use administrator functionality. /// public enum MfaLevel : int { /// /// Multi-factor authentication is not required to use administrator functionality. /// Disabled = 0, /// /// Multi-factor authentication is required to use administrator functionality. /// Enabled = 1 } /// /// Represents the value of explicit content filter in a guild. /// public enum ExplicitContentFilter : int { /// /// Explicit content filter is disabled. /// Disabled = 0, /// /// Only messages from members without any roles are scanned. /// MembersWithoutRoles = 1, /// /// Messages from all members are scanned. /// AllMembers = 2 } /// /// Represents the formats for a guild widget. /// public enum WidgetType : int { /// /// The widget is represented in shield format. /// This is the default widget type. /// Shield = 0, /// /// The widget is represented as the first banner type. /// Banner1 = 1, /// /// The widget is represented as the second banner type. /// Banner2 = 2, /// /// The widget is represented as the third banner type. /// Banner3 = 3, /// /// The widget is represented in the fourth banner type. /// Banner4 = 4 } /// /// Represents the guild features. /// public class GuildFeatures { /// /// Guild has access to set an animated guild icon. /// public bool CanSetAnimatedIcon { get; } /// /// Guild has access to set a guild banner image. /// public bool CanSetBanner { get; } /// /// Guild has access to use commerce features (i.e. create store channels) /// public bool CanCreateStoreChannels { get; } /// /// Guild can enable Welcome Screen, Membership Screening, Stage Channels, News Channels and receives community updates. /// Furthermore the guild can apply as a partner and for the discovery (if the prerequisites are given). /// and is usable. /// public bool HasCommunityEnabled { get; } /// /// Guild is able to be discovered in the discovery. /// public bool IsDiscoverable { get; } /// /// Guild is able to be featured in the discovery. /// public bool IsFeatureable { get; } /// /// Guild has access to set an invite splash background. /// public bool CanSetInviteSplash { get; } /// /// Guild has enabled Membership Screening. /// public bool HasMembershipScreeningEnabled { get; } /// /// Guild has access to create news channels. /// is usable. /// public bool CanCreateNewsChannels { get; } /// /// Guild is partnered. /// public bool IsPartnered { get; } /// /// Guild has increased custom emoji slots. /// public bool CanUploadMoreEmojis { get; } /// /// Guild can be previewed before joining via Membership Screening or the discovery. /// public bool HasPreviewEnabled { get; } /// /// Guild has access to set a vanity URL. /// public bool CanSetVanityUrl { get; } /// /// Guild is verified. /// public bool IsVerified { get; } /// /// Guild has access to set 384kbps bitrate in voice (previously VIP voice servers). /// public bool CanAccessVipRegions { get; } /// /// Guild has enabled the welcome screen. /// public bool HasWelcomeScreenEnabled { get; } /// /// Guild has enabled ticketed events. /// public bool HasTicketedEventsEnabled { get; } /// /// Guild has enabled monetization. /// public bool HasMonetizationEnabled { get; } /// /// Guild has increased custom sticker slots. /// public bool CanUploadMoreStickers { get; } /// /// Guild has access to the three day archive time for threads. /// Needs Premium Tier 1 (). /// public bool CanSetThreadArchiveDurationThreeDays { get; } /// /// Guild has access to the seven day archive time for threads. /// Needs Premium Tier 2 (). /// public bool CanSetThreadArchiveDurationSevenDays { get; } /// /// Guild has access to create private threads. /// Needs Premium Tier 2 (). /// public bool CanCreatePrivateThreads { get; } /// /// Guild is a hub. /// is usable. /// public bool IsHub { get; } /// /// Guild has full access to threads. + /// Old Feature. /// public bool HasThreadTestingEnabled { get; } /// /// Guild has access to threads. /// public bool HasThreadsEnabled { get; } /// /// Guild can set role icons. /// public bool CanSetRoleIcons { get; } /// /// Guild has the new thread permissions. + /// Old Feature. /// public bool HasNewThreadPermissions { get; } - - + + /// + /// Guild can set thread default auto archive duration. + /// Old Feature. + /// + public bool CanSetThreadDefaultAutoArchiveDuration { get; } + /// /// Guild has enabled role subsriptions. /// public bool HasRoleSubscriptionsEnabled { get; } /// /// Guild has premium tier 3 override. /// public bool PremiumTierThreeOverride { get; } + /// + /// Guild has access to text in voice. + /// + public bool TextInVoiceEnabled { get; } + /// /// String of guild features. /// public string FeatureString { get; } /// /// Checks the guild features and constructs a new object. /// /// Guild to check public GuildFeatures(DiscordGuild guild) { this.CanSetAnimatedIcon = guild.RawFeatures.Contains("ANIMATED_ICON"); this.CanSetBanner = guild.RawFeatures.Contains("BANNER"); this.CanCreateStoreChannels = guild.RawFeatures.Contains("COMMERCE"); this.HasCommunityEnabled = guild.RawFeatures.Contains("COMMUNITY"); this.IsDiscoverable = !guild.RawFeatures.Contains("DISCOVERABLE_DISABLED") && guild.RawFeatures.Contains("DISCOVERABLE"); this.IsFeatureable = guild.RawFeatures.Contains("FEATURABLE"); this.CanSetInviteSplash = guild.RawFeatures.Contains("INVITE_SPLASH"); this.HasMembershipScreeningEnabled = guild.RawFeatures.Contains("MEMBER_VERIFICATION_GATE_ENABLED"); this.CanCreateNewsChannels = guild.RawFeatures.Contains("NEWS"); this.IsPartnered = guild.RawFeatures.Contains("PARTNERED"); this.CanUploadMoreEmojis = guild.RawFeatures.Contains("MORE_EMOJI"); this.HasPreviewEnabled = guild.RawFeatures.Contains("PREVIEW_ENABLED"); this.CanSetVanityUrl = guild.RawFeatures.Contains("VANITY_URL"); this.IsVerified = guild.RawFeatures.Contains("VERIFIED"); this.CanAccessVipRegions = guild.RawFeatures.Contains("VIP_REGIONS"); this.HasWelcomeScreenEnabled = guild.RawFeatures.Contains("WELCOME_SCREEN_ENABLED"); this.HasTicketedEventsEnabled = guild.RawFeatures.Contains("TICKETED_EVENTS_ENABLED"); this.HasMonetizationEnabled = guild.RawFeatures.Contains("MONETIZATION_ENABLED"); this.CanUploadMoreStickers = guild.RawFeatures.Contains("MORE_STICKERS"); this.CanSetThreadArchiveDurationThreeDays = guild.RawFeatures.Contains("THREE_DAY_THREAD_ARCHIVE"); this.CanSetThreadArchiveDurationSevenDays = guild.RawFeatures.Contains("SEVEN_DAY_THREAD_ARCHIVE"); this.CanCreatePrivateThreads = guild.RawFeatures.Contains("PRIVATE_THREADS"); this.IsHub = guild.RawFeatures.Contains("HUB"); this.HasThreadTestingEnabled = guild.RawFeatures.Contains("THREADS_ENABLED_TESTING"); this.HasThreadsEnabled = guild.RawFeatures.Contains("THREADS_ENABLED"); this.CanSetRoleIcons = guild.RawFeatures.Contains("ROLE_ICONS"); this.HasNewThreadPermissions = guild.RawFeatures.Contains("NEW_THREAD_PERMISSIONS"); this.HasRoleSubscriptionsEnabled = guild.RawFeatures.Contains("ROLE_SUBSCRIPTIONS_ENABLED"); this.PremiumTierThreeOverride = guild.RawFeatures.Contains("PREMIUM_TIER_3_OVERRIDE"); + this.CanSetThreadDefaultAutoArchiveDuration = guild.RawFeatures.Contains("THREAD_DEFAULT_AUTO_ARCHIVE_DURATION"); + this.TextInVoiceEnabled = guild.RawFeatures.Contains("TEXT_IN_VOICE_ENABLED"); var _features = guild.RawFeatures.Any() ? "" : "NONE"; foreach (var feature in guild.RawFeatures) { _features += feature + " "; } this.FeatureString = _features; } } } diff --git a/DisCatSharp/Enums/Invite/TargetActivity.cs b/DisCatSharp/Enums/Invite/TargetActivity.cs index 7968e514e..55f5626f2 100644 --- a/DisCatSharp/Enums/Invite/TargetActivity.cs +++ b/DisCatSharp/Enums/Invite/TargetActivity.cs @@ -1,84 +1,89 @@ // This file is part of the DisCatSharp project. // // Copyright (c) 2021 AITSYS // // 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. namespace DisCatSharp { /// /// Represents the activity this invite is for. /// public enum TargetActivity : ulong { /// /// Represents no embedded application. /// None = 0, /// /// Represents the embedded application Poker Night. /// PokerNight = 755827207812677713, /// /// Represents the embedded application Betrayal.io. /// Betrayal = 773336526917861400, /// /// Represents the embedded application Fishington.io. /// Fishington = 814288819477020702, /// /// Represents the embedded application YouTube Together. /// YouTubeTogether = 755600276941176913, /// /// Represents the embedded application Chess in the park. /// Dev? /// ChessInThePark = 832012586023256104, /// /// Represents the embedded application Chess in the park. /// This is another version. /// Stable? /// ChessInThePark2 = 832012774040141894, /// /// Represents the embedded application YouTube Together. /// This is the new (staff only) version. /// YouTubeTogetherV2 = 880218394199220334, /// /// Represents the embedded application Letter Tile. /// LetterTile = 879863686565621790, /// /// Represents the embedded application Word Snacks. /// - WordSnacks = 879863976006127627 + WordSnacks = 879863976006127627, + + /// + /// Represents the embedded application Doodle Crew. + /// + DoodleCrew = 878067389634314250 } } diff --git a/DisCatSharp/Enums/User/UserFlags.cs b/DisCatSharp/Enums/User/UserFlags.cs index a7a3e8b84..5d00b52ee 100644 --- a/DisCatSharp/Enums/User/UserFlags.cs +++ b/DisCatSharp/Enums/User/UserFlags.cs @@ -1,133 +1,138 @@ // This file is part of the DisCatSharp project. // // Copyright (c) 2021 AITSYS // // 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. using System; namespace DisCatSharp { /// /// Represents additional details of a users account. /// [Flags] public enum UserFlags { /// /// The user has no flags. /// None = 0, /// /// The user is a Discord employee. /// DiscordEmployee = 1 << 0, /// /// The user is a Discord partner. /// DiscordPartner = 1 << 1, /// /// The user has the HypeSquad badge. /// HypeSquadEvents = 1 << 2, /// /// The user reached the first bug hunter tier. /// BugHunterLevelOne = 1 << 3, /// /// The user has SMS recovery for 2FA enabled. /// MfaSms = 1 << 4, /// /// The user is marked as dismissed Nitro promotion /// PremiumPromoDismissed = 1 << 5, /// /// The user is a member of house bravery. /// HouseBravery = 1 << 6, /// /// The user is a member of house brilliance. /// HouseBrilliance = 1 << 7, /// /// The user is a member of house balance. /// HouseBalance = 1 << 8, /// /// The user has the early supporter badge. /// EarlySupporter = 1 << 9, /// /// Whether the user is apart of a Discord developer team. /// TeamUser = 1 << 10, /// /// Relates to partner/verification applications.. /// PartnerOrVerificationApplication = 1 << 11, /// /// Whether the user is an official system user. /// System = 1 << 12, /// /// Whether the user has unread system messages.. /// HasUnreadUrgentMessages = 1 << 13, /// /// The user reached the second bug hunter tier. /// BugHunterLevelTwo = 1 << 14, /// /// The user has a pending deletion for being underage in DOB prompt. /// UnderageDeleted = 1 << 15, /// /// The user is a verified bot. /// VerifiedBot = 1 << 16, /// /// The user is a verified bot developer. /// VerifiedBotDeveloper = 1 << 17, /// /// The user is a discord certified moderator. /// - DiscordCertifiedModerator = 1 << 18 + DiscordCertifiedModerator = 1 << 18, + + /// + /// The user is a bot http interaction. + /// + BotHttpInteractions = 1 << 19 } } diff --git a/DisCatSharp/Net/Rest/Endpoints.cs b/DisCatSharp/Net/Rest/Endpoints.cs index 15f379421..342a09727 100644 --- a/DisCatSharp/Net/Rest/Endpoints.cs +++ b/DisCatSharp/Net/Rest/Endpoints.cs @@ -1,370 +1,378 @@ // This file is part of the DisCatSharp project. // // Copyright (c) 2021 AITSYS // // 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. using DisCatSharp; namespace DisCatSharp.Net { /// /// The discord endpoints. /// internal static class Endpoints { /// /// The base discord api uri. /// public const string BASE_URI = "https://discord.com/api/v9"; /// /// The oauth2 endpoint. /// public const string OAUTH2 = "/oauth2"; /// /// The oauth2 authorize endpoint. /// public const string AUTHORIZE = "/authorize"; /// /// The applications endpoint. /// public const string APPLICATIONS = "/applications"; /// /// The message reactions endpoint. /// public const string REACTIONS = "/reactions"; /// /// The self (@me) endpoint. /// public const string ME = "/@me"; /// /// The @original endpoint. /// public const string ORIGINAL = "/@original"; /// /// The permissions endpoint. /// public const string PERMISSIONS = "/permissions"; /// /// The recipients endpoint. /// public const string RECIPIENTS = "/recipients"; /// /// The bulk-delete endpoint. /// public const string BULK_DELETE = "/bulk-delete"; /// /// The integrations endpoint. /// public const string INTEGRATIONS = "/integrations"; /// /// The applications endpoint. /// public const string SYNC = "/sync"; /// /// The prune endpoint. /// Used for user removal. /// public const string PRUNE = "/prune"; /// /// The regions endpoint. /// public const string REGIONS = "/regions"; /// /// The connections endpoint. /// public const string CONNECTIONS = "/connections"; /// /// The icons endpoint. /// public const string ICONS = "/icons"; /// /// The gateway endpoint. /// public const string GATEWAY = "/gateway"; /// /// The oauth2 auth endpoint. /// public const string AUTH = "/auth"; /// /// The oauth2 login endpoint. /// public const string LOGIN = "/login"; /// /// The channels endpoint. /// public const string CHANNELS = "/channels"; /// /// The messages endpoint. /// public const string MESSAGES = "/messages"; /// /// The pinned messages endpoint. /// public const string PINS = "/pins"; /// /// The users endpoint. /// public const string USERS = "/users"; /// /// The guilds endpoint. /// public const string GUILDS = "/guilds"; /// /// The guild discovery splash endpoint. /// public const string GUILD_DISCOVERY_SPLASHES = "/discovery-splashes"; /// /// The guild splash endpoint. /// public const string SPLASHES = "/splashes"; /// /// The search endpoint. /// public const string SEARCH = "/search"; /// /// The invites endpoint. /// public const string INVITES = "/invites"; /// /// The roles endpoint. /// public const string ROLES = "/roles"; /// /// The members endpoint. /// public const string MEMBERS = "/members"; /// /// The typing endpoint. /// Triggers a typing indicator inside a channel. /// public const string TYPING = "/typing"; /// /// The avatars endpoint. /// public const string AVATARS = "/avatars"; /// /// The bans endpoint. /// public const string BANS = "/bans"; /// /// The webhook endpoint. /// public const string WEBHOOKS = "/webhooks"; /// /// The slack endpoint. /// Used for . /// public const string SLACK = "/slack"; /// /// The github endpoint. /// Used for . /// public const string GITHUB = "/github"; /// /// The bot endpoint. /// public const string BOT = "/bot"; /// /// The voice endpoint. /// public const string VOICE = "/voice"; /// /// The audit logs endpoint. /// public const string AUDIT_LOGS = "/audit-logs"; /// /// The acknowledge endpoint. /// Indicates that a message is read. /// public const string ACK = "/ack"; /// /// The nickname endpoint. /// public const string NICK = "/nick"; /// /// The assets endpoint. /// public const string ASSETS = "/assets"; /// /// The embed endpoint. /// public const string EMBED = "/embed"; /// /// The emojis endpoint. /// public const string EMOJIS = "/emojis"; /// /// The vanity url endpoint. /// public const string VANITY_URL = "/vanity-url"; /// /// The guild preview endpoint. /// public const string PREVIEW = "/preview"; /// /// The followers endpoint. /// public const string FOLLOWERS = "/followers"; /// /// The crosspost endpoint. /// public const string CROSSPOST = "/crosspost"; /// /// The guild widget endpoint. /// public const string WIDGET = "/widget"; /// /// The guild widget json endpoint. /// public const string WIDGET_JSON = "/widget.json"; /// /// The guild widget png endpoint. /// public const string WIDGET_PNG = "/widget.png"; /// /// The templates endpoint. /// public const string TEMPLATES = "/templates"; /// /// The member verification gate endpoint. /// public const string MEMBER_VERIFICATION = "/member-verification"; /// /// The slash commands endpoint. /// public const string COMMANDS = "/commands"; /// /// The interactions endpoint. /// public const string INTERACTIONS = "/interactions"; /// /// The interaction/command callback endpoint. /// public const string CALLBACK = "/callback"; /// /// The welcome screen endpoint. /// public const string WELCOME_SCREEN = "/welcome-screen"; /// /// The voice states endpoint. /// public const string VOICE_STATES = "/voice-states"; /// /// The stage instances endpoint. /// public const string STAGE_INSTANCES = "/stage-instances"; /// /// The threads endpoint. /// public const string THREADS = "/threads"; /// /// The public threads endpoint. /// public const string THREAD_PUBLIC = "/public"; /// /// The private threads endpoint. /// public const string THREAD_PRIVATE = "/private"; /// /// The active threads endpoint. /// public const string THREAD_ACTIVE = "/active"; /// /// The archived threads endpoint. /// public const string THREAD_ARCHIVED = "/archived"; /// /// The thread members endpoint. /// public const string THREAD_MEMBERS = "/thread-members"; /// /// The guild events endpoint. /// Used for stage events. /// public const string EVENTS = "/events"; /// /// The stickers endpoint. /// public const string STICKERS = "/stickers"; /// /// The sticker packs endpoint. /// Global nitro sticker packs. /// public const string STICKERPACKS = "/sticker-packs"; /// /// The store endpoint. /// public const string STORE = "/store"; /// /// The app assets endpoint. /// public const string APP_ASSETS = "/app-assets"; /// /// The app icons endpoint. /// public const string APP_ICONS = "/app-icons"; /// /// The team icons endpoint. /// public const string TEAM_ICONS = "/team-icons"; /// /// The channel icons endpoint. /// public const string CHANNEL_ICONS = "/channel-icons"; /// /// The user banners endpoint. /// public const string BANNERS = "/banners"; /// /// The sticker endpoint. /// This endpoint is the static nitro sticker application. /// public const string STICKER_APPLICATION = "/710982414301790216"; /// /// The role subscription endpoint. /// public const string ROLE_SUBSCRIPTIONS = "/role-subscriptions"; /// /// The group listings endpoint. /// public const string GROUP_LISTINGS = "/group-listings"; /// /// The subscription listings endpoint. /// public const string SUBSCRIPTION_LISTINGS = "/subscription-listings"; /// /// The directory entries endpoint. /// public const string DIRECTORY_ENTRIES = "/directory-entries"; /// /// The counts endpoint. /// public const string COUNTS = "/counts"; /// /// The list endpoint. /// public const string LIST = "/list"; /// /// The role icons endpoint. /// public const string ROLE_ICONS = "/role-icons"; + /// + /// The activities endpoint. + /// + public const string ACTIVITIES = "/activities "; + /// + /// The config endpoint. + /// + public const string CONFIG = "/config"; } } diff --git a/README.md b/README.md index a2dbafbfa..f421589bd 100644 --- a/README.md +++ b/README.md @@ -1,84 +1,90 @@ # DisCatSharp [![GitHub](https://img.shields.io/github/license/Aiko-IT-Systems/DisCatSharp?label=License)](https://github.com/Aiko-IT-Systems/DisCatSharp/blob/main/LICENSE.md) [![Sponsors](https://img.shields.io/github/sponsors/Lulalaby?label=Sponsors)](https://github.com/sponsors/Lulalaby) [![Discord Server](https://img.shields.io/discord/858089281214087179.svg?label=Discord)](https://discord.gg/discatsharp) ![oie_u4NkggjBd6fc](https://user-images.githubusercontent.com/14029133/133850667-11872a7b-1dad-4a47-baab-aad2ecfc29d5.jpg) Discord Bot Library written in C# for .NET. https://discord.gg/discatsharp #### Status [![NuGet](https://img.shields.io/nuget/v/DisCatSharp.svg?label=NuGet%20Overall%20Version)](https://nuget.dcs.aitsys.dev) [![Build status](https://ci.appveyor.com/api/projects/status/fy4xn9s3cq7j30j7/branch/main?svg=true)](https://ci.appveyor.com/project/AITSYS/discatsharp/branch/main) #### Commit Activities [![GitHub last commit](https://img.shields.io/github/last-commit/Aiko-IT-Systems/DisCatSharp?label=Last%20Commit)](https://aitsys.dev/source/DisCatSharp/history/) [![GitHub commit activity](https://img.shields.io/github/commit-activity/w/Aiko-IT-Systems/DisCatSharp?label=Commit%20Activity)](https://github.com/Aiko-IT-Systems/DisCatSharp/commits/main) +[![wakatime](https://wakatime.com/badge/github/Aiko-IT-Systems/DisCatSharp.svg)](https://wakatime.com/badge/github/Aiko-IT-Systems/DisCatSharp) #### Stats [![GitHub pull requests](https://img.shields.io/github/issues-pr/Aiko-IT-Systems/DisCatSharp?label=PRs)](https://github.com/Aiko-IT-Systems/DisCatSharp/pulls) [![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/Aiko-IT-Systems/DisCatSharp?label=Size)](#) [![GitHub contributors](https://img.shields.io/github/contributors/Aiko-IT-Systems/DisCatSharp)](https://github.com/Aiko-IT-Systems/DisCatSharp/graphs/contributors) [![GitHub Repo stars](https://img.shields.io/github/stars/Aiko-IT-Systems/DisCatSharp?label=Stars)](https://github.com/Aiko-IT-Systems/DisCatSharp/stargazers) +[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/5282/badge)](https://bestpractices.coreinfrastructure.org/projects/5282) +[![StackShare](http://img.shields.io/badge/tech-stack-0690fa.svg?style=flat)](https://stackshare.io/aiko-it-systems/discatsharp) ## Why DisCatSharp? We want the lib always up-to-date. The newest features are important for us. So the API version is always the newest, in the actual case `v9`. ## Where is the Changelog? On our guild! You find it in [this channel](https://discord.com/channels/858089281214087179/858099438580006913). ## Installing You can install the library from following source: The latest release is always available on [NuGet](https://nuget.dcs.aitsys.dev). ## Documentation The documentation for the latest stable version is available at [docs.dcs.aitsys.dev/lts](https://docs.dcs.aitsys.dev/lts). The documentation of the latest nightly versions is available at [docs.dcs.aitsys.dev](https://docs.dcs.aitsys.dev). ## Bugs or Feature requests? -Join our [support guild](https://discord.gg/discatsharp) and open a support ticket. All requests are tracked at [aitsys.dev](https://aitsys.dev). +Either join our [support guild](https://discord.gg/discatsharp) and open a support ticket. +Or write a mail to dcs@aitsys.dev. + +All requests are tracked at [aitsys.dev](https://aitsys.dev). ## Tutorials * [Howto](https://docs.dcs.aitsys.dev/articles/basics/bot_account.html) * [Examples](https://examples.dcs.aitsys.dev) ## Snippts [Snippets for Visual Studio](https://github.com/Aiko-IT-Systems/DisCatSharp.Snippets) ## Latest NuGet Packages Package|NuGet |--|--| DisCatSharp|[![NuGet](https://img.shields.io/nuget/v/DisCatSharp.svg?label=)](https://nuget.dcs.aitsys.dev/DisCatSharp) DisCatSharp.ApplicationCommands|[![NuGet](https://img.shields.io/nuget/v/DisCatSharp.ApplicationCommands.svg?label=)](https://nuget.dcs.aitsys.dev/DisCatSharp.ApplicationCommands) DisCatSharp.CommandsNext|[![NuGet](https://img.shields.io/nuget/v/DisCatSharp.CommandsNext.svg?label=)](https://nuget.dcs.aitsys.dev/DisCatSharp.CommandsNext) DisCatSharp.Common|[![NuGet](https://img.shields.io/nuget/v/DisCatSharp.Common.svg?label=)](https://nuget.dcs.aitsys.dev/DisCatSharp.Common) DisCatSharp.Interactivity|[![NuGet](https://img.shields.io/nuget/v/DisCatSharp.Interactivity.svg?label=)](https://nuget.dcs.aitsys.dev/DisCatSharp.Interactivity) DisCatSharp.Lavalink|[![NuGet](https://img.shields.io/nuget/v/DisCatSharp.Lavalink.svg?label=)](https://nuget.dcs.aitsys.dev/DisCatSharp.Lavalink) DisCatSharp.VoiceNext|[![NuGet](https://img.shields.io/nuget/v/DisCatSharp.VoiceNext.svg?label=)](https://nuget.dcs.aitsys.dev/DisCatSharp.VoiceNext) DisCatSharp.VoiceNext.Natives|[![NuGet](https://img.shields.io/nuget/v/DisCatSharp.VoiceNext.Natives.svg?label=)](https://nuget.dcs.aitsys.dev/DisCatSharp.VoiceNext.Natives) ## Outdated NuGet Packages Package|NuGet |--|--| DisCatSharp.SlashCommands|[![NuGet](https://img.shields.io/nuget/v/DisCatSharp.SlashCommands.svg?label=)](https://nuget.dcs.aitsys.dev/DisCatSharp.SlashCommands) ## Releasing To release a new version do the following steps: - Create locally a repo named `release/VERSION` (Don't forget to replace VERSION with the target version number) - Replace version number with the correct version in [appveyor.yml#L78](https://github.com/Aiko-IT-Systems/DisCatSharp/blob/main/appveyor.yml#L78) with the new release number and [appveyor.yml#L5](https://github.com/Aiko-IT-Systems/DisCatSharp/blob/main/appveyor.yml#L5) with the next-ahead release number. - Replace nuget version number in [Version.targets#L4](https://github.com/Aiko-IT-Systems/DisCatSharp/blob/main/Version.targets#L4) - Publish branch to GitHub - Wait for the CI/CD to complete. - Merge the branch into main and delete it afterwards ## Thanks Big thanks goes to the following people who helps us ♥️ - [Auros Nexus](https://github.com/Auros) - [Lunar Starstrum](https://github.com/OoLunar) - [Johannes](https://github.com/JMLutra) - [Geferon](https://github.com/geferon) - [Alice](https://github.com/mortAlice) ## Notice Feel free to re-use code in DSharpPlus. diff --git a/Version.targets b/Version.targets index 130df92fc..4df4a9685 100644 --- a/Version.targets +++ b/Version.targets @@ -1,21 +1,21 @@ - 9.8.3 + 9.8.4 $(VersionPrefix)-$(VersionSuffix)-$(BuildNumber) $(VersionPrefix).$(BuildNumber) $(VersionPrefix).$(BuildNumber) $(VersionPrefix)-$(VersionSuffix) $(VersionPrefix).0 $(VersionPrefix).0 $(VersionPrefix) $(VersionPrefix).0 $(VersionPrefix).0 diff --git a/appveyor.yml b/appveyor.yml index bbcc5ca54..b423c2543 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,142 +1,140 @@ - branches: only: - main - version: 9.8.3-nightly-{build} + version: 9.8.4-nightly-{build} pull_requests: do_not_increment_build_number: true skip_tags: true max_jobs: 1 image: Visual Studio 2019 clone_depth: 1 build_script: - ps: |- # Version number $BUILD_NUMBER = [int]$Env:APPVEYOR_BUILD_NUMBER $BUILD_SUFFIX = "nightly" # Branch $BRANCH = "$Env:APPVEYOR_REPO_BRANCH" $Env:DOCFX_SOURCE_BRANCH_NAME = "$BRANCH" # Output directory $Env:ARTIFACT_DIR = ".\artifacts" $dir = New-Item -type directory $env:ARTIFACT_DIR $dir = $dir.FullName # Verbosity Write-Host "Build: $BUILD_NUMBER / Branch: $BRANCH" Write-Host "Artifacts will be placed in: $dir" # Check if this is a PR if (-not $Env:APPVEYOR_PULL_REQUEST_NUMBER) { Write-Host "Commencing complete build" & .\rebuild-all.ps1 -ArtifactLocation "$dir" -Configuration "Release" -VersionSuffix "$BUILD_SUFFIX" -BuildNumber $BUILD_NUMBER & Remove-Item "$dir\*.symbols.nupkg" } else { Write-Host "Building from PR ($Env:APPVEYOR_PULL_REQUEST_NUMBER)" & .\rebuild-all.ps1 -ArtifactLocation "$dir" -Configuration "Release" -VersionSuffix "$BUILD_SUFFIX" -BuildNumber $BUILD_NUMBER & Remove-Item "$dir\*.symbols.nupkg" } artifacts: - path: artifacts\*.snupkg - path: artifacts\*.nupkg - path: artifacts\dcs-docs.tar.xz deploy: - provider: NuGet server: api_key: secure: eml4lPttwjBZg7WdwX3tbx34ZDNssgb2zwthatNbolRY0PnaCIswbuPClf9IWrw7 skip_symbols: false - provider: GitHub auth_token: secure: oMF8sv9mhVjO7pBctQOwlmfd5aHQ4hvMoVCz77bgO9+1zBQSelPHxk0bCVfXNCCp prerelease: true force_update: true - provider: NuGet server: https://nuget.pkg.github.com/Aiko-IT-Systems/index.json username: lulalaby api_key: secure: SBGo8KrGJ7t5wwMNHKD0WSzrQ+PLJbqXE3FtDH2yGkSrQewO+kzmwp/xGk5a84He skip_symbols: true on_success: - ps: Invoke-RestMethod https://raw.githubusercontent.com/DiscordHooks/appveyor-discord-webhook/master/send.ps1 -o send.ps1 - ps: ./send.ps1 success $env:WEBHOOK_URL on_failure: - ps: Invoke-RestMethod https://raw.githubusercontent.com/DiscordHooks/appveyor-discord-webhook/master/send.ps1 -o send.ps1 - ps: ./send.ps1 failure $env:WEBHOOK_URL # Releases - branches: only: - /release/ - version: 9.8.2 + version: 9.8.3 pull_requests: do_not_increment_build_number: true skip_tags: true max_jobs: 1 image: Visual Studio 2019 clone_depth: 1 build_script: - ps: |- # Version number $BUILD_NUMBER = [int]$Env:APPVEYOR_BUILD_NUMBER # Branch $BRANCH = "$Env:APPVEYOR_REPO_BRANCH" $Env:DOCFX_SOURCE_BRANCH_NAME = "$BRANCH" # Output directory $Env:ARTIFACT_DIR = ".\artifacts" $dir = New-Item -type directory $env:ARTIFACT_DIR $dir = $dir.FullName # Verbosity Write-Host "Build: $BUILD_NUMBER / Branch: $BRANCH" Write-Host "Artifacts will be placed in: $dir" # Check if this is a PR if (-not $Env:APPVEYOR_PULL_REQUEST_NUMBER) { - # Rebuild documentation Write-Host "Commencing complete build" - & .\rebuild-all.ps1 -ArtifactLocation "$dir" -Configuration "Release" -DocsPath ".\DisCatSharp.Docs" -DocsPackageName "dcs-docs" + & .\rebuild-all.ps1 -ArtifactLocation "$dir" -Configuration "Release" -VersionSuffix "$BUILD_SUFFIX" -BuildNumber $BUILD_NUMBER & Remove-Item "$dir\*.symbols.nupkg" } else { - # Skip documentation - Write-Host "Building from PR ($Env:APPVEYOR_PULL_REQUEST_NUMBER); skipping docs build" - & .\rebuild-all.ps1 -ArtifactLocation "$dir" -Configuration "Release" + Write-Host "Building from PR ($Env:APPVEYOR_PULL_REQUEST_NUMBER)" + & .\rebuild-all.ps1 -ArtifactLocation "$dir" -Configuration "Release" -VersionSuffix "$BUILD_SUFFIX" -BuildNumber $BUILD_NUMBER & Remove-Item "$dir\*.symbols.nupkg" } artifacts: - path: artifacts\*.snupkg - path: artifacts\*.nupkg - path: artifacts\dcs-docs.tar.xz deploy: - provider: NuGet server: api_key: secure: eml4lPttwjBZg7WdwX3tbx34ZDNssgb2zwthatNbolRY0PnaCIswbuPClf9IWrw7 skip_symbols: false - provider: GitHub auth_token: secure: oMF8sv9mhVjO7pBctQOwlmfd5aHQ4hvMoVCz77bgO9+1zBQSelPHxk0bCVfXNCCp prerelease: false force_update: true - provider: NuGet server: https://nuget.pkg.github.com/Aiko-IT-Systems/index.json username: lulalaby api_key: secure: SBGo8KrGJ7t5wwMNHKD0WSzrQ+PLJbqXE3FtDH2yGkSrQewO+kzmwp/xGk5a84He skip_symbols: true on_success: - ps: Invoke-RestMethod https://raw.githubusercontent.com/DiscordHooks/appveyor-discord-webhook/master/send.ps1 -o send.ps1 - ps: ./send.ps1 success $env:WEBHOOK_URL on_failure: - ps: Invoke-RestMethod https://raw.githubusercontent.com/DiscordHooks/appveyor-discord-webhook/master/send.ps1 -o send.ps1 - ps: ./send.ps1 failure $env:WEBHOOK_URL