diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index d858f382c5..723b0f9576 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "docfx": { - "version": "2.62.2", + "version": "2.76.0", "commands": [ "docfx" ] diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 3d1935b7a8..44296f3d9f 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -1,12 +1,11 @@ # Based on an issue template from the Discord API documentation. name: Bug Report description: A bug has been found in the library -title: "[Bug]: " labels: ["bug"] body: - type: markdown attributes: - value: "Before opening a new issue, please search existing issues: https://github.com/Nihlus/Remora.Discord/issues" + value: "Before opening a new issue, please search existing issues: https://github.com/Remora/Remora.Discord/issues?q=is%3Aissue+label%3Abug" - type: textarea id: description attributes: diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index ee82f665bb..b3df0ad525 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,8 @@ blank_issues_enabled: false contact_links: - name: Developer Documentation - url: https://nihlus.github.io/Remora.Discord/ - about: Need documentation and examples for the API? Head over to Discord's developer documentation. + url: https://remora.github.io/Remora.Discord/main/articles/intro.html + about: Need documentation and examples for the API? Head over to Remora.Discord's documentation. - name: Discord Server url: https://discord.gg/dyYmwashVs about: Need help with the library? Talk to us in our Discord server. diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml index bc6335999e..c540f66765 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.yml +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -1,11 +1,11 @@ # Based on an issue template from the Discord API documentation. name: Feature Request description: Suggestions for new or different behavior in the library -labels: ["feature request"] +labels: ["enhancement"] body: - type: markdown attributes: - value: "Before opening a new issue, please search existing issues: https://github.com/Nihlus/Remora.Discord/issues" + value: "Before opening a new issue, please search existing issues: https://github.com/Remora/Remora.Discord/issues?q=is%3Aissue+label%3Aenhancement" - type: textarea id: description attributes: @@ -33,4 +33,4 @@ body: label: Additional Details description: Is there anything else you can add about this feature request? validations: - required: false \ No newline at end of file + required: false diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 3294a7bd1f..828cce07fa 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -18,47 +18,59 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - with: - submodules: recursive - - - uses: actions/setup-dotnet@v2 - with: - dotnet-version: | - 6.0.x - 7.0.x - - - name: Build - run: | - dotnet restore - dotnet build -c Release --no-restore - - - name: Test - run: dotnet test -c Release --no-restore --no-build --verbosity minimal - - - name: Package - if: github.ref == 'refs/heads/main' && github.event_name == 'push' - run: dotnet pack -c Release --no-restore --no-build --version-suffix "github$GITHUB_RUN_ID" - - - uses: actions/upload-artifact@v3 - if: github.ref == 'refs/heads/main' && github.event_name == 'push' - with: - name: nupkg - path: nuget/* - - - name: Build Docs - if: github.event_name == 'push' - run: | - dotnet tool restore - cd ./docfx - sed -i -E "s/%APP_VERSION%/${GITHUB_REF_NAME}/" docfx_project/docfx.json - dotnet docfx docfx_project/docfx.json - - - uses: actions/upload-artifact@v3 - if: github.event_name == 'push' - with: - name: docfx-site - path: docfx/docfx_project/_site/ + - uses: actions/checkout@v4 + with: + submodules: recursive + + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: | + 6.0.x + 7.0.x + 8.0.x + + - name: Build + run: | + dotnet restore + dotnet build -c Release --no-restore + + - name: Test + run: dotnet test -c Release --no-restore --no-build --verbosity minimal + + - name: Inspect + uses: JetBrains/ReSharper-InspectCode@v0.3 + with: + tool-version: 2023.3.4 + solution: Remora.Discord.sln + build: false + no-build: true + telemetry-optout: true + + - name: Package + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + run: dotnet pack -c Release --no-restore --no-build --version-suffix "github$GITHUB_RUN_ID" + + - uses: actions/upload-artifact@v4 + with: + name: nupkg + path: nuget/* + + - name: Build Docs + if: github.event_name == 'push' + run: | + dotnet tool restore + cd ./docfx + sed -i -E "s/%APP_VERSION%/${GITHUB_REF_NAME}/" docfx_project/docfx.json + dotnet docfx docfx_project/docfx.json + + - uses: actions/upload-artifact@v4 + if: github.event_name == 'push' + with: + name: docfx-site + path: docfx/docfx_project/_site/ + + permissions: + security-events: write publish_docs: name: Publish Documentation @@ -71,12 +83,12 @@ jobs: steps: - name: Checkout triggering branch - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: base - name: Checkout gh-pages - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: gh-pages path: site @@ -85,7 +97,7 @@ jobs: run: bash base/docfx/scripts/prepare.sh - name: Download documentation artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: docfx-site path: site/${{ github.ref_name }} @@ -107,15 +119,17 @@ jobs: steps: - name: Download package artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: nupkg path: nuget # To ensure that the current version being pushed does not get pruned we prune first. - name: Prune packages older than 4 versions (new version is the 5th) - uses: smartsquaregmbh/delete-old-packages@v0.6.0 + uses: smartsquaregmbh/delete-old-packages@v0.8.0 with: + organization: Remora + type: nuget keep: 4 names: | Remora.Discord diff --git a/.idea/.idea.Remora.Discord/.idea/.gitignore b/.idea/.idea.Remora.Discord/.idea/.gitignore index bc9371e1b5..343d51bf13 100644 --- a/.idea/.idea.Remora.Discord/.idea/.gitignore +++ b/.idea/.idea.Remora.Discord/.idea/.gitignore @@ -11,3 +11,5 @@ # Datasource local storage ignored files /dataSources/ /dataSources.local.xml +# GitHub Copilot persisted chat sessions +/copilot/chatSessions diff --git a/.idea/.idea.Remora.Discord/.idea/deployment.xml b/.idea/.idea.Remora.Discord/.idea/deployment.xml new file mode 100644 index 0000000000..5508f92e0a --- /dev/null +++ b/.idea/.idea.Remora.Discord/.idea/deployment.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.Remora.Discord/.idea/inspectionProfiles/Project_Default.xml b/.idea/.idea.Remora.Discord/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000000..5064a65063 --- /dev/null +++ b/.idea/.idea.Remora.Discord/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,14 @@ + + + + \ No newline at end of file diff --git a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Bidirectional/IHeartbeatAcknowledge.cs b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Bidirectional/IHeartbeatAcknowledge.cs index 928c078b7e..322d410b56 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Bidirectional/IHeartbeatAcknowledge.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Bidirectional/IHeartbeatAcknowledge.cs @@ -30,6 +30,4 @@ namespace Remora.Discord.API.Abstractions.Gateway.Bidirectional; /// Represents a heartbeat acknowledgement. This interface defines no data. /// [PublicAPI] -public interface IHeartbeatAcknowledge : IGatewayEvent, IGatewayCommand -{ -} +public interface IHeartbeatAcknowledge : IGatewayEvent, IGatewayCommand; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Commands/IGatewayCommand.cs b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Commands/IGatewayCommand.cs index e8514e77c7..bd264c7234 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Commands/IGatewayCommand.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Commands/IGatewayCommand.cs @@ -28,6 +28,4 @@ namespace Remora.Discord.API.Abstractions.Gateway.Commands; /// Represents a marker interface for gateway commands. /// [PublicAPI] -public interface IGatewayCommand : IGatewayPayloadData -{ -} +public interface IGatewayCommand : IGatewayPayloadData; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/ApplicationCommands/IApplicationCommandPermissionsUpdate.cs b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/ApplicationCommands/IApplicationCommandPermissionsUpdate.cs index 5c69e93517..0f50d375bc 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/ApplicationCommands/IApplicationCommandPermissionsUpdate.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/ApplicationCommands/IApplicationCommandPermissionsUpdate.cs @@ -29,6 +29,4 @@ namespace Remora.Discord.API.Abstractions.Gateway.Events; /// Represents an update to the permissions of a command. /// [PublicAPI] -public interface IApplicationCommandPermissionsUpdate : IGatewayEvent, IGuildApplicationCommandPermissions -{ -} +public interface IApplicationCommandPermissionsUpdate : IGatewayEvent, IGuildApplicationCommandPermissions; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/AuditLog/IGuildAuditLogEntryCreate.cs b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/AuditLog/IGuildAuditLogEntryCreate.cs index fefd4b9e51..7951b28a83 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/AuditLog/IGuildAuditLogEntryCreate.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/AuditLog/IGuildAuditLogEntryCreate.cs @@ -29,6 +29,4 @@ namespace Remora.Discord.API.Abstractions.Gateway.Events; /// Represents the creation of an audit log entry. /// [PublicAPI] -public interface IGuildAuditLogEntryCreate : IGatewayEvent, IAuditLogEntry -{ -} +public interface IGuildAuditLogEntryCreate : IGatewayEvent, IAuditLogEntry; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/AutoModeration/IAutoModerationRuleCreate.cs b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/AutoModeration/IAutoModerationRuleCreate.cs index 56b467d594..0bb3ce9859 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/AutoModeration/IAutoModerationRuleCreate.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/AutoModeration/IAutoModerationRuleCreate.cs @@ -29,6 +29,4 @@ namespace Remora.Discord.API.Abstractions.Gateway.Events; /// Represents the update of an auto moderation rule. /// [PublicAPI] -public interface IAutoModerationRuleCreate : IGatewayEvent, IAutoModerationRule -{ -} +public interface IAutoModerationRuleCreate : IGatewayEvent, IAutoModerationRule; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/AutoModeration/IAutoModerationRuleDelete.cs b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/AutoModeration/IAutoModerationRuleDelete.cs index c515a44404..9215ffdffc 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/AutoModeration/IAutoModerationRuleDelete.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/AutoModeration/IAutoModerationRuleDelete.cs @@ -29,6 +29,4 @@ namespace Remora.Discord.API.Abstractions.Gateway.Events; /// Represents the deletion of an auto moderation rule. /// [PublicAPI] -public interface IAutoModerationRuleDelete : IGatewayEvent, IAutoModerationRule -{ -} +public interface IAutoModerationRuleDelete : IGatewayEvent, IAutoModerationRule; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/AutoModeration/IAutoModerationRuleUpdate.cs b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/AutoModeration/IAutoModerationRuleUpdate.cs index 8a292eec77..1bcbf459a6 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/AutoModeration/IAutoModerationRuleUpdate.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/AutoModeration/IAutoModerationRuleUpdate.cs @@ -29,6 +29,4 @@ namespace Remora.Discord.API.Abstractions.Gateway.Events; /// Represents the creation of an auto moderation rule. /// [PublicAPI] -public interface IAutoModerationRuleUpdate : IGatewayEvent, IAutoModerationRule -{ -} +public interface IAutoModerationRuleUpdate : IGatewayEvent, IAutoModerationRule; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Channels/IChannelCreate.cs b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Channels/IChannelCreate.cs index c9c8959fda..66958f2317 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Channels/IChannelCreate.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Channels/IChannelCreate.cs @@ -29,6 +29,4 @@ namespace Remora.Discord.API.Abstractions.Gateway.Events; /// Represents the creation of a channel. /// [PublicAPI] -public interface IChannelCreate : IGatewayEvent, IChannel -{ -} +public interface IChannelCreate : IGatewayEvent, IChannel; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Channels/IChannelDelete.cs b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Channels/IChannelDelete.cs index a44066d55b..3fc46f7210 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Channels/IChannelDelete.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Channels/IChannelDelete.cs @@ -29,6 +29,4 @@ namespace Remora.Discord.API.Abstractions.Gateway.Events; /// Represents the deletion of a channel. /// [PublicAPI] -public interface IChannelDelete : IGatewayEvent, IChannel -{ -} +public interface IChannelDelete : IGatewayEvent, IChannel; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Channels/IChannelUpdate.cs b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Channels/IChannelUpdate.cs index 7d909f5fb4..11c54b33cc 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Channels/IChannelUpdate.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Channels/IChannelUpdate.cs @@ -29,6 +29,4 @@ namespace Remora.Discord.API.Abstractions.Gateway.Events; /// Represents the update of a channel. /// [PublicAPI] -public interface IChannelUpdate : IGatewayEvent, IChannel -{ -} +public interface IChannelUpdate : IGatewayEvent, IChannel; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Channels/IStageInstanceCreate.cs b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Channels/IStageInstanceCreate.cs index 671b1b9691..2956ac3fe6 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Channels/IStageInstanceCreate.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Channels/IStageInstanceCreate.cs @@ -29,6 +29,4 @@ namespace Remora.Discord.API.Abstractions.Gateway.Events; /// Represents the creation of a stage instance. /// [PublicAPI] -public interface IStageInstanceCreate : IStageInstance, IGatewayEvent -{ -} +public interface IStageInstanceCreate : IStageInstance, IGatewayEvent; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Channels/IStageInstanceDelete.cs b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Channels/IStageInstanceDelete.cs index 8a6454366e..f9200b5081 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Channels/IStageInstanceDelete.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Channels/IStageInstanceDelete.cs @@ -29,6 +29,4 @@ namespace Remora.Discord.API.Abstractions.Gateway.Events; /// Represents a deletion of a stage instance. /// [PublicAPI] -public interface IStageInstanceDelete : IStageInstance, IGatewayEvent -{ -} +public interface IStageInstanceDelete : IStageInstance, IGatewayEvent; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Channels/IStageInstanceUpdate.cs b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Channels/IStageInstanceUpdate.cs index ed8a96bb4c..82a3175c4f 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Channels/IStageInstanceUpdate.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Channels/IStageInstanceUpdate.cs @@ -29,6 +29,4 @@ namespace Remora.Discord.API.Abstractions.Gateway.Events; /// Represents an update of a stage instance. /// [PublicAPI] -public interface IStageInstanceUpdate : IStageInstance, IGatewayEvent -{ -} +public interface IStageInstanceUpdate : IStageInstance, IGatewayEvent; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Channels/IThreadDelete.cs b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Channels/IThreadDelete.cs index c00f577ad5..3149acea47 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Channels/IThreadDelete.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Channels/IThreadDelete.cs @@ -29,6 +29,4 @@ namespace Remora.Discord.API.Abstractions.Gateway.Events; /// Represents the creation of a channel. /// [PublicAPI] -public interface IThreadDelete : IGatewayEvent, IPartialChannel -{ -} +public interface IThreadDelete : IGatewayEvent, IPartialChannel; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Channels/IThreadUpdate.cs b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Channels/IThreadUpdate.cs index 4a4afd73cc..b8a490ea32 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Channels/IThreadUpdate.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Channels/IThreadUpdate.cs @@ -29,6 +29,4 @@ namespace Remora.Discord.API.Abstractions.Gateway.Events; /// Represents the creation of a channel. /// [PublicAPI] -public interface IThreadUpdate : IGatewayEvent, IChannel -{ -} +public interface IThreadUpdate : IGatewayEvent, IChannel; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/ConnectingResuming/IReconnect.cs b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/ConnectingResuming/IReconnect.cs index ea2f15cea9..f99c3e0877 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/ConnectingResuming/IReconnect.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/ConnectingResuming/IReconnect.cs @@ -28,6 +28,4 @@ namespace Remora.Discord.API.Abstractions.Gateway.Events; /// Represents a reconnection request. This interface defines no data. /// [PublicAPI] -public interface IReconnect : IGatewayEvent -{ -} +public interface IReconnect : IGatewayEvent; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/ConnectingResuming/IResumed.cs b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/ConnectingResuming/IResumed.cs index fcf3ed09f8..c6ca6183df 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/ConnectingResuming/IResumed.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/ConnectingResuming/IResumed.cs @@ -28,6 +28,4 @@ namespace Remora.Discord.API.Abstractions.Gateway.Events; /// Represents an event that marks the completion of a session resumption. /// [PublicAPI] -public interface IResumed : IGatewayEvent -{ -} +public interface IResumed : IGatewayEvent; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/GuildScheduledEvents/IGuildScheduledEventCreate.cs b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/GuildScheduledEvents/IGuildScheduledEventCreate.cs index bf372c27a6..b4135c1370 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/GuildScheduledEvents/IGuildScheduledEventCreate.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/GuildScheduledEvents/IGuildScheduledEventCreate.cs @@ -29,6 +29,4 @@ namespace Remora.Discord.API.Abstractions.Gateway.Events; /// Sent when a scheduled event is created. /// [PublicAPI] -public interface IGuildScheduledEventCreate : IGatewayEvent, IGuildScheduledEvent -{ -} +public interface IGuildScheduledEventCreate : IGatewayEvent, IGuildScheduledEvent; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/GuildScheduledEvents/IGuildScheduledEventDelete.cs b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/GuildScheduledEvents/IGuildScheduledEventDelete.cs index b06fa10a3f..3c51edf98f 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/GuildScheduledEvents/IGuildScheduledEventDelete.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/GuildScheduledEvents/IGuildScheduledEventDelete.cs @@ -29,6 +29,4 @@ namespace Remora.Discord.API.Abstractions.Gateway.Events; /// Sent when a scheduled event is deleted. /// [PublicAPI] -public interface IGuildScheduledEventDelete : IGatewayEvent, IGuildScheduledEvent -{ -} +public interface IGuildScheduledEventDelete : IGatewayEvent, IGuildScheduledEvent; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/GuildScheduledEvents/IGuildScheduledEventUpdate.cs b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/GuildScheduledEvents/IGuildScheduledEventUpdate.cs index d27a5385f5..1640767409 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/GuildScheduledEvents/IGuildScheduledEventUpdate.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/GuildScheduledEvents/IGuildScheduledEventUpdate.cs @@ -29,6 +29,4 @@ namespace Remora.Discord.API.Abstractions.Gateway.Events; /// Sent when a scheduled event is updated. /// [PublicAPI] -public interface IGuildScheduledEventUpdate : IGatewayEvent, IGuildScheduledEvent -{ -} +public interface IGuildScheduledEventUpdate : IGatewayEvent, IGuildScheduledEvent; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Guilds/IGuildDelete.cs b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Guilds/IGuildDelete.cs index 34d5083851..9962681252 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Guilds/IGuildDelete.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Guilds/IGuildDelete.cs @@ -29,6 +29,4 @@ namespace Remora.Discord.API.Abstractions.Gateway.Events; /// Represents the delete of a guild. /// [PublicAPI] -public interface IGuildDelete : IGatewayEvent, IUnavailableGuild -{ -} +public interface IGuildDelete : IGatewayEvent, IUnavailableGuild; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Guilds/IGuildUpdate.cs b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Guilds/IGuildUpdate.cs index d1e95e6584..1f7b0537d8 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Guilds/IGuildUpdate.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Guilds/IGuildUpdate.cs @@ -29,6 +29,4 @@ namespace Remora.Discord.API.Abstractions.Gateway.Events; /// Represents the update of a guild. /// [PublicAPI] -public interface IGuildUpdate : IGatewayEvent, IGuild -{ -} +public interface IGuildUpdate : IGatewayEvent, IGuild; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/IGatewayEvent.cs b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/IGatewayEvent.cs index 557e51256d..e0d8a319bf 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/IGatewayEvent.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/IGatewayEvent.cs @@ -28,6 +28,4 @@ namespace Remora.Discord.API.Abstractions.Gateway.Events; /// Acts as a marker interface for gateway events. /// [PublicAPI] -public interface IGatewayEvent : IGatewayPayloadData -{ -} +public interface IGatewayEvent : IGatewayPayloadData; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Interactions/IInteractionCreate.cs b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Interactions/IInteractionCreate.cs index fd51c8332d..fda0bdd0a1 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Interactions/IInteractionCreate.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Interactions/IInteractionCreate.cs @@ -29,6 +29,4 @@ namespace Remora.Discord.API.Abstractions.Gateway.Events; /// Represents a user-invoked slash command. /// [PublicAPI] -public interface IInteractionCreate : IInteraction, IGatewayEvent -{ -} +public interface IInteractionCreate : IInteraction, IGatewayEvent; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Monetization/IEntitlementCreate.cs b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Monetization/IEntitlementCreate.cs new file mode 100644 index 0000000000..e9656d8f8c --- /dev/null +++ b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Monetization/IEntitlementCreate.cs @@ -0,0 +1,32 @@ +// +// IEntitlementCreate.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using JetBrains.Annotations; +using Remora.Discord.API.Abstractions.Objects; + +namespace Remora.Discord.API.Abstractions.Gateway.Events; + +/// +/// Represents a user's initial subscription to an SKU. +/// +[PublicAPI] +public interface IEntitlementCreate : IEntitlement, IGatewayEvent; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Monetization/IEntitlementDelete.cs b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Monetization/IEntitlementDelete.cs new file mode 100644 index 0000000000..20ea38950d --- /dev/null +++ b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Monetization/IEntitlementDelete.cs @@ -0,0 +1,32 @@ +// +// IEntitlementDelete.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using JetBrains.Annotations; +using Remora.Discord.API.Abstractions.Objects; + +namespace Remora.Discord.API.Abstractions.Gateway.Events; + +/// +/// Represents a removal of an entitlement from a user. +/// +[PublicAPI] +public interface IEntitlementDelete : IEntitlement, IGatewayEvent; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Monetization/IEntitlementUpdate.cs b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Monetization/IEntitlementUpdate.cs new file mode 100644 index 0000000000..37c97bacb4 --- /dev/null +++ b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Monetization/IEntitlementUpdate.cs @@ -0,0 +1,32 @@ +// +// IEntitlementUpdate.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using JetBrains.Annotations; +using Remora.Discord.API.Abstractions.Objects; + +namespace Remora.Discord.API.Abstractions.Gateway.Events; + +/// +/// Represents a renewal of a user's entitlement to an SKU. +/// +[PublicAPI] +public interface IEntitlementUpdate : IEntitlement, IGatewayEvent; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Presence/IPresenceUpdate.cs b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Presence/IPresenceUpdate.cs index c0fe5306f6..9a97d8bcc2 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Presence/IPresenceUpdate.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Presence/IPresenceUpdate.cs @@ -29,6 +29,4 @@ namespace Remora.Discord.API.Abstractions.Gateway.Events; /// Represents the update of a user's presence. /// [PublicAPI] -public interface IPresenceUpdate : IGatewayEvent, IPresence -{ -} +public interface IPresenceUpdate : IGatewayEvent, IPresence; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Users/IUserUpdate.cs b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Users/IUserUpdate.cs index ff07f62719..f463495f75 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Users/IUserUpdate.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Users/IUserUpdate.cs @@ -29,6 +29,4 @@ namespace Remora.Discord.API.Abstractions.Gateway.Events; /// Represents an update to a user. /// [PublicAPI] -public interface IUserUpdate : IGatewayEvent, IUser -{ -} +public interface IUserUpdate : IGatewayEvent, IUser; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Voice/IVoiceStateUpdate.cs b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Voice/IVoiceStateUpdate.cs index d074eddb8e..94c178b763 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Voice/IVoiceStateUpdate.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Gateway/Events/Voice/IVoiceStateUpdate.cs @@ -29,6 +29,4 @@ namespace Remora.Discord.API.Abstractions.Gateway.Events; /// Represents an update to a user's voice state. /// [PublicAPI] -public interface IVoiceStateUpdate : IGatewayEvent, IVoiceState -{ -} +public interface IVoiceStateUpdate : IGatewayEvent, IVoiceState; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Gateway/IGatewayPayloadData.cs b/Backend/Remora.Discord.API.Abstractions/API/Gateway/IGatewayPayloadData.cs index 30064dadab..0d391316dd 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Gateway/IGatewayPayloadData.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Gateway/IGatewayPayloadData.cs @@ -28,6 +28,4 @@ namespace Remora.Discord.API.Abstractions.Gateway; /// Represents a marker interface for gateway payload data. /// [PublicAPI] -public interface IGatewayPayloadData -{ -} +public interface IGatewayPayloadData; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Gateway/IPayload.cs b/Backend/Remora.Discord.API.Abstractions/API/Gateway/IPayload.cs index 6f010d6318..e4997f1843 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Gateway/IPayload.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Gateway/IPayload.cs @@ -28,6 +28,4 @@ namespace Remora.Discord.API.Abstractions.Gateway; /// Marker interface for payload classes. /// [PublicAPI] -public interface IPayload -{ -} +public interface IPayload; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Applications/ApplicationIntegrationType.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Applications/ApplicationIntegrationType.cs new file mode 100644 index 0000000000..c7115b5cfb --- /dev/null +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Applications/ApplicationIntegrationType.cs @@ -0,0 +1,42 @@ +// +// ApplicationIntegrationType.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using JetBrains.Annotations; + +namespace Remora.Discord.API.Abstractions.Objects; + +/// +/// Represents valid locations for users to install application integrations. +/// +[PublicAPI] +public enum ApplicationIntegrationType +{ + /// + /// Specifies that the integration can be installed on a guild. + /// + GuildInstallable = 0, + + /// + /// Specifies that the integration can be installed on a user. + /// + UserInstallable = 1, +} diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Applications/IApplication.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Applications/IApplication.cs index 24a865bb82..7e02553e0f 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Objects/Applications/IApplication.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Applications/IApplication.cs @@ -68,6 +68,11 @@ public interface IApplication : IPartialApplication /// new bool DoesBotRequireCodeGrant { get; } + /// + /// Gets the user object for the bot user associated with the application. + /// + new Optional Bot { get; } + /// /// Gets the URL to the application's terms of service. /// @@ -128,6 +133,23 @@ public interface IApplication : IPartialApplication /// new Optional ApproximateGuildCount { get; } + /// + /// Gets the redirect URIs for the application. + /// + new Optional> RedirectUris { get; } + + /// + /// Gets the interaction endpoint URL for the application. + /// + new Optional InteractionsEndpointUrl { get; } + + /// + /// Gets the application's role connection verification entry point, + /// which when configured will render the app as a verification method + /// in the guild role verification configuration. + /// + new Optional RoleConnectionsVerificationUrl { get; } + /// /// Gets up to 5 tags describing the content and functionality of the application. /// @@ -144,11 +166,9 @@ public interface IApplication : IPartialApplication new Optional CustomInstallUrl { get; } /// - /// Gets the application's role connection verification entry point, - /// which when configured will render the app as a verification method - /// in the guild role verification configuration. + /// Gets the application's integration type configurations. /// - new Optional RoleConnectionsVerificationUrl { get; } + new IReadOnlyDictionary IntegrationTypesConfig { get; } /// Optional IPartialApplication.ID => this.ID; @@ -171,6 +191,9 @@ public interface IApplication : IPartialApplication /// Optional IPartialApplication.DoesBotRequireCodeGrant => this.DoesBotRequireCodeGrant; + /// + Optional IPartialApplication.Bot => this.Bot; + /// Optional IPartialApplication.TermsOfServiceURL => this.TermsOfServiceURL; @@ -207,6 +230,15 @@ public interface IApplication : IPartialApplication /// Optional IPartialApplication.ApproximateGuildCount => this.ApproximateGuildCount; + /// + Optional> IPartialApplication.RedirectUris => this.RedirectUris; + + /// + Optional IPartialApplication.InteractionsEndpointUrl => this.InteractionsEndpointUrl; + + /// + Optional IPartialApplication.RoleConnectionsVerificationUrl => this.RoleConnectionsVerificationUrl; + /// Optional> IPartialApplication.Tags => this.Tags; @@ -217,5 +249,5 @@ public interface IApplication : IPartialApplication Optional IPartialApplication.CustomInstallUrl => this.CustomInstallUrl; /// - Optional IPartialApplication.RoleConnectionsVerificationUrl => this.RoleConnectionsVerificationUrl; + Optional> IPartialApplication.IntegrationTypesConfig => new(this.IntegrationTypesConfig); } diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Applications/IApplicationIntegrationTypeConfig.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Applications/IApplicationIntegrationTypeConfig.cs new file mode 100644 index 0000000000..66cca05a81 --- /dev/null +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Applications/IApplicationIntegrationTypeConfig.cs @@ -0,0 +1,37 @@ +// +// IApplicationIntegrationTypeConfig.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using JetBrains.Annotations; + +namespace Remora.Discord.API.Abstractions.Objects; + +/// +/// The integration type configuration for an application. +/// +[PublicAPI] +public interface IApplicationIntegrationTypeConfig +{ + /// + /// Gets the OAuth2 install parameters for the integration type. + /// + public IApplicationOAuth2InstallParams OAuth2InstallParams { get; } +} diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Applications/IApplicationOAuth2InstallParams.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Applications/IApplicationOAuth2InstallParams.cs new file mode 100644 index 0000000000..2419832d46 --- /dev/null +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Applications/IApplicationOAuth2InstallParams.cs @@ -0,0 +1,46 @@ +// +// IApplicationOAuth2InstallParams.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace Remora.Discord.API.Abstractions.Objects; + +/// +/// Represents the OAuth2 install parameters for an application. +/// +[PublicAPI] +public interface IApplicationOAuth2InstallParams +{ + /// + /// Gets the permissions required for the application. + /// + /// + /// Only applicable if includes `bot`. + /// + IDiscordPermissionSet Permissions { get; } + + /// + /// Gets the list of OAuth2 scopes required for the application. + /// + IReadOnlyList Scopes { get; } +} diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Applications/IPartialApplication.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Applications/IPartialApplication.cs index 2d47dec7fc..57dfb37e98 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Objects/Applications/IPartialApplication.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Applications/IPartialApplication.cs @@ -54,6 +54,9 @@ public interface IPartialApplication /// Optional DoesBotRequireCodeGrant { get; } + /// + Optional Bot { get; } + /// Optional TermsOfServiceURL { get; } @@ -90,6 +93,15 @@ public interface IPartialApplication /// Optional ApproximateGuildCount { get; } + /// + Optional> RedirectUris { get; } + + /// + Optional InteractionsEndpointUrl { get; } + + /// + Optional RoleConnectionsVerificationUrl { get; } + /// Optional> Tags { get; } @@ -99,6 +111,6 @@ public interface IPartialApplication /// Optional CustomInstallUrl { get; } - /// - Optional RoleConnectionsVerificationUrl { get; } + /// + Optional> IntegrationTypesConfig { get; } } diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/AuditLogs/IOptionalAuditEntryInfo.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/AuditLogs/IOptionalAuditEntryInfo.cs index b300928021..bfae273674 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Objects/AuditLogs/IOptionalAuditEntryInfo.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/AuditLogs/IOptionalAuditEntryInfo.cs @@ -124,4 +124,13 @@ public interface IOptionalAuditEntryInfo /// /// Optional Type { get; } + + /// + /// Gets the type of integration which performed the action. + /// + /// + /// This is a strangely documented property from Discord, but corresponds to the + /// property. + /// + Optional IntegrationType { get; } } diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Channels/ChannelFlags.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Channels/ChannelFlags.cs index 507ab95370..a5668ce655 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Objects/Channels/ChannelFlags.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Channels/ChannelFlags.cs @@ -39,5 +39,10 @@ public enum ChannelFlags /// /// The forum requires a tag to be specified when creating a thread. /// - RequireTag = 1 << 4 + RequireTag = 1 << 4, + + /// + /// Hides the embedded media download options. Available only for . + /// + HideMediaDownloadOptions = 1 << 15 } diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Channels/ChannelType.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Channels/ChannelType.cs index e0fa2ab227..e12a42aaf6 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Objects/Channels/ChannelType.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Channels/ChannelType.cs @@ -89,5 +89,10 @@ public enum ChannelType /// /// A channel that can only contain threads. /// - GuildForum = 15 + GuildForum = 15, + + /// + /// A channel that can only contain threads, similar to . + /// + GuildMedia = 16 } diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Channels/ThreadMemberFlags.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Channels/ThreadMemberFlags.cs index 70d217007b..72af8acd71 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Objects/Channels/ThreadMemberFlags.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Channels/ThreadMemberFlags.cs @@ -29,6 +29,4 @@ namespace Remora.Discord.API.Abstractions.Objects; /// Enumerates various thread member flags. /// [PublicAPI, Flags] -public enum ThreadMemberFlags -{ -} +public enum ThreadMemberFlags; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/ApplicationCommands/IApplicationCommand.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/ApplicationCommands/IApplicationCommand.cs index 61fbeb55d0..f4f8607d5b 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/ApplicationCommands/IApplicationCommand.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/ApplicationCommands/IApplicationCommand.cs @@ -114,4 +114,14 @@ public interface IApplicationCommand /// Gets a value indicating whether this command is age-restricted. /// Optional IsNsfw { get; } + + /// + /// Gets a value indicating the contexts in which this command can be installed. + /// + Optional> IntegrationTypes { get; } + + /// + /// Gets a value indicating the contexts in which this command can be invoked. + /// + Optional> Contexts { get; } } diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/ApplicationCommands/IApplicationCommandPermissions.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/ApplicationCommands/IApplicationCommandPermissions.cs index 5adee583ac..52fedc4260 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/ApplicationCommands/IApplicationCommandPermissions.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/ApplicationCommands/IApplicationCommandPermissions.cs @@ -44,5 +44,5 @@ public interface IApplicationCommandPermissions /// /// Gets a value indicating whether the referenced entity has permission to use the command. /// - bool HasPermission { get; } + bool HasPermission { get; } } diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/ApplicationCommands/IBulkApplicationCommandData.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/ApplicationCommands/IBulkApplicationCommandData.cs index 8fe59b2d65..940b0012f4 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/ApplicationCommands/IBulkApplicationCommandData.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/ApplicationCommands/IBulkApplicationCommandData.cs @@ -61,4 +61,10 @@ public interface IBulkApplicationCommandData /// Optional IsNsfw { get; } + + /// + Optional> IntegrationTypes { get; } + + /// + Optional> Contexts { get; } } diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/IMessageComponent.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/IMessageComponent.cs index 24315411d5..ad52b2320f 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/IMessageComponent.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/IMessageComponent.cs @@ -28,6 +28,4 @@ namespace Remora.Discord.API.Abstractions.Objects; /// Represents the base type for all components. This is a marker interface with no real functionality. /// [PublicAPI] -public interface IMessageComponent : IPartialMessageComponent -{ -} +public interface IMessageComponent : IPartialMessageComponent; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/IPartialMessageComponent.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/IPartialMessageComponent.cs index 7e5a2cdbac..efc494f1b2 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/IPartialMessageComponent.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/IPartialMessageComponent.cs @@ -28,6 +28,4 @@ namespace Remora.Discord.API.Abstractions.Objects; /// Represents the base type for all partial components. This is a marker interface with no real functionality. /// [PublicAPI] -public interface IPartialMessageComponent -{ -} +public interface IPartialMessageComponent; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IChannelSelectComponent.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IChannelSelectComponent.cs index 6e007ebeca..752c2fea4a 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IChannelSelectComponent.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IChannelSelectComponent.cs @@ -30,7 +30,7 @@ namespace Remora.Discord.API.Abstractions.Objects; /// Represents a dropdown of selectable channels. /// [PublicAPI] -public interface IChannelSelectComponent : ISelectMenuComponent, IPartialChannelSelectComponent +public interface IChannelSelectComponent : IMentionableSelectComponent, IPartialChannelSelectComponent { /// /// Gets the channel types to show on a component. diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IMentionableSelectComponent.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IMentionableSelectComponent.cs index fdd842da13..df0debbe14 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IMentionableSelectComponent.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IMentionableSelectComponent.cs @@ -20,7 +20,9 @@ // along with this program. If not, see . // +using System.Collections.Generic; using JetBrains.Annotations; +using Remora.Rest.Core; namespace Remora.Discord.API.Abstractions.Objects; @@ -30,4 +32,12 @@ namespace Remora.Discord.API.Abstractions.Objects; [PublicAPI] public interface IMentionableSelectComponent : ISelectMenuComponent, IPartialMentionableSelectComponent { + /// + /// Gets the default values for auto-populated select menu components. + /// + new Optional> DefaultValues { get; } + + /// + Optional> IPartialMentionableSelectComponent.DefaultValues + => this.DefaultValues.Map>(v => v); } diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IPartialChannelSelectComponent.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IPartialChannelSelectComponent.cs index abc37f8de5..82f9e2cd73 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IPartialChannelSelectComponent.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IPartialChannelSelectComponent.cs @@ -30,7 +30,7 @@ namespace Remora.Discord.API.Abstractions.Objects; /// Represents a partial dropdown of selectable channels. /// [PublicAPI] -public interface IPartialChannelSelectComponent : IPartialSelectMenuComponent +public interface IPartialChannelSelectComponent : IPartialMentionableSelectComponent { /// Optional> ChannelTypes { get; } diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IPartialMentionableSelectComponent.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IPartialMentionableSelectComponent.cs index 388eed422e..e0a8f60e24 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IPartialMentionableSelectComponent.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IPartialMentionableSelectComponent.cs @@ -20,14 +20,18 @@ // along with this program. If not, see . // +using System.Collections.Generic; using JetBrains.Annotations; +using Remora.Rest.Core; namespace Remora.Discord.API.Abstractions.Objects; /// -/// Represents a partial dropdown of selectable mentionables (users and roles). +/// Represents a partial dropdown of selectable mentionables (users, roles, and channels). /// [PublicAPI] public interface IPartialMentionableSelectComponent : IPartialSelectMenuComponent { + /// + Optional> DefaultValues { get; } } diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IPartialRoleSelectComponent.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IPartialRoleSelectComponent.cs index 636b93d39c..56a14238a6 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IPartialRoleSelectComponent.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IPartialRoleSelectComponent.cs @@ -28,6 +28,4 @@ namespace Remora.Discord.API.Abstractions.Objects; /// Represents a partial dropdown of selectable roles. /// [PublicAPI] -public interface IPartialRoleSelectComponent : IPartialSelectMenuComponent -{ -} +public interface IPartialRoleSelectComponent : IPartialMentionableSelectComponent; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IPartialSelectDefaultValue.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IPartialSelectDefaultValue.cs new file mode 100644 index 0000000000..962ee83eb7 --- /dev/null +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IPartialSelectDefaultValue.cs @@ -0,0 +1,39 @@ +// +// IPartialSelectDefaultValue.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using JetBrains.Annotations; +using Remora.Rest.Core; + +namespace Remora.Discord.API.Abstractions.Objects; + +/// +/// Represents a partial default value for a select menu. +/// +[PublicAPI] +public interface IPartialSelectDefaultValue +{ + /// + Optional ID { get; } + + /// + Optional Type { get; } +} diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IPartialUserSelectComponent.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IPartialUserSelectComponent.cs index d069792efc..a190c351a0 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IPartialUserSelectComponent.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IPartialUserSelectComponent.cs @@ -28,6 +28,4 @@ namespace Remora.Discord.API.Abstractions.Objects; /// Represents a partial dropdown of selectable users. /// [PublicAPI] -public interface IPartialUserSelectComponent : IPartialSelectMenuComponent -{ -} +public interface IPartialUserSelectComponent : IPartialMentionableSelectComponent; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IRoleSelectComponent.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IRoleSelectComponent.cs index 64dd45997e..42446f889c 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IRoleSelectComponent.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IRoleSelectComponent.cs @@ -28,6 +28,4 @@ namespace Remora.Discord.API.Abstractions.Objects; /// Represents a dropdown of selectable roles. /// [PublicAPI] -public interface IRoleSelectComponent : ISelectMenuComponent, IPartialRoleSelectComponent -{ -} +public interface IRoleSelectComponent : IMentionableSelectComponent, IPartialRoleSelectComponent; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/ISelectDefaultValue.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/ISelectDefaultValue.cs new file mode 100644 index 0000000000..8a3c6088f4 --- /dev/null +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/ISelectDefaultValue.cs @@ -0,0 +1,49 @@ +// +// ISelectDefaultValue.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using JetBrains.Annotations; +using Remora.Rest.Core; + +namespace Remora.Discord.API.Abstractions.Objects; + +/// +/// Represents a default value for a select menu. +/// +[PublicAPI] +public interface ISelectDefaultValue : IPartialSelectDefaultValue +{ + /// + /// Gets the ID of the default entity (user, role, or channel). + /// + new Snowflake ID { get; } + + /// + /// Gets the type of value that represents. Can be one of "user", "role", or "channel". + /// + new string Type { get; } + + /// + Optional IPartialSelectDefaultValue.ID => this.ID; + + /// + Optional IPartialSelectDefaultValue.Type => this.Type; +} diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IUserSelectComponent.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IUserSelectComponent.cs index dfba1ac38d..120c8515aa 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IUserSelectComponent.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/Components/SelectMenu/IUserSelectComponent.cs @@ -28,6 +28,4 @@ namespace Remora.Discord.API.Abstractions.Objects; /// Represents a dropdown of selectable users. /// [PublicAPI] -public interface IUserSelectComponent : ISelectMenuComponent, IPartialUserSelectComponent -{ -} +public interface IUserSelectComponent : IMentionableSelectComponent, IPartialUserSelectComponent; diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/IApplicationCommandInteractionMetadata.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/IApplicationCommandInteractionMetadata.cs new file mode 100644 index 0000000000..2b7349b175 --- /dev/null +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/IApplicationCommandInteractionMetadata.cs @@ -0,0 +1,37 @@ +// +// IApplicationCommandInteractionMetadata.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using JetBrains.Annotations; + +namespace Remora.Discord.API.Abstractions.Objects; + +/// +/// Represents metadata related to application commands. +/// +[PublicAPI] +public interface IApplicationCommandInteractionMetadata : IMessageInteractionMetadata +{ + /// + /// Gets the name of the command. + /// + string Name { get; } +} diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/IInteraction.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/IInteraction.cs index d10742391d..52a8c6ea2f 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/IInteraction.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/IInteraction.cs @@ -21,6 +21,7 @@ // using System; +using System.Collections.Generic; using JetBrains.Annotations; using OneOf; using Remora.Rest.Core; @@ -102,7 +103,7 @@ public interface IInteraction /// /// Gets the computed permissions for the application in the context of the interaction's execution. /// - Optional AppPermissions { get; } + IDiscordPermissionSet AppPermissions { get; } /// /// Gets the locale of the invoking user. @@ -116,4 +117,28 @@ public interface IInteraction /// Gets the locale of the guild the interaction was sent from. /// Optional GuildLocale { get; } + + /// + /// Gets, for monetized apps, any entitlements for the invoking user. + /// + IReadOnlyList Entitlements { get; } + + /// + /// Gets the context of the interaction. + /// + Optional Context { get; } + + /// + /// Gets the integrations that authorized the interaction. + /// + /// + /// This is a mapping of the integration type to the ID of its resource. + /// + /// The dictionary contains the following, given the circumstances:
+ /// - If the integration is installed to a user, a key of and the value is the user ID.
+ /// - If the integration is installed to a guild, a key of and the value is the guild ID. + /// If the interaction is sent outside the context of a guild, the value is always zero.
+ ///
+ ///
+ Optional> AuthorizingIntegrationOwners { get; } } diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/IMessageComponentInteractionMetadata.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/IMessageComponentInteractionMetadata.cs new file mode 100644 index 0000000000..36725b1112 --- /dev/null +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/IMessageComponentInteractionMetadata.cs @@ -0,0 +1,38 @@ +// +// IMessageComponentInteractionMetadata.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using JetBrains.Annotations; +using Remora.Rest.Core; + +namespace Remora.Discord.API.Abstractions.Objects; + +/// +/// Represents metadata related to a message component interaction. +/// +[PublicAPI] +public interface IMessageComponentInteractionMetadata : IMessageInteractionMetadata +{ + /// + /// Gets the ID of the message that was interacted with. + /// + Snowflake InteractedMessageID { get; } +} diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/IMessageInteractionMetadata.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/IMessageInteractionMetadata.cs new file mode 100644 index 0000000000..8e2decdbc1 --- /dev/null +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/IMessageInteractionMetadata.cs @@ -0,0 +1,68 @@ +// +// IMessageInteractionMetadata.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Collections.Generic; +using JetBrains.Annotations; +using Remora.Rest.Core; + +namespace Remora.Discord.API.Abstractions.Objects; + +/// +/// Represents the metadata of an application command interaction. +/// +[PublicAPI] +public interface IMessageInteractionMetadata +{ + /// + /// Gets the ID of the interaction. + /// + Snowflake ID { get; } + + /// + /// Gets the ID of the user who triggered the interaction. + /// + Snowflake UserID { get; } + + /// + /// Gets the type of the interaction. + /// + InteractionType Type { get; } + + /// + /// Gets the ID of the original response message. Only applicable to followup messages. + /// + Optional OriginalResponseMessageID { get; } + + /// + /// Gets the integrations that authorized the interaction. + /// + /// + /// This is a mapping of the integration type to the ID of its resource. + /// + /// The dictionary contains the following, given the circumstances:
+ /// - If the integration is installed to a user, a key of and the value is the user ID.
+ /// - If the integration is installed to a guild, a key of and the value is the guild ID. + /// If the interaction is sent outside the context of a guild, the value is always zero.
+ ///
+ ///
+ IReadOnlyDictionary AuthorizingIntegrationOwners { get; } +} diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/IModalSubmitInteractionMetadata.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/IModalSubmitInteractionMetadata.cs new file mode 100644 index 0000000000..2c2dfd30e1 --- /dev/null +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/IModalSubmitInteractionMetadata.cs @@ -0,0 +1,38 @@ +// +// IModalSubmitInteractionMetadata.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using JetBrains.Annotations; +using OneOf; + +namespace Remora.Discord.API.Abstractions.Objects; + +/// +/// Represents metadata related to a modal submit interaction. +/// +[PublicAPI] +public interface IModalSubmitInteractionMetadata : IMessageInteractionMetadata +{ + /// + /// Gets the metadata for the interaction that triggered the modal. + /// + OneOf TriggeringInteractionMetadata { get; } +} diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/InteractionCallbackType.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/InteractionCallbackType.cs index 43f0ff1f30..70a4743df3 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/InteractionCallbackType.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/InteractionCallbackType.cs @@ -72,5 +72,10 @@ public enum InteractionCallbackType /// /// Only relevant for component-based interactions and application commands. /// - Modal = 9 + Modal = 9, + + /// + /// Respond to an interaction with an upgrade button. Only available for apps with monetization enabled. + /// + PremiumRequired = 10 } diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/InteractionContextType.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/InteractionContextType.cs new file mode 100644 index 0000000000..7c600ebd4d --- /dev/null +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/InteractionContextType.cs @@ -0,0 +1,47 @@ +// +// InteractionContextType.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using JetBrains.Annotations; + +namespace Remora.Discord.API.Abstractions.Objects; + +/// +/// Enumerates various interaction context types. +/// +[PublicAPI] +public enum InteractionContextType +{ + /// + /// The interaction was executed in the context of a guild. + /// + Guild = 0, + + /// + /// The interaction was executed in the context of a direct message to the bot associated with the application. + /// + BotDM = 1, + + /// + /// The interaction was executed in the context of a direct message or group direct message. + /// + PrivateChannel = 2, +} diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Messages/IMessage.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Messages/IMessage.cs index 1bfcd2e301..de9157c48e 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Objects/Messages/IMessage.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Messages/IMessage.cs @@ -23,6 +23,7 @@ using System; using System.Collections.Generic; using JetBrains.Annotations; +using OneOf; using Remora.Rest.Core; namespace Remora.Discord.API.Abstractions.Objects; @@ -151,7 +152,7 @@ public interface IMessage : IPartialMessage /// /// Gets the message reference. Sent with cross-posted messages. /// - new Optional MessageReference { get; } + new Optional MessageReference { get; } /// /// Gets a set of bitwise flags describing extra features of the message. @@ -188,6 +189,16 @@ public interface IMessage : IPartialMessage /// new Optional Position { get; } + /// + /// Gets data for users, members, channels, and roles in the message's auto-populated select menus. + /// + new Optional Resolved { get; } + + /// + /// Gets the metadata of the interaction, if any. + /// + new Optional> InteractionMetadata { get; } + /// Optional IPartialMessage.ID => this.ID; @@ -274,4 +285,10 @@ public interface IMessage : IPartialMessage /// Optional IPartialMessage.Position => this.Position; + + /// + Optional IPartialMessage.Resolved => this.Resolved; + + /// + Optional> IPartialMessage.InteractionMetadata => this.InteractionMetadata; } diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Messages/IPartialMessage.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Messages/IPartialMessage.cs index fb4222b95a..d0807fb5f1 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Objects/Messages/IPartialMessage.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Messages/IPartialMessage.cs @@ -23,6 +23,7 @@ using System; using System.Collections.Generic; using JetBrains.Annotations; +using OneOf; using Remora.Rest.Core; namespace Remora.Discord.API.Abstractions.Objects; @@ -97,7 +98,7 @@ public interface IPartialMessage Optional ApplicationID { get; } /// - Optional MessageReference { get; } + Optional MessageReference { get; } /// Optional Flags { get; } @@ -119,4 +120,10 @@ public interface IPartialMessage /// Optional Position { get; } + + /// + Optional Resolved { get; } + + /// + Optional> InteractionMetadata { get; } } diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Monetization/EntitlementOwnerType.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Monetization/EntitlementOwnerType.cs new file mode 100644 index 0000000000..02c61ee81c --- /dev/null +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Monetization/EntitlementOwnerType.cs @@ -0,0 +1,42 @@ +// +// EntitlementOwnerType.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using JetBrains.Annotations; + +namespace Remora.Discord.API.Abstractions.Objects; + +/// +/// Enumerates various entitlement owner types. +/// +[PublicAPI] +public enum EntitlementOwnerType +{ + /// + /// The entitlement is owned by a guild. + /// + Guild = 1, + + /// + /// The entitlement is owned by a user. + /// + User = 2 +} diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Monetization/EntitlementType.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Monetization/EntitlementType.cs new file mode 100644 index 0000000000..773506a61e --- /dev/null +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Monetization/EntitlementType.cs @@ -0,0 +1,72 @@ +// +// EntitlementType.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using JetBrains.Annotations; + +namespace Remora.Discord.API.Abstractions.Objects; + +/// +/// Enumerates various entitlement types. +/// +[PublicAPI] +public enum EntitlementType +{ + /// + /// The entitlement was purchased by a user. + /// + Purchase = 1, + + /// + /// The entitlement is a Nitro subscription. + /// + PremiumSubscription = 2, + + /// + /// The entitlement was gifted by a developer. + /// + DeveloperGift = 3, + + /// + /// The entitlement was purchased by a developer in application test mode. + /// + TestModePurchase = 4, + + /// + /// The entitlement was granted when the SKU was free. + /// + FreePurchase = 5, + + /// + /// The entitlement was gifted by another user. + /// + UserGift = 6, + + /// + /// The entitlement was claimed by a user for free as part of their Nitro subscription. + /// + PremiumPurchase = 7, + + /// + /// The entitlement was purchased as an app subscription. + /// + ApplicationSubscription = 8 +} diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Monetization/IEntitlement.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Monetization/IEntitlement.cs new file mode 100644 index 0000000000..70cb7920d7 --- /dev/null +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Monetization/IEntitlement.cs @@ -0,0 +1,114 @@ +// +// IEntitlement.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; +using JetBrains.Annotations; +using Remora.Rest.Core; + +namespace Remora.Discord.API.Abstractions.Objects; + +/// +/// Represents information about a user's access to monetized application features. +/// +[PublicAPI] +public interface IEntitlement : IPartialEntitlement +{ + /// + /// Gets the ID of the entitlement. + /// + new Snowflake ID { get; } + + /// + /// Gets the ID of the SKU. + /// + new Snowflake SKUID { get; } + + /// + /// Gets the ID of the parent application. + /// + new Snowflake ApplicationID { get; } + + /// + /// Gets the ID of the user that is granted access to the entitlement's SKU. + /// + new Optional UserID { get; } + + /// + /// Gets the type of the entitlement. + /// + new EntitlementType Type { get; } + + /// + /// Gets a value indicating whether the entitlement has been deleted. + /// + new bool IsDeleted { get; } + + /// + /// Gets the start time at which the entitlement is valid. + /// + new Optional StartsAt { get; } + + /// + /// Gets the end time at which the entitlement is no longer valid. + /// + new Optional EndsAt { get; } + + /// + /// Gets the ID of the guild that is granted access to the entitlement's SKU. + /// + new Optional GuildID { get; } + + /// + /// Gets a value indicating whether the entitlement has been consumed. + /// + new Optional IsConsumed { get; } + + /// + Optional IPartialEntitlement.ID => this.ID; + + /// + Optional IPartialEntitlement.SKUID => this.SKUID; + + /// + Optional IPartialEntitlement.ApplicationID => this.ApplicationID; + + /// + Optional IPartialEntitlement.UserID => this.UserID; + + /// + Optional IPartialEntitlement.Type => this.Type; + + /// + Optional IPartialEntitlement.IsDeleted => this.IsDeleted; + + /// + Optional IPartialEntitlement.StartsAt => this.StartsAt; + + /// + Optional IPartialEntitlement.EndsAt => this.EndsAt; + + /// + Optional IPartialEntitlement.GuildID => this.GuildID; + + /// + Optional IPartialEntitlement.IsConsumed => this.IsConsumed; +} diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Monetization/IPartialEntitlement.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Monetization/IPartialEntitlement.cs new file mode 100644 index 0000000000..64f126e86c --- /dev/null +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Monetization/IPartialEntitlement.cs @@ -0,0 +1,64 @@ +// +// IPartialEntitlement.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; +using JetBrains.Annotations; +using Remora.Rest.Core; + +namespace Remora.Discord.API.Abstractions.Objects; + +/// +/// Represents partial information about a user's access to monetized application features. +/// +[PublicAPI] +public interface IPartialEntitlement +{ + /// + Optional ID { get; } + + /// + Optional SKUID { get; } + + /// + Optional ApplicationID { get; } + + /// + Optional UserID { get; } + + /// + Optional Type { get; } + + /// + Optional IsDeleted { get; } + + /// + Optional StartsAt { get; } + + /// + Optional EndsAt { get; } + + /// + Optional GuildID { get; } + + /// + Optional IsConsumed { get; } +} diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Monetization/ISKU.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Monetization/ISKU.cs new file mode 100644 index 0000000000..a41ab3e232 --- /dev/null +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Monetization/ISKU.cs @@ -0,0 +1,63 @@ +// +// ISKU.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using JetBrains.Annotations; +using Remora.Rest.Core; + +namespace Remora.Discord.API.Abstractions.Objects; + +/// +/// Represents a premium offering. SKU stands for Stock Keeping Unit. +/// +[PublicAPI] +public interface ISKU +{ + /// + /// Gets the ID of the SKU. + /// + Snowflake ID { get; } + + /// + /// Gets the type of the SKU. + /// + SKUType Type { get; } + + /// + /// Gets the ID of the parent application. + /// + Snowflake ApplicationID { get; } + + /// + /// Gets the customer-facing name of the premium offering. + /// + string Name { get; } + + /// + /// Gets the system-generated URL slug based on the SKU's name. + /// + string Slug { get; } + + /// + /// Gets the flags for this SKU. + /// + SKUFlags Flags { get; } +} diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Monetization/SKUFlags.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Monetization/SKUFlags.cs new file mode 100644 index 0000000000..2233a23280 --- /dev/null +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Monetization/SKUFlags.cs @@ -0,0 +1,49 @@ +// +// SKUFlags.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; +using JetBrains.Annotations; + +namespace Remora.Discord.API.Abstractions.Objects; + +/// +/// Enumerates various flags for an SKU. +/// +[Flags] +[PublicAPI] +public enum SKUFlags +{ + /// + /// The SKU is available for purchase. + /// + Available = 1 << 2, + + /// + /// A subscription purchased by a user and applied to a single server. + /// + GuildSubscription = 1 << 7, + + /// + /// A subscription purchased by a user for themselves. + /// + UserSubscription = 1 << 8 +} diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Monetization/SKUType.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Monetization/SKUType.cs new file mode 100644 index 0000000000..a1edc9e959 --- /dev/null +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Monetization/SKUType.cs @@ -0,0 +1,42 @@ +// +// SKUType.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using JetBrains.Annotations; + +namespace Remora.Discord.API.Abstractions.Objects; + +/// +/// Enumerates various SKU types. +/// +[PublicAPI] +public enum SKUType +{ + /// + /// A recurring subscription. + /// + Subscription = 5, + + /// + /// A system-generated group for each SKU. + /// + SubscriptionGroup = 6 +} diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Permissions/DiscordPermission.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Permissions/DiscordPermission.cs index 820bd6f5ec..d6979cfe37 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Objects/Permissions/DiscordPermission.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Permissions/DiscordPermission.cs @@ -188,7 +188,7 @@ public enum DiscordPermission ManageWebhooks = 29, /// - /// Allows management and editing of emojis, stickers, and soundboard sounds. + /// Allows for editing and deleting of emojis, stickers, and soundboard sounds created by all users. /// ManageGuildExpressions = 30, @@ -203,7 +203,7 @@ public enum DiscordPermission RequestToSpeak = 32, /// - /// Allows the user to manage scheduled events. + /// Allows for editing and deleting scheduled events created by all users. /// ManageEvents = 33, @@ -254,27 +254,23 @@ public enum DiscordPermission UseSoundboard = 42, /// - /// Allows for creating emojis, stickers, and soundboard sounds, independently of managing them. + /// Allows for creating emojis, stickers, and soundboard sounds, as well as editing and deleting those created by + /// the current user. /// CreateGuildExpressions = 43, /// - /// Allows the usage of custom soundboard sounds from other servers. + /// Allows for creating scheduled events, as well as editing and deleting those created by the current user. /// - UseExternalSounds = 45, + CreateEvents = 44, /// - /// Allows for sending voice messages. - /// - SendVoiceMessages = 46, - - /// - /// Allows for interaction with Clyde (AI). + /// Allows the usage of custom soundboard sounds from other servers. /// - UseClydeAi = 47, + UseExternalSounds = 45, /// - /// Allows for setting the status of a voice channel. + /// Allows for sending voice messages. /// - SetVoiceChannelStatus = 48 + SendVoiceMessages = 46 } diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Reactions/IReaction.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Reactions/IReaction.cs index 69025cb02d..42fd79cb90 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Objects/Reactions/IReaction.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Reactions/IReaction.cs @@ -20,6 +20,8 @@ // along with this program. If not, see . // +using System.Collections.Generic; +using System.Drawing; using JetBrains.Annotations; namespace Remora.Discord.API.Abstractions.Objects; @@ -31,17 +33,32 @@ namespace Remora.Discord.API.Abstractions.Objects; public interface IReaction { /// - /// Gets the number of times this emoji has been used to react. + /// Gets the total number of times this emoji has been used to react, including burst (super) reactions. /// int Count { get; } /// - /// Gets a value indicating whether the current user has reacted using this emoji. + /// Gets detailed information about the reaction counts. + /// + IReactionCountDetails CountDetails { get; } + + /// + /// Gets a value indicating whether the current user has added a reaction using this emoji. /// bool HasCurrentUserReacted { get; } + /// + /// Gets a value indicating whether the current user has added a burst (super) reaction using this emoji. + /// + bool HasCurrentUserBurstReacted { get; } + /// /// Gets the partial emoji information. /// IPartialEmoji Emoji { get; } + + /// + /// Gets the colors used for the super reaction. + /// + IReadOnlyList BurstColours { get; } } diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Reactions/IReactionCountDetails.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Reactions/IReactionCountDetails.cs new file mode 100644 index 0000000000..d5abf886f4 --- /dev/null +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Reactions/IReactionCountDetails.cs @@ -0,0 +1,42 @@ +// +// IReactionCountDetails.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using JetBrains.Annotations; + +namespace Remora.Discord.API.Abstractions.Objects; + +/// +/// Represents detailed information about a reaction count. +/// +[PublicAPI] +public interface IReactionCountDetails +{ + /// + /// Gets the number of burst (super) reactions that have been made with the associated emoji. + /// + int Burst { get; } + + /// + /// Gets the number of normal reactions that have been made with the associated emoji. + /// + int Normal { get; } +} diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Teams/ITeamMember.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Teams/ITeamMember.cs index ef16ccd5fa..b367d977a0 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Objects/Teams/ITeamMember.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Teams/ITeamMember.cs @@ -51,4 +51,9 @@ public interface ITeamMember /// Gets the user that's part of the team. /// IPartialUser User { get; } + + /// + /// Gets the user's role. + /// + TeamMemberRole Role { get; } } diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Teams/TeamMemberRole.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Teams/TeamMemberRole.cs new file mode 100644 index 0000000000..611858f98f --- /dev/null +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Teams/TeamMemberRole.cs @@ -0,0 +1,55 @@ +// +// TeamMemberRole.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +namespace Remora.Discord.API.Abstractions.Objects; + +/// +/// Enumerates various roles a team member can have. +/// +public enum TeamMemberRole +{ + /// + /// Owners are the most permissible role, and can take destructive, irreversible actions like deleting team-owned + /// apps or the team itself. Teams are limited to 1 owner. + /// + Owner, + + /// + /// Admins have similar access as owners, except they cannot take destructive actions on the team or team-owned + /// apps. + /// + Admin, + + /// + /// Developers can access information about team-owned apps, like the client secret or public key. They can also + /// take limited actions on team-owned apps, like configuring interaction endpoints or resetting the bot token. + /// Members with the Developer role *cannot* manage the team or its members, or take destructive actions on + /// team-owned apps. + /// + Developer, + + /// + /// Read-only members can access information about a team and any team-owned apps. Some examples include getting the + /// IDs of applications and exporting payout records. + /// + ReadOnly +} diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Users/IUser.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Users/IUser.cs index aff96206c9..39cbd4f7e1 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Objects/Users/IUser.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Users/IUser.cs @@ -55,7 +55,7 @@ public interface IUser : IPartialUser /// /// Gets the user's display name, if it is set. For bots, this is the application name. /// - new string? GlobalName { get; } + new Optional GlobalName { get; } /// /// Gets the user's avatar hash. diff --git a/Backend/Remora.Discord.API.Abstractions/API/Rest/IBulkBanResponse.cs b/Backend/Remora.Discord.API.Abstractions/API/Rest/IBulkBanResponse.cs new file mode 100644 index 0000000000..3baaf0779f --- /dev/null +++ b/Backend/Remora.Discord.API.Abstractions/API/Rest/IBulkBanResponse.cs @@ -0,0 +1,44 @@ +// +// IBulkBanResponse.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Collections.Generic; +using JetBrains.Annotations; +using Remora.Rest.Core; + +namespace Remora.Discord.API.Abstractions.Rest; + +/// +/// Represents a response to a bulk banning request. +/// +[PublicAPI] +public interface IBulkBanResponse +{ + /// + /// Gets the IDs of the users who were successfully banned. + /// + IReadOnlyList BannedUsers { get; } + + /// + /// Gets the IDs of the users who were not banned. + /// + IReadOnlyList FailedUsers { get; } +} diff --git a/Backend/Remora.Discord.API.Abstractions/API/Rest/IDiscordRestApplicationAPI.cs b/Backend/Remora.Discord.API.Abstractions/API/Rest/IDiscordRestApplicationAPI.cs index 662c0fe4e6..cb703bd09a 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Rest/IDiscordRestApplicationAPI.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Rest/IDiscordRestApplicationAPI.cs @@ -20,7 +20,9 @@ // along with this program. If not, see . // +using System; using System.Collections.Generic; +using System.IO; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; @@ -70,6 +72,8 @@ Task>> GetGlobalApplicationCommandsAsy /// The permissions required to execute the command. /// Whether this command is executable in DMs. /// Whether the command is age-restricted. + /// The installation contexts the command can be installed to. + /// The contexts in which the command is allowed to be run in. /// The cancellation token for this operation. /// A creation result which may or may not have succeeded. Task> CreateGlobalApplicationCommandAsync @@ -84,6 +88,8 @@ Task> CreateGlobalApplicationCommandAsync Optional defaultMemberPermissions = default, Optional dmPermission = default, Optional isNsfw = default, + Optional> allowedIntegrationTypes = default, + Optional> allowedContextTypes = default, CancellationToken ct = default ); @@ -128,6 +134,8 @@ Task> GetGlobalApplicationCommandAsync /// The permissions required to execute the command. /// Whether this command is executable in DMs. /// Whether this command is age-restricted. + /// The installation contexts the command can be installed to. + /// The contexts in which the command is allowed to be run in. /// The cancellation token for this operation. /// A creation result which may or may not have succeeded. Task> EditGlobalApplicationCommandAsync @@ -142,6 +150,8 @@ Task> EditGlobalApplicationCommandAsync Optional defaultMemberPermissions = default, Optional dmPermission = default, Optional isNsfw = default, + Optional> allowedIntegrationTypes = default, + Optional> allowedContextTypes = default, CancellationToken ct = default ); @@ -195,6 +205,8 @@ Task>> GetGuildApplicationCommandsAsyn /// The localized descriptions of the command. /// The permissions required to execute the command. /// Whether the command is age-restricted. + /// The installation contexts the command can be installed to. + /// The contexts in which the command is allowed to be run in. /// The cancellation token for this operation. /// A creation result which may or may not have succeeded. Task> CreateGuildApplicationCommandAsync @@ -209,6 +221,8 @@ Task> CreateGuildApplicationCommandAsync Optional?> descriptionLocalizations = default, Optional defaultMemberPermissions = default, Optional isNsfw = default, + Optional> allowedIntegrationTypes = default, + Optional> allowedContextTypes = default, CancellationToken ct = default ); @@ -257,6 +271,8 @@ Task> GetGuildApplicationCommandAsync /// The localized descriptions of the command. /// The permissions required to execute the command. /// Whether this command is age-restricted. + /// The installation contexts the command can be installed to. + /// The contexts in which the command is allowed to be run in. /// The cancellation token for this operation. /// A creation result which may or may not have succeeded. /// @@ -274,6 +290,8 @@ Task> EditGuildApplicationCommandAsync Optional?> descriptionLocalizations = default, Optional defaultMemberPermissions = default, Optional isNsfw = default, + Optional> allowedIntegrationTypes = default, + Optional> allowedContextTypes = default, CancellationToken ct = default ); @@ -379,4 +397,34 @@ Task>> UpdateApplicatio /// The cancellation token for this operation. /// The application object. Task> GetCurrentApplicationAsync(CancellationToken ct = default); + + /// + /// Edit properties of the application associated with the requesting bot user. + /// + /// The default custom authorization URL of the app. + /// The description of the app. + /// The role connections verification URL of the app. + /// The settings for the app's in-app authorization. + /// The new flags. + /// The new icon. + /// The new cover image. + /// The new interactions endpoint URL. + /// The new tags. + /// The new integration types. + /// The cancellation token for this operation. + /// The updated application. + Task> EditCurrentApplicationAsync + ( + Optional customInstallUrl = default, + Optional description = default, + Optional roleConnectionsVerificationUrl = default, + Optional installParams = default, + Optional flags = default, + Optional icon = default, + Optional coverImage = default, + Optional interactionsEndpointUrl = default, + Optional> tags = default, + Optional> integrationTypes = default, + CancellationToken ct = default + ); } diff --git a/Backend/Remora.Discord.API.Abstractions/API/Rest/IDiscordRestChannelAPI.cs b/Backend/Remora.Discord.API.Abstractions/API/Rest/IDiscordRestChannelAPI.cs index 9e6aea54d5..8b8ef96c4a 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Rest/IDiscordRestChannelAPI.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Rest/IDiscordRestChannelAPI.cs @@ -380,6 +380,52 @@ Task> ModifyForumChannelAsync CancellationToken ct = default ); + /// + /// Modifies the given media channel. + /// + /// The ID of the channel. + /// The new name of the channel. + /// The new position of the channel in the listing. + /// The new topic of the channel. Max 1024 characters (4096 for forums). + /// The new NSFW status of the channel. + /// The new rate limit per user. + /// The new permission overwrites. + /// The new parent category ID. + /// + /// The default time of inactivity after which threads in the channel are archived. + /// + /// The new channel flags. + /// + /// The set of tags that can be used in a forum channel. Only "name" is required to be set. + /// + /// The emoji to show in the add reaction button on threads in a forum. + /// + /// The initial to set on new threads in a forum channel. + /// + /// The default sort order of posts. + /// The reason to mark the action in the audit log with. + /// The cancellation token for this operation. + /// A modification result which may or may not have succeeded. + Task> ModifyMediaChannelAsync + ( + Snowflake channelID, + Optional name = default, + Optional position = default, + Optional topic = default, + Optional isNsfw = default, + Optional rateLimitPerUser = default, + Optional?> permissionOverwrites = default, + Optional parentID = default, + Optional defaultAutoArchiveDuration = default, + Optional flags = default, + Optional> availableTags = default, + Optional defaultReactionEmoji = default, + Optional defaultThreadRateLimitPerUser = default, + Optional defaultSortOrder = default, + Optional reason = default, + CancellationToken ct = default + ); + /// /// Deletes a channel by its ID. /// @@ -462,6 +508,7 @@ Task> GetChannelMessageAsync /// mentioned in this parameter will be deleted. /// /// The message flags. + /// Indicates whether messages should be deduplicated using the passed nonce. /// The cancellation token for this operation. /// A creation result which may or may not have succeeded. Task> CreateMessageAsync @@ -477,6 +524,7 @@ Task> CreateMessageAsync Optional> stickerIDs = default, Optional>> attachments = default, Optional flags = default, + Optional enforceNonce = default, CancellationToken ct = default ); diff --git a/Backend/Remora.Discord.API.Abstractions/API/Rest/IDiscordRestGuildAPI.cs b/Backend/Remora.Discord.API.Abstractions/API/Rest/IDiscordRestGuildAPI.cs index 8d84bc1e62..9fe76b3b58 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Rest/IDiscordRestGuildAPI.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Rest/IDiscordRestGuildAPI.cs @@ -205,6 +205,9 @@ Task>> GetGuildChannelsAsync /// The tags that can be used in a forum channel. /// The default sort order of posts. /// The default layout of forums. + /// + /// The initial rate limit per user to set on newly created threads in a channel. + /// /// The reason to mark the action in the audit log with. /// The cancellation token for this operation. /// A creation result which may or may not have succeeded. @@ -228,6 +231,7 @@ Task> CreateGuildChannelAsync Optional?> availableTags = default, Optional defaultSortOrder = default, Optional defaultForumLayout = default, + Optional defaultThreadRateLimitPerUser = default, Optional reason = default, CancellationToken ct = default ); @@ -244,6 +248,9 @@ Task> CreateGuildChannelAsync /// The ID of the parent category of the new channel. /// Whether the new channel is NSFW. /// The default auto archival duration for threads. + /// + /// The initial rate limit per user to set on newly created threads in a channel. + /// /// The reason to mark the action in the audit log with. /// The cancellation token for this operation. /// A creation result which may or may not have succeeded. @@ -258,6 +265,7 @@ Task> CreateGuildTextChannelAsync Optional parentID = default, Optional isNsfw = default, Optional defaultAutoArchiveDuration = default, + Optional defaultThreadRateLimitPerUser = default, Optional reason = default, CancellationToken ct = default ); @@ -273,6 +281,9 @@ Task> CreateGuildTextChannelAsync /// The ID of the parent category of the new channel. /// Whether the new channel is NSFW. /// The default auto archival duration for threads. + /// + /// The initial rate limit per user to set on newly created threads in a channel. + /// /// The reason to mark the action in the audit log with. /// The cancellation token for this operation. /// A creation result which may or may not have succeeded. @@ -286,6 +297,7 @@ Task> CreateGuildAnnouncementChannelAsync Optional parentID = default, Optional isNsfw = default, Optional defaultAutoArchiveDuration = default, + Optional defaultThreadRateLimitPerUser = default, Optional reason = default, CancellationToken ct = default ); @@ -296,6 +308,7 @@ Task> CreateGuildAnnouncementChannelAsync /// The ID of the guild. /// The name of the new channel. /// The topic of the new channel. + /// The number of seconds a user has to wait between messages. /// The sorting position of the new channel. /// The permission overwrites of the new channel. /// The ID of the parent category of the new channel. @@ -305,6 +318,9 @@ Task> CreateGuildAnnouncementChannelAsync /// The tags that can be used in a forum channel. /// The default sort order of posts. /// The default layout of forums. + /// + /// The initial rate limit per user to set on newly created threads in a channel. + /// /// The reason to mark the action in the audit log with. /// The cancellation token for this operation. /// A creation result which may or may not have succeeded. @@ -313,6 +329,7 @@ Task> CreateGuildForumChannelAsync Snowflake guildID, string name, Optional topic = default, + Optional rateLimitPerUser = default, Optional position = default, Optional?> permissionOverwrites = default, Optional parentID = default, @@ -322,6 +339,45 @@ Task> CreateGuildForumChannelAsync Optional?> availableTags = default, Optional defaultSortOrder = default, Optional defaultForumLayout = default, + Optional defaultThreadRateLimitPerUser = default, + Optional reason = default, + CancellationToken ct = default + ); + + /// + /// Creates a new forum channel for the guild. + /// + /// The ID of the guild. + /// The name of the new channel. + /// The topic of the new channel. + /// The number of seconds a user has to wait between messages. + /// The sorting position of the new channel. + /// The permission overwrites of the new channel. + /// The ID of the parent category of the new channel. + /// The default auto archival duration for threads. + /// The default emoji to show in reaction buttons of forum posts. + /// The tags that can be used in a forum channel. + /// The default sort order of posts. + /// + /// The initial rate limit per user to set on newly created threads in a channel. + /// + /// The reason to mark the action in the audit log with. + /// The cancellation token for this operation. + /// A creation result which may or may not have succeeded. + Task> CreateGuildMediaChannelAsync + ( + Snowflake guildID, + string name, + Optional topic = default, + Optional rateLimitPerUser = default, + Optional position = default, + Optional?> permissionOverwrites = default, + Optional parentID = default, + Optional defaultAutoArchiveDuration = default, + Optional defaultReactionEmoji = default, + Optional?> availableTags = default, + Optional defaultSortOrder = default, + Optional defaultThreadRateLimitPerUser = default, Optional reason = default, CancellationToken ct = default ); @@ -667,6 +723,26 @@ Task RemoveGuildBanAsync CancellationToken ct = default ); + /// + /// Bans up to 200 users from the given guild. + /// + /// The ID of the guild. + /// The IDs of the users to ban. + /// + /// The number of seconds to delete messages for (0-604800). Defaults to 0. + /// + /// The reason to mark the action in the audit log with. + /// The cancellation token for this operation. + /// The bulk banning response. + Task> BulkGuildBanAsync + ( + Snowflake guildID, + IReadOnlyList userIDs, + Optional deleteMessageSeconds = default, + Optional reason = default, + CancellationToken ct = default + ); + /// /// Gets the roles in the guild. /// diff --git a/Backend/Remora.Discord.API.Abstractions/API/Rest/IDiscordRestMonetizationAPI.cs b/Backend/Remora.Discord.API.Abstractions/API/Rest/IDiscordRestMonetizationAPI.cs new file mode 100644 index 0000000000..a3a75995a9 --- /dev/null +++ b/Backend/Remora.Discord.API.Abstractions/API/Rest/IDiscordRestMonetizationAPI.cs @@ -0,0 +1,118 @@ +// +// IDiscordRestMonetizationAPI.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Remora.Discord.API.Abstractions.Objects; +using Remora.Rest.Core; +using Remora.Results; + +namespace Remora.Discord.API.Abstractions.Rest; + +/// +/// Represents the Discord Monetization API. +/// +[PublicAPI] +public interface IDiscordRestMonetizationAPI +{ + /// + /// Gets all entitlements for a given application. + /// + /// The ID of the application. + /// The ID of the user to limit the search to. + /// The SKUs to limit the search to. + /// The entitlement to search before. + /// The entitlement to search after. + /// The maximum number of entitlements to return (1-100). Defaults to 100. + /// The ID of the guild to limit the search to. + /// Whether to exclude expired entitlements. + /// The cancellation token for this operation. + /// The entitlements. + Task>> ListEntitlementsAsync + ( + Snowflake applicationID, + Optional userID = default, + Optional> skuIDs = default, + Optional before = default, + Optional after = default, + Optional limit = default, + Optional guildID = default, + Optional excludeEnded = default, + CancellationToken ct = default + ); + + /// + /// Marks the given one-time purchase item as consumed. + /// + /// The ID of the application. + /// The ID of the entitlement. + /// The cancellation token for this operation. + /// A value representing the result of the operation. + Task ConsumeEntitlementAsync + ( + Snowflake applicationID, + Snowflake entitlementID, + CancellationToken ct = default + ); + + /// + /// Creates a test entitlement to a given SKU for a given guild or user. + /// + /// The ID of the application. + /// The ID of the SKU to grant the entitlement for. + /// The ID of the guild or user to grant the entitlement to. + /// The type of the owner. + /// The cancellation token for this operation. + /// The test entitlement. + Task> CreateTestEntitlementAsync + ( + Snowflake applicationID, + Snowflake skuID, + Snowflake ownerID, + EntitlementOwnerType ownerType, + CancellationToken ct = default + ); + + /// + /// Deletes the given test entitlement. + /// + /// The ID of the application. + /// The ID of the entitlement. + /// The cancellation token for this operation. + /// A value representing the result of the operation. + Task DeleteTestEntitlementAsync + ( + Snowflake applicationID, + Snowflake entitlementID, + CancellationToken ct = default + ); + + /// + /// Gets all SKUs for the given application. + /// + /// The ID of the application. + /// The cancellation token for this operation. + /// The SKUs. + Task>> ListSKUsAsync(Snowflake applicationID, CancellationToken ct = default); +} diff --git a/Backend/Remora.Discord.API.Abstractions/API/Rest/IDiscordRestStageInstanceAPI.cs b/Backend/Remora.Discord.API.Abstractions/API/Rest/IDiscordRestStageInstanceAPI.cs index 5242a59c5c..0d4241261d 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Rest/IDiscordRestStageInstanceAPI.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Rest/IDiscordRestStageInstanceAPI.cs @@ -44,6 +44,7 @@ public interface IDiscordRestStageInstanceAPI /// /// Indicates whether @everyone should be notified that a stage instance has started. /// + /// The scheduled event associated with this stage instance. /// The reason to mark the action in the audit log with. /// The cancellation token for this operation. /// A result which may or may not have succeeded. @@ -53,6 +54,7 @@ Task> CreateStageInstanceAsync string topic, Optional privacyLevel = default, Optional sendStartNotification = default, + Optional guildScheduledEventID = default, Optional reason = default, CancellationToken ct = default ); diff --git a/Backend/Remora.Discord.API.Abstractions/API/Rest/IDiscordRestUserAPI.cs b/Backend/Remora.Discord.API.Abstractions/API/Rest/IDiscordRestUserAPI.cs index 7b0813f61e..7fbcac6e1e 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Rest/IDiscordRestUserAPI.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Rest/IDiscordRestUserAPI.cs @@ -137,7 +137,7 @@ Task> CreateDMAsync /// /// The cancellation token for this operation. /// A retrieval result which may or may not have succeeded. - Task>> GetUserConnectionsAsync + Task>> GetCurrentUserConnectionsAsync ( CancellationToken ct = default ); @@ -152,7 +152,7 @@ Task>> GetUserConnectionsAsync /// The ID of the application. /// The cancellation token for this operation. /// A retrieval result which may or may not have succeeded. - Task> GetUserApplicationRoleConnectionAsync + Task> GetCurrentUserApplicationRoleConnectionAsync ( Snowflake applicationID, CancellationToken ct = default @@ -174,7 +174,7 @@ Task> GetUserApplicationRoleConnectionAsync /// /// The cancellation token for this operation. /// A retrieval result which may or may not have succeeded. - Task> UpdateUserApplicationRoleConnectionAsync + Task> UpdateCurrentUserApplicationRoleConnectionAsync ( Snowflake applicationID, Optional platformName = default, diff --git a/Backend/Remora.Discord.API.Abstractions/API/Rest/IDiscordRestWebhookAPI.cs b/Backend/Remora.Discord.API.Abstractions/API/Rest/IDiscordRestWebhookAPI.cs index aec612e2a9..6e847b7af9 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Rest/IDiscordRestWebhookAPI.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Rest/IDiscordRestWebhookAPI.cs @@ -225,6 +225,7 @@ Task DeleteWebhookWithTokenAsync /// /// The message flags. /// The name of the forum thread to create. + /// The tags to apply to the thread (requires the webhook channel to be a forum or media channel). /// The cancellation token for this operation. /// /// A result which may or may not have succeeded. The returned message is null if @@ -246,6 +247,7 @@ Task DeleteWebhookWithTokenAsync Optional>> attachments = default, Optional flags = default, Optional threadName = default, + Optional> appliedTags = default, CancellationToken ct = default ); diff --git a/Backend/Remora.Discord.API.Abstractions/API/VoiceGateway/Commands/IVoiceGatewayCommand.cs b/Backend/Remora.Discord.API.Abstractions/API/VoiceGateway/Commands/IVoiceGatewayCommand.cs index 2a89e7fbaf..f825a640bd 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/VoiceGateway/Commands/IVoiceGatewayCommand.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/VoiceGateway/Commands/IVoiceGatewayCommand.cs @@ -28,6 +28,4 @@ namespace Remora.Discord.API.Abstractions.VoiceGateway.Commands; /// Acts as a marker interface for voice gateway commands. /// [PublicAPI] -public interface IVoiceGatewayCommand : IVoiceGatewayPayloadData -{ -} +public interface IVoiceGatewayCommand : IVoiceGatewayPayloadData; diff --git a/Backend/Remora.Discord.API.Abstractions/API/VoiceGateway/Events/IVoiceGatewayEvent.cs b/Backend/Remora.Discord.API.Abstractions/API/VoiceGateway/Events/IVoiceGatewayEvent.cs index ffabe8c501..a124614f7b 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/VoiceGateway/Events/IVoiceGatewayEvent.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/VoiceGateway/Events/IVoiceGatewayEvent.cs @@ -28,6 +28,4 @@ namespace Remora.Discord.API.Abstractions.VoiceGateway.Events; /// Acts as a marker interface for voice gateway events. /// [PublicAPI] -public interface IVoiceGatewayEvent : IVoiceGatewayPayloadData -{ -} +public interface IVoiceGatewayEvent : IVoiceGatewayPayloadData; diff --git a/Backend/Remora.Discord.API.Abstractions/API/VoiceGateway/Events/IVoiceResumed.cs b/Backend/Remora.Discord.API.Abstractions/API/VoiceGateway/Events/IVoiceResumed.cs index 969b34b717..b32f697df7 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/VoiceGateway/Events/IVoiceResumed.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/VoiceGateway/Events/IVoiceResumed.cs @@ -28,6 +28,4 @@ namespace Remora.Discord.API.Abstractions.VoiceGateway.Events; /// Represents an acknowledged session resume. /// [PublicAPI] -public interface IVoiceResumed : IVoiceGatewayEvent -{ -} +public interface IVoiceResumed : IVoiceGatewayEvent; diff --git a/Backend/Remora.Discord.API.Abstractions/API/VoiceGateway/IVoiceGatewayPayloadData.cs b/Backend/Remora.Discord.API.Abstractions/API/VoiceGateway/IVoiceGatewayPayloadData.cs index 30775449eb..5c3cd2f86b 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/VoiceGateway/IVoiceGatewayPayloadData.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/VoiceGateway/IVoiceGatewayPayloadData.cs @@ -28,6 +28,4 @@ namespace Remora.Discord.API.Abstractions.VoiceGateway; /// Represents a marker interface for voice gateway payload data. /// [PublicAPI] -public interface IVoiceGatewayPayloadData -{ -} +public interface IVoiceGatewayPayloadData; diff --git a/Backend/Remora.Discord.API.Abstractions/Remora.Discord.API.Abstractions.csproj b/Backend/Remora.Discord.API.Abstractions/Remora.Discord.API.Abstractions.csproj index 890d15831c..bf9d4f5165 100644 --- a/Backend/Remora.Discord.API.Abstractions/Remora.Discord.API.Abstractions.csproj +++ b/Backend/Remora.Discord.API.Abstractions/Remora.Discord.API.Abstractions.csproj @@ -1,23 +1,13 @@ - 79.0.0 + 81.0.0 Interface definitions of Discord's API - BREAKING: Implement support for join and mention raid protection. - Add external sound permission. - Add several new error codes. - BREAKING: Add RateLimitPerUser to Voice and Stage channels. - BREAKING: Implement new username field. - BREAKING: Implement role flags. - BREAKING: Implement attachment flags. - BREAKING: Implement onboarding mode. - BREAKING: Implement MessageAuthorID. - BREAKING: Add more audit log events. - BREAKING: Add defaultForumLayout parameter. - BREAKING: Implement approximate guild count and partial guild. - BREAKING: Implement avatar decorations. - BREAKING: Implement with_counts for user guild endpoint. + BREAKING: Revert "Remove global_name from User objects" + BREAKING: fix: wrap GlobalName in Optional + Update permissions. + BREAKING: Implement applied tags for webhooks. @@ -127,6 +117,18 @@ IDiscordRestGuildAPI.cs + + IMessageInteractionMetadata.cs + + + IMessageInteractionMetadata.cs + + + IMessageInteractionMetadata.cs + + + IDiscordRestGuildAPI.cs + diff --git a/Backend/Remora.Discord.API.Abstractions/Remora.Discord.API.Abstractions.csproj.DotSettings b/Backend/Remora.Discord.API.Abstractions/Remora.Discord.API.Abstractions.csproj.DotSettings index 2f46ed7149..98f693855d 100644 --- a/Backend/Remora.Discord.API.Abstractions/Remora.Discord.API.Abstractions.csproj.DotSettings +++ b/Backend/Remora.Discord.API.Abstractions/Remora.Discord.API.Abstractions.csproj.DotSettings @@ -13,6 +13,7 @@ True True True + True True True True @@ -42,6 +43,7 @@ True True True + True True True True diff --git a/Backend/Remora.Discord.API.Abstractions/Results/DiscordError.cs b/Backend/Remora.Discord.API.Abstractions/Results/DiscordError.cs index 9271f79946..5a133ba714 100644 --- a/Backend/Remora.Discord.API.Abstractions/Results/DiscordError.cs +++ b/Backend/Remora.Discord.API.Abstractions/Results/DiscordError.cs @@ -566,6 +566,11 @@ public enum DiscordError /// TagRequired = 40067, + /// + /// An entitlement has already been granted for this resource. + /// + EntitlementAlreadyGranted = 40074, + /// /// Missing access. /// @@ -743,6 +748,11 @@ public enum DiscordError /// InvalidGuild = 50055, + /// + /// The SKU is invalid. + /// + InvalidSKU = 50057, + /// /// The request origin is invalid. /// @@ -908,6 +918,11 @@ public enum DiscordError /// ReactionBlocked = 90001, + /// + /// The user cannot use burst (super) reactions. + /// + UserCannotUseBurstReactions = 90002, + /// /// The application isn't available yet. Try again later. /// @@ -1031,5 +1046,10 @@ public enum DiscordError /// /// Onboarding cannot be updated while requirements are not met. /// - CannotUpdateOnboarding = 350001 + CannotUpdateOnboarding = 350001, + + /// + /// Failed to bulk ban the given set of users. + /// + FailedToBanUsers = 500000 } diff --git a/Backend/Remora.Discord.API/API/Gateway/Events/AutoModeration/AutoModerationRuleCreate.cs b/Backend/Remora.Discord.API/API/Gateway/Events/AutoModeration/AutoModerationRuleCreate.cs index c1c8be8e3f..1e65c6620b 100644 --- a/Backend/Remora.Discord.API/API/Gateway/Events/AutoModeration/AutoModerationRuleCreate.cs +++ b/Backend/Remora.Discord.API/API/Gateway/Events/AutoModeration/AutoModerationRuleCreate.cs @@ -57,4 +57,5 @@ IReadOnlyList ExemptChannels IsEnabled, ExemptRoles, ExemptChannels -), IAutoModerationRuleCreate; +), +IAutoModerationRuleCreate; diff --git a/Backend/Remora.Discord.API/API/Gateway/Events/AutoModeration/AutoModerationRuleDelete.cs b/Backend/Remora.Discord.API/API/Gateway/Events/AutoModeration/AutoModerationRuleDelete.cs index e1b14afe56..2e015cfc1f 100644 --- a/Backend/Remora.Discord.API/API/Gateway/Events/AutoModeration/AutoModerationRuleDelete.cs +++ b/Backend/Remora.Discord.API/API/Gateway/Events/AutoModeration/AutoModerationRuleDelete.cs @@ -57,4 +57,5 @@ IReadOnlyList ExemptChannels IsEnabled, ExemptRoles, ExemptChannels -), IAutoModerationRuleDelete; +), +IAutoModerationRuleDelete; diff --git a/Backend/Remora.Discord.API/API/Gateway/Events/AutoModeration/AutoModerationRuleUpdate.cs b/Backend/Remora.Discord.API/API/Gateway/Events/AutoModeration/AutoModerationRuleUpdate.cs index 4c656d4f46..e14473bfdf 100644 --- a/Backend/Remora.Discord.API/API/Gateway/Events/AutoModeration/AutoModerationRuleUpdate.cs +++ b/Backend/Remora.Discord.API/API/Gateway/Events/AutoModeration/AutoModerationRuleUpdate.cs @@ -57,4 +57,5 @@ IReadOnlyList ExemptChannels IsEnabled, ExemptRoles, ExemptChannels -), IAutoModerationRuleUpdate; +), +IAutoModerationRuleUpdate; diff --git a/Backend/Remora.Discord.API/API/Gateway/Events/Interactions/InteractionCreate.cs b/Backend/Remora.Discord.API/API/Gateway/Events/Interactions/InteractionCreate.cs index 19aba8836f..b7a411f20f 100644 --- a/Backend/Remora.Discord.API/API/Gateway/Events/Interactions/InteractionCreate.cs +++ b/Backend/Remora.Discord.API/API/Gateway/Events/Interactions/InteractionCreate.cs @@ -20,6 +20,7 @@ // along with this program. If not, see . // +using System.Collections.Generic; using JetBrains.Annotations; using OneOf; using Remora.Discord.API.Abstractions.Gateway.Events; @@ -45,8 +46,11 @@ public record InteractionCreate Optional User, string Token, int Version, - Optional Message = default, - Optional AppPermissions = default, - Optional Locale = default, - Optional GuildLocale = default + Optional Message, + IDiscordPermissionSet AppPermissions, + Optional Locale, + Optional GuildLocale, + IReadOnlyList Entitlements, + Optional Context, + Optional> AuthorizingIntegrationOwners ) : IInteractionCreate; diff --git a/Backend/Remora.Discord.API/API/Gateway/Events/Messages/MessageCreate.cs b/Backend/Remora.Discord.API/API/Gateway/Events/Messages/MessageCreate.cs index c035f34c45..4ff3e7c950 100644 --- a/Backend/Remora.Discord.API/API/Gateway/Events/Messages/MessageCreate.cs +++ b/Backend/Remora.Discord.API/API/Gateway/Events/Messages/MessageCreate.cs @@ -23,6 +23,7 @@ using System; using System.Collections.Generic; using JetBrains.Annotations; +using OneOf; using Remora.Discord.API.Abstractions.Gateway.Events; using Remora.Discord.API.Abstractions.Objects; using Remora.Rest.Core; @@ -63,5 +64,7 @@ public record MessageCreate Optional Thread = default, Optional> Components = default, Optional> StickerItems = default, - Optional Position = default + Optional Position = default, + Optional Resolved = default, + Optional> InteractionMetadata = default ) : IMessageCreate; diff --git a/Backend/Remora.Discord.API/API/Gateway/Events/Messages/MessageUpdate.cs b/Backend/Remora.Discord.API/API/Gateway/Events/Messages/MessageUpdate.cs index f97f7c887e..d3d5591ccf 100644 --- a/Backend/Remora.Discord.API/API/Gateway/Events/Messages/MessageUpdate.cs +++ b/Backend/Remora.Discord.API/API/Gateway/Events/Messages/MessageUpdate.cs @@ -23,6 +23,7 @@ using System; using System.Collections.Generic; using JetBrains.Annotations; +using OneOf; using Remora.Discord.API.Abstractions.Gateway.Events; using Remora.Discord.API.Abstractions.Objects; using Remora.Rest.Core; @@ -63,5 +64,7 @@ public record MessageUpdate Optional Thread = default, Optional> Components = default, Optional> StickerItems = default, - Optional Position = default + Optional Position = default, + Optional Resolved = default, + Optional> InteractionMetadata = default ) : IMessageUpdate; diff --git a/Backend/Remora.Discord.API/API/Gateway/Events/Monetization/EntitlementCreate.cs b/Backend/Remora.Discord.API/API/Gateway/Events/Monetization/EntitlementCreate.cs new file mode 100644 index 0000000000..a46b4eac32 --- /dev/null +++ b/Backend/Remora.Discord.API/API/Gateway/Events/Monetization/EntitlementCreate.cs @@ -0,0 +1,45 @@ +// +// EntitlementCreate.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; +using JetBrains.Annotations; +using Remora.Discord.API.Abstractions.Gateway.Events; +using Remora.Discord.API.Abstractions.Objects; +using Remora.Rest.Core; + +namespace Remora.Discord.API.Gateway.Events; + +/// +[PublicAPI] +public record EntitlementCreate +( + Snowflake ID, + Snowflake SKUID, + Snowflake ApplicationID, + Optional UserID, + EntitlementType Type, + bool IsDeleted, + Optional StartsAt = default, + Optional EndsAt = default, + Optional GuildID = default, + Optional IsConsumed = default +) : IEntitlementCreate; diff --git a/Backend/Remora.Discord.API/API/Gateway/Events/Monetization/EntitlementDelete.cs b/Backend/Remora.Discord.API/API/Gateway/Events/Monetization/EntitlementDelete.cs new file mode 100644 index 0000000000..50b5ee4743 --- /dev/null +++ b/Backend/Remora.Discord.API/API/Gateway/Events/Monetization/EntitlementDelete.cs @@ -0,0 +1,45 @@ +// +// EntitlementDelete.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; +using JetBrains.Annotations; +using Remora.Discord.API.Abstractions.Gateway.Events; +using Remora.Discord.API.Abstractions.Objects; +using Remora.Rest.Core; + +namespace Remora.Discord.API.Gateway.Events; + +/// +[PublicAPI] +public record EntitlementDelete +( + Snowflake ID, + Snowflake SKUID, + Snowflake ApplicationID, + Optional UserID, + EntitlementType Type, + bool IsDeleted, + Optional StartsAt = default, + Optional EndsAt = default, + Optional GuildID = default, + Optional IsConsumed = default +) : IEntitlementDelete; diff --git a/Backend/Remora.Discord.API/API/Gateway/Events/Monetization/EntitlementUpdate.cs b/Backend/Remora.Discord.API/API/Gateway/Events/Monetization/EntitlementUpdate.cs new file mode 100644 index 0000000000..b0f6dcba37 --- /dev/null +++ b/Backend/Remora.Discord.API/API/Gateway/Events/Monetization/EntitlementUpdate.cs @@ -0,0 +1,45 @@ +// +// EntitlementUpdate.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; +using JetBrains.Annotations; +using Remora.Discord.API.Abstractions.Gateway.Events; +using Remora.Discord.API.Abstractions.Objects; +using Remora.Rest.Core; + +namespace Remora.Discord.API.Gateway.Events; + +/// +[PublicAPI] +public record EntitlementUpdate +( + Snowflake ID, + Snowflake SKUID, + Snowflake ApplicationID, + Optional UserID, + EntitlementType Type, + bool IsDeleted, + Optional StartsAt = default, + Optional EndsAt = default, + Optional GuildID = default, + Optional IsConsumed = default +) : IEntitlementUpdate; diff --git a/Backend/Remora.Discord.API/API/Gateway/Events/Users/UserUpdate.cs b/Backend/Remora.Discord.API/API/Gateway/Events/Users/UserUpdate.cs index 7d1c29b2a9..c3f42e185b 100644 --- a/Backend/Remora.Discord.API/API/Gateway/Events/Users/UserUpdate.cs +++ b/Backend/Remora.Discord.API/API/Gateway/Events/Users/UserUpdate.cs @@ -35,7 +35,7 @@ public record UserUpdate Snowflake ID, string Username, ushort Discriminator, - string? GlobalName, + Optional GlobalName, IImageHash? Avatar, Optional IsBot = default, Optional IsSystem = default, diff --git a/Backend/Remora.Discord.API/API/Objects/Applications/Application.cs b/Backend/Remora.Discord.API/API/Objects/Applications/Application.cs index a26fb30b18..11cf2e040c 100644 --- a/Backend/Remora.Discord.API/API/Objects/Applications/Application.cs +++ b/Backend/Remora.Discord.API/API/Objects/Applications/Application.cs @@ -41,11 +41,13 @@ public record Application Optional> RPCOrigins, bool IsBotPublic, bool DoesBotRequireCodeGrant, + Optional Bot, Optional TermsOfServiceURL, Optional PrivacyPolicyURL, Optional Owner, string VerifyKey, ITeam? Team, + IReadOnlyDictionary IntegrationTypesConfig, Optional GuildID = default, Optional Guild = default, Optional PrimarySKUID = default, @@ -53,8 +55,10 @@ public record Application Optional CoverImage = default, Optional Flags = default, Optional ApproximateGuildCount = default, + Optional> RedirectUris = default, + Optional InteractionsEndpointUrl = default, + Optional RoleConnectionsVerificationUrl = default, Optional> Tags = default, Optional InstallParams = default, - Optional CustomInstallUrl = default, - Optional RoleConnectionsVerificationUrl = default + Optional CustomInstallUrl = default ) : IApplication; diff --git a/Backend/Remora.Discord.API/API/Objects/Applications/PartialApplication.cs b/Backend/Remora.Discord.API/API/Objects/Applications/PartialApplication.cs index 7f19e039a6..58726b36e3 100644 --- a/Backend/Remora.Discord.API/API/Objects/Applications/PartialApplication.cs +++ b/Backend/Remora.Discord.API/API/Objects/Applications/PartialApplication.cs @@ -41,6 +41,7 @@ public record PartialApplication Optional> RPCOrigins = default, Optional IsBotPublic = default, Optional DoesBotRequireCodeGrant = default, + Optional Bot = default, Optional TermsOfServiceURL = default, Optional PrivacyPolicyURL = default, Optional Owner = default, @@ -53,8 +54,11 @@ public record PartialApplication Optional CoverImage = default, Optional Flags = default, Optional ApproximateGuildCount = default, + Optional> RedirectUris = default, + Optional InteractionsEndpointUrl = default, + Optional RoleConnectionsVerificationUrl = default, Optional> Tags = default, Optional InstallParams = default, Optional CustomInstallUrl = default, - Optional RoleConnectionsVerificationUrl = default + Optional> IntegrationTypesConfig = default ) : IPartialApplication; diff --git a/Backend/Remora.Discord.API/API/Objects/AuditLogs/OptionalAuditEntryInfo.cs b/Backend/Remora.Discord.API/API/Objects/AuditLogs/OptionalAuditEntryInfo.cs index 5446096204..180fc474c6 100644 --- a/Backend/Remora.Discord.API/API/Objects/AuditLogs/OptionalAuditEntryInfo.cs +++ b/Backend/Remora.Discord.API/API/Objects/AuditLogs/OptionalAuditEntryInfo.cs @@ -42,5 +42,6 @@ public record OptionalAuditEntryInfo Optional MembersRemoved = default, Optional MessageID = default, Optional RoleName = default, - Optional Type = default + Optional Type = default, + Optional IntegrationType = default ) : IOptionalAuditEntryInfo; diff --git a/Backend/Remora.Discord.API/API/Objects/Interactions/ApplicationCommandInteractionMetadata.cs b/Backend/Remora.Discord.API/API/Objects/Interactions/ApplicationCommandInteractionMetadata.cs new file mode 100644 index 0000000000..83714785d2 --- /dev/null +++ b/Backend/Remora.Discord.API/API/Objects/Interactions/ApplicationCommandInteractionMetadata.cs @@ -0,0 +1,40 @@ +// +// ApplicationCommandInteractionMetadata.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Collections.Generic; +using JetBrains.Annotations; +using Remora.Discord.API.Abstractions.Objects; +using Remora.Rest.Core; + +namespace Remora.Discord.API.Objects; + +/// +[PublicAPI] +public record ApplicationCommandInteractionMetadata +( + Snowflake ID, + Snowflake UserID, + InteractionType Type, + Optional OriginalResponseMessageID, + IReadOnlyDictionary AuthorizingIntegrationOwners, + string Name +) : IApplicationCommandInteractionMetadata; diff --git a/Backend/Remora.Discord.API/API/Objects/Interactions/ApplicationCommands/ApplicationCommand.cs b/Backend/Remora.Discord.API/API/Objects/Interactions/ApplicationCommands/ApplicationCommand.cs index fd7a81b558..d909a12895 100644 --- a/Backend/Remora.Discord.API/API/Objects/Interactions/ApplicationCommands/ApplicationCommand.cs +++ b/Backend/Remora.Discord.API/API/Objects/Interactions/ApplicationCommands/ApplicationCommand.cs @@ -47,5 +47,7 @@ public record ApplicationCommand Optional DescriptionLocalized = default, IDiscordPermissionSet? DefaultMemberPermissions = default, Optional DMPermission = default, - Optional IsNsfw = default + Optional IsNsfw = default, + Optional> IntegrationTypes = default, + Optional> Contexts = default ) : IApplicationCommand; diff --git a/Backend/Remora.Discord.API/API/Objects/Interactions/ApplicationCommands/BulkApplicationCommandData.cs b/Backend/Remora.Discord.API/API/Objects/Interactions/ApplicationCommands/BulkApplicationCommandData.cs index 9c19c5c6f0..97df83b703 100644 --- a/Backend/Remora.Discord.API/API/Objects/Interactions/ApplicationCommands/BulkApplicationCommandData.cs +++ b/Backend/Remora.Discord.API/API/Objects/Interactions/ApplicationCommands/BulkApplicationCommandData.cs @@ -40,5 +40,7 @@ public record BulkApplicationCommandData Optional?> DescriptionLocalizations = default, IDiscordPermissionSet? DefaultMemberPermissions = default, Optional DMPermission = default, - Optional IsNsfw = default + Optional IsNsfw = default, + Optional> IntegrationTypes = default, + Optional> Contexts = default ) : IBulkApplicationCommandData; diff --git a/Backend/Remora.Discord.API/API/Objects/Interactions/ApplicationIntegrationTypeConfig.cs b/Backend/Remora.Discord.API/API/Objects/Interactions/ApplicationIntegrationTypeConfig.cs new file mode 100644 index 0000000000..4be5c8f345 --- /dev/null +++ b/Backend/Remora.Discord.API/API/Objects/Interactions/ApplicationIntegrationTypeConfig.cs @@ -0,0 +1,30 @@ +// +// ApplicationIntegrationTypeConfig.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using JetBrains.Annotations; +using Remora.Discord.API.Abstractions.Objects; + +namespace Remora.Discord.API.Objects; + +/// +[PublicAPI] +public record ApplicationIntegrationTypeConfig(IApplicationOAuth2InstallParams OAuth2InstallParams) : IApplicationIntegrationTypeConfig; diff --git a/Backend/Remora.Discord.API/API/Objects/Interactions/ApplicationOAuth2InstallParams.cs b/Backend/Remora.Discord.API/API/Objects/Interactions/ApplicationOAuth2InstallParams.cs new file mode 100644 index 0000000000..ef86f77243 --- /dev/null +++ b/Backend/Remora.Discord.API/API/Objects/Interactions/ApplicationOAuth2InstallParams.cs @@ -0,0 +1,31 @@ +// +// ApplicationOAuth2InstallParams.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Collections.Generic; +using JetBrains.Annotations; +using Remora.Discord.API.Abstractions.Objects; + +namespace Remora.Discord.API.Objects; + +/// +[PublicAPI] +public record ApplicationOAuth2InstallParams(IDiscordPermissionSet Permissions, IReadOnlyList Scopes) : IApplicationOAuth2InstallParams; diff --git a/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/ChannelSelectComponent.cs b/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/ChannelSelectComponent.cs index 7dfa7d50e8..3cc6a90edc 100644 --- a/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/ChannelSelectComponent.cs +++ b/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/ChannelSelectComponent.cs @@ -36,7 +36,8 @@ public record ChannelSelectComponent Optional Placeholder = default, Optional MinValues = default, Optional MaxValues = default, - Optional IsDisabled = default + Optional IsDisabled = default, + Optional> DefaultValues = default ) : IChannelSelectComponent { /// diff --git a/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/MentionableSelectComponent.cs b/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/MentionableSelectComponent.cs index 1e8f5ef79b..1b7714b3f4 100644 --- a/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/MentionableSelectComponent.cs +++ b/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/MentionableSelectComponent.cs @@ -20,6 +20,7 @@ // along with this program. If not, see . // +using System.Collections.Generic; using JetBrains.Annotations; using Remora.Discord.API.Abstractions.Objects; using Remora.Rest.Core; @@ -34,7 +35,8 @@ public record MentionableSelectComponent Optional Placeholder = default, Optional MinValues = default, Optional MaxValues = default, - Optional IsDisabled = default + Optional IsDisabled = default, + Optional> DefaultValues = default ) : IMentionableSelectComponent { /// diff --git a/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/PartialChannelSelectComponent.cs b/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/PartialChannelSelectComponent.cs index 22525e2c6b..ae0e792235 100644 --- a/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/PartialChannelSelectComponent.cs +++ b/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/PartialChannelSelectComponent.cs @@ -36,7 +36,8 @@ public record PartialChannelSelectComponent Optional Placeholder = default, Optional MinValues = default, Optional MaxValues = default, - Optional IsDisabled = default + Optional IsDisabled = default, + Optional> DefaultValues = default ) : IPartialChannelSelectComponent { /// diff --git a/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/PartialMentionableSelectComponent.cs b/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/PartialMentionableSelectComponent.cs index 82d9daae18..cf69d01409 100644 --- a/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/PartialMentionableSelectComponent.cs +++ b/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/PartialMentionableSelectComponent.cs @@ -20,6 +20,7 @@ // along with this program. If not, see . // +using System.Collections.Generic; using JetBrains.Annotations; using Remora.Discord.API.Abstractions.Objects; using Remora.Rest.Core; @@ -34,7 +35,8 @@ public record PartialMentionableSelectComponent Optional Placeholder = default, Optional MinValues = default, Optional MaxValues = default, - Optional IsDisabled = default + Optional IsDisabled = default, + Optional> DefaultValues = default ) : IPartialMentionableSelectComponent { /// diff --git a/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/PartialRoleSelectComponent.cs b/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/PartialRoleSelectComponent.cs index 98cd830b2e..b7ea4b29a1 100644 --- a/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/PartialRoleSelectComponent.cs +++ b/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/PartialRoleSelectComponent.cs @@ -20,6 +20,7 @@ // along with this program. If not, see . // +using System.Collections.Generic; using JetBrains.Annotations; using Remora.Discord.API.Abstractions.Objects; using Remora.Rest.Core; @@ -34,7 +35,8 @@ public record PartialRoleSelectComponent Optional Placeholder = default, Optional MinValues = default, Optional MaxValues = default, - Optional IsDisabled = default + Optional IsDisabled = default, + Optional> DefaultValues = default ) : IPartialRoleSelectComponent { /// diff --git a/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/PartialUserSelectComponent.cs b/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/PartialUserSelectComponent.cs index 588571d124..fecdb3bf8b 100644 --- a/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/PartialUserSelectComponent.cs +++ b/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/PartialUserSelectComponent.cs @@ -20,6 +20,7 @@ // along with this program. If not, see . // +using System.Collections.Generic; using JetBrains.Annotations; using Remora.Discord.API.Abstractions.Objects; using Remora.Rest.Core; @@ -34,7 +35,8 @@ public record PartialUserSelectComponent Optional Placeholder = default, Optional MinValues = default, Optional MaxValues = default, - Optional IsDisabled = default + Optional IsDisabled = default, + Optional> DefaultValues = default ) : IPartialUserSelectComponent { /// diff --git a/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/RoleSelectComponent.cs b/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/RoleSelectComponent.cs index 28e5e70c4c..7620d4f2cc 100644 --- a/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/RoleSelectComponent.cs +++ b/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/RoleSelectComponent.cs @@ -20,6 +20,7 @@ // along with this program. If not, see . // +using System.Collections.Generic; using JetBrains.Annotations; using Remora.Discord.API.Abstractions.Objects; using Remora.Rest.Core; @@ -34,7 +35,8 @@ public record RoleSelectComponent Optional Placeholder = default, Optional MinValues = default, Optional MaxValues = default, - Optional IsDisabled = default + Optional IsDisabled = default, + Optional> DefaultValues = default ) : IRoleSelectComponent { /// diff --git a/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/SelectDefaultValue.cs b/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/SelectDefaultValue.cs new file mode 100644 index 0000000000..784cfd0064 --- /dev/null +++ b/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/SelectDefaultValue.cs @@ -0,0 +1,31 @@ +// +// SelectDefaultValue.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using JetBrains.Annotations; +using Remora.Discord.API.Abstractions.Objects; +using Remora.Rest.Core; + +namespace Remora.Discord.API.Objects; + +/// +[PublicAPI] +public record SelectDefaultValue(Snowflake ID, string Type) : ISelectDefaultValue; diff --git a/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/UserSelectComponent.cs b/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/UserSelectComponent.cs index 430af50ef6..0c263a76d4 100644 --- a/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/UserSelectComponent.cs +++ b/Backend/Remora.Discord.API/API/Objects/Interactions/Components/SelectMenu/UserSelectComponent.cs @@ -20,6 +20,7 @@ // along with this program. If not, see . // +using System.Collections.Generic; using JetBrains.Annotations; using Remora.Discord.API.Abstractions.Objects; using Remora.Rest.Core; @@ -34,7 +35,8 @@ public record UserSelectComponent Optional Placeholder = default, Optional MinValues = default, Optional MaxValues = default, - Optional IsDisabled = default + Optional IsDisabled = default, + Optional> DefaultValues = default ) : IUserSelectComponent { /// diff --git a/Backend/Remora.Discord.API/API/Objects/Interactions/Interaction.cs b/Backend/Remora.Discord.API/API/Objects/Interactions/Interaction.cs index 6909cbeeac..1e65ef2a4d 100644 --- a/Backend/Remora.Discord.API/API/Objects/Interactions/Interaction.cs +++ b/Backend/Remora.Discord.API/API/Objects/Interactions/Interaction.cs @@ -20,6 +20,7 @@ // along with this program. If not, see . // +using System.Collections.Generic; using JetBrains.Annotations; using OneOf; using Remora.Discord.API.Abstractions.Objects; @@ -44,8 +45,11 @@ public record Interaction Optional User, string Token, int Version, - Optional Message = default, - Optional AppPermissions = default, - Optional Locale = default, - Optional GuildLocale = default + Optional Message, + IDiscordPermissionSet AppPermissions, + Optional Locale, + Optional GuildLocale, + IReadOnlyList Entitlements, + Optional Context, + Optional> AuthorizingIntegrationOwners ) : IInteraction; diff --git a/Backend/Remora.Discord.API/API/Objects/Interactions/MessageComponentInteractionMetadata.cs b/Backend/Remora.Discord.API/API/Objects/Interactions/MessageComponentInteractionMetadata.cs new file mode 100644 index 0000000000..701a08d5e8 --- /dev/null +++ b/Backend/Remora.Discord.API/API/Objects/Interactions/MessageComponentInteractionMetadata.cs @@ -0,0 +1,40 @@ +// +// MessageComponentInteractionMetadata.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Collections.Generic; +using JetBrains.Annotations; +using Remora.Discord.API.Abstractions.Objects; +using Remora.Rest.Core; + +namespace Remora.Discord.API.Objects; + +/// +[PublicAPI] +public record MessageComponentInteractionMetadata +( + Snowflake ID, + Snowflake UserID, + InteractionType Type, + Optional OriginalResponseMessageID, + IReadOnlyDictionary AuthorizingIntegrationOwners, + Snowflake InteractedMessageID +) : IMessageComponentInteractionMetadata; diff --git a/Backend/Remora.Discord.API/API/Objects/Interactions/ModalSubmitInteractionMetadata.cs b/Backend/Remora.Discord.API/API/Objects/Interactions/ModalSubmitInteractionMetadata.cs new file mode 100644 index 0000000000..0ddf24ab7f --- /dev/null +++ b/Backend/Remora.Discord.API/API/Objects/Interactions/ModalSubmitInteractionMetadata.cs @@ -0,0 +1,41 @@ +// +// ModalSubmitInteractionMetadata.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Collections.Generic; +using JetBrains.Annotations; +using OneOf; +using Remora.Discord.API.Abstractions.Objects; +using Remora.Rest.Core; + +namespace Remora.Discord.API.Objects; + +/// +[PublicAPI] +public record ModalSubmitInteractionMetadata +( + Snowflake ID, + Snowflake UserID, + InteractionType Type, + Optional OriginalResponseMessageID, + IReadOnlyDictionary AuthorizingIntegrationOwners, + OneOf TriggeringInteractionMetadata +) : IModalSubmitInteractionMetadata; diff --git a/Backend/Remora.Discord.API/API/Objects/Messages/Message.cs b/Backend/Remora.Discord.API/API/Objects/Messages/Message.cs index 8bbfdd5af0..480d9ef1da 100644 --- a/Backend/Remora.Discord.API/API/Objects/Messages/Message.cs +++ b/Backend/Remora.Discord.API/API/Objects/Messages/Message.cs @@ -23,6 +23,7 @@ using System; using System.Collections.Generic; using JetBrains.Annotations; +using OneOf; using Remora.Discord.API.Abstractions.Objects; using Remora.Rest.Core; @@ -62,5 +63,7 @@ public record Message Optional Thread = default, Optional> Components = default, Optional> StickerItems = default, - Optional Position = default + Optional Position = default, + Optional Resolved = default, + Optional> InteractionMetadata = default ) : IMessage; diff --git a/Backend/Remora.Discord.API/API/Objects/Messages/PartialMessage.cs b/Backend/Remora.Discord.API/API/Objects/Messages/PartialMessage.cs index 98d4ad7bd8..d1bd60826b 100644 --- a/Backend/Remora.Discord.API/API/Objects/Messages/PartialMessage.cs +++ b/Backend/Remora.Discord.API/API/Objects/Messages/PartialMessage.cs @@ -23,6 +23,7 @@ using System; using System.Collections.Generic; using JetBrains.Annotations; +using OneOf; using Remora.Discord.API.Abstractions.Objects; using Remora.Rest.Core; @@ -62,5 +63,7 @@ public record PartialMessage Optional Thread = default, Optional> Components = default, Optional> StickerItems = default, - Optional Position = default + Optional Position = default, + Optional Resolved = default, + Optional> InteractionMetadata = default ) : IPartialMessage; diff --git a/Backend/Remora.Discord.API/API/Objects/Monetization/Entitlement.cs b/Backend/Remora.Discord.API/API/Objects/Monetization/Entitlement.cs new file mode 100644 index 0000000000..a744a85ace --- /dev/null +++ b/Backend/Remora.Discord.API/API/Objects/Monetization/Entitlement.cs @@ -0,0 +1,44 @@ +// +// Entitlement.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; +using JetBrains.Annotations; +using Remora.Discord.API.Abstractions.Objects; +using Remora.Rest.Core; + +namespace Remora.Discord.API.Objects; + +/// +[PublicAPI] +public record Entitlement +( + Snowflake ID, + Snowflake SKUID, + Snowflake ApplicationID, + Optional UserID, + EntitlementType Type, + bool IsDeleted, + Optional StartsAt = default, + Optional EndsAt = default, + Optional GuildID = default, + Optional IsConsumed = default +) : IEntitlement; diff --git a/Backend/Remora.Discord.API/API/Objects/Monetization/PartialEntitlement.cs b/Backend/Remora.Discord.API/API/Objects/Monetization/PartialEntitlement.cs new file mode 100644 index 0000000000..16cd480b23 --- /dev/null +++ b/Backend/Remora.Discord.API/API/Objects/Monetization/PartialEntitlement.cs @@ -0,0 +1,44 @@ +// +// PartialEntitlement.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; +using JetBrains.Annotations; +using Remora.Discord.API.Abstractions.Objects; +using Remora.Rest.Core; + +namespace Remora.Discord.API.Objects; + +/// +[PublicAPI] +public record PartialEntitlement +( + Optional ID = default, + Optional SKUID = default, + Optional ApplicationID = default, + Optional UserID = default, + Optional Type = default, + Optional IsDeleted = default, + Optional StartsAt = default, + Optional EndsAt = default, + Optional GuildID = default, + Optional IsConsumed = default +) : IPartialEntitlement; diff --git a/Backend/Remora.Discord.API/API/Objects/Monetization/SKU.cs b/Backend/Remora.Discord.API/API/Objects/Monetization/SKU.cs new file mode 100644 index 0000000000..61c390ea5c --- /dev/null +++ b/Backend/Remora.Discord.API/API/Objects/Monetization/SKU.cs @@ -0,0 +1,31 @@ +// +// SKU.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using JetBrains.Annotations; +using Remora.Discord.API.Abstractions.Objects; +using Remora.Rest.Core; + +namespace Remora.Discord.API.Objects; + +/// +[PublicAPI] +public record SKU(Snowflake ID, SKUType Type, Snowflake ApplicationID, string Name, string Slug, SKUFlags Flags) : ISKU; diff --git a/Backend/Remora.Discord.API/API/Objects/Reactions/Reaction.cs b/Backend/Remora.Discord.API/API/Objects/Reactions/Reaction.cs index dce3449e0b..8e15bed81a 100644 --- a/Backend/Remora.Discord.API/API/Objects/Reactions/Reaction.cs +++ b/Backend/Remora.Discord.API/API/Objects/Reactions/Reaction.cs @@ -20,6 +20,8 @@ // along with this program. If not, see . // +using System.Collections.Generic; +using System.Drawing; using JetBrains.Annotations; using Remora.Discord.API.Abstractions.Objects; @@ -29,4 +31,12 @@ namespace Remora.Discord.API.Objects; /// [PublicAPI] -public record Reaction(int Count, bool HasCurrentUserReacted, IPartialEmoji Emoji) : IReaction; +public record Reaction +( + int Count, + IReactionCountDetails CountDetails, + bool HasCurrentUserReacted, + bool HasCurrentUserBurstReacted, + IPartialEmoji Emoji, + IReadOnlyList BurstColours +) : IReaction; diff --git a/Backend/Remora.Discord.API/API/Objects/Reactions/ReactionCountDetails.cs b/Backend/Remora.Discord.API/API/Objects/Reactions/ReactionCountDetails.cs new file mode 100644 index 0000000000..f4147e710d --- /dev/null +++ b/Backend/Remora.Discord.API/API/Objects/Reactions/ReactionCountDetails.cs @@ -0,0 +1,30 @@ +// +// ReactionCountDetails.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using JetBrains.Annotations; +using Remora.Discord.API.Abstractions.Objects; + +namespace Remora.Discord.API.Objects; + +/// +[PublicAPI] +public record ReactionCountDetails(int Burst, int Normal) : IReactionCountDetails; diff --git a/Backend/Remora.Discord.API/API/Objects/Teams/TeamMember.cs b/Backend/Remora.Discord.API/API/Objects/Teams/TeamMember.cs index 8a6b5a6d6b..cba10f1b91 100644 --- a/Backend/Remora.Discord.API/API/Objects/Teams/TeamMember.cs +++ b/Backend/Remora.Discord.API/API/Objects/Teams/TeamMember.cs @@ -36,5 +36,6 @@ public record TeamMember MembershipState MembershipState, IReadOnlyList Permissions, Snowflake TeamID, - IPartialUser User + IPartialUser User, + TeamMemberRole Role ) : ITeamMember; diff --git a/Backend/Remora.Discord.API/API/Objects/Users/User.cs b/Backend/Remora.Discord.API/API/Objects/Users/User.cs index 5a76204d78..b2eab65428 100644 --- a/Backend/Remora.Discord.API/API/Objects/Users/User.cs +++ b/Backend/Remora.Discord.API/API/Objects/Users/User.cs @@ -36,7 +36,7 @@ public record User Snowflake ID, string Username, ushort Discriminator, - string? GlobalName, + Optional GlobalName, IImageHash? Avatar, Optional IsBot = default, Optional IsSystem = default, diff --git a/Backend/Remora.Discord.API/API/Objects/Users/UserMention.cs b/Backend/Remora.Discord.API/API/Objects/Users/UserMention.cs index 10113794b3..14ec48b68d 100644 --- a/Backend/Remora.Discord.API/API/Objects/Users/UserMention.cs +++ b/Backend/Remora.Discord.API/API/Objects/Users/UserMention.cs @@ -34,7 +34,7 @@ public record UserMention Snowflake ID, string Username, ushort Discriminator, - string? GlobalName, + Optional GlobalName, IImageHash? Avatar, Optional IsBot = default, Optional IsSystem = default, diff --git a/Backend/Remora.Discord.API/API/Rest/BulkBanResponse.cs b/Backend/Remora.Discord.API/API/Rest/BulkBanResponse.cs new file mode 100644 index 0000000000..469100b53b --- /dev/null +++ b/Backend/Remora.Discord.API/API/Rest/BulkBanResponse.cs @@ -0,0 +1,36 @@ +// +// BulkBanResponse.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Collections.Generic; +using JetBrains.Annotations; +using Remora.Discord.API.Abstractions.Rest; +using Remora.Rest.Core; + +namespace Remora.Discord.API.Rest; + +/// +[PublicAPI] +public record BulkBanResponse +( + IReadOnlyList BannedUsers, + IReadOnlyList FailedUsers +) : IBulkBanResponse; diff --git a/Backend/Remora.Discord.API/Extensions/ServiceCollectionExtensions.cs b/Backend/Remora.Discord.API/Extensions/ServiceCollectionExtensions.cs index d6d45b9f09..56a3aaba60 100644 --- a/Backend/Remora.Discord.API/Extensions/ServiceCollectionExtensions.cs +++ b/Backend/Remora.Discord.API/Extensions/ServiceCollectionExtensions.cs @@ -114,7 +114,8 @@ public static IServiceCollection ConfigureDiscordJsonConverters .AddTeamObjectConverters() .AddStageInstanceObjectConverters() .AddStickerObjectConverters() - .AddApplicationRoleConnectionObjectConverters(); + .AddApplicationRoleConnectionObjectConverters() + .AddMonetizationConverters(); options.AddDataObjectConverter(); options.AddConverter(); @@ -416,6 +417,22 @@ private static JsonSerializerOptions AddGatewayEventConverters(this JsonSerializ // Application commands options.AddDataObjectConverter(); + // Monetization + options.AddDataObjectConverter() + .WithPropertyName(e => e.SKUID, "sku_id") + .WithPropertyName(e => e.IsDeleted, "deleted") + .WithPropertyName(e => e.IsConsumed, "consumed"); + + options.AddDataObjectConverter() + .WithPropertyName(e => e.SKUID, "sku_id") + .WithPropertyName(e => e.IsDeleted, "deleted") + .WithPropertyName(e => e.IsConsumed, "consumed"); + + options.AddDataObjectConverter() + .WithPropertyName(e => e.SKUID, "sku_id") + .WithPropertyName(e => e.IsDeleted, "deleted") + .WithPropertyName(e => e.IsConsumed, "consumed"); + // Other options.AddDataObjectConverter(); @@ -688,6 +705,8 @@ private static JsonSerializerOptions AddGuildObjectConverters(this JsonSerialize .WithPropertyName(o => o.ChannelIDs, "channel_ids") .WithPropertyName(o => o.RoleIDs, "role_ids"); + options.AddDataObjectConverter(); + return options; } @@ -875,7 +894,12 @@ private static JsonSerializerOptions AddPresenceObjectConverters(this JsonSerial private static JsonSerializerOptions AddReactionObjectConverters(this JsonSerializerOptions options) { options.AddDataObjectConverter() - .WithPropertyName(r => r.HasCurrentUserReacted, "me"); + .WithPropertyName(r => r.HasCurrentUserReacted, "me") + .WithPropertyName(r => r.HasCurrentUserBurstReacted, "me_burst") + .WithPropertyName(r => r.BurstColours, "burst_colors") + .WithPropertyConverter(r => r.BurstColours, new HexCodeColourConverter()); + + options.AddDataObjectConverter(); return options; } @@ -1033,7 +1057,7 @@ private static JsonSerializerOptions AddInteractionObjectConverters(this JsonSer options.AddDataObjectConverter(); options.AddDataObjectConverter() - .WithPropertyName(d => d.IsNsfw, "nsfw"); + .WithPropertyName(d => d.IsNsfw, "nsfw"); options.AddDataObjectConverter() .WithPropertyName(o => o.IsDefault, "default") @@ -1042,8 +1066,9 @@ private static JsonSerializerOptions AddInteractionObjectConverters(this JsonSer options.AddDataObjectConverter(); options.AddDataObjectConverter(); + options.AddDataObjectConverter() - .WithPropertyName(d => d.IsNsfw, "nsfw"); + .WithPropertyName(d => d.IsNsfw, "nsfw"); options.AddDataObjectConverter < @@ -1162,6 +1187,8 @@ private static JsonSerializerOptions AddInteractionObjectConverters(this JsonSer options.AddDataObjectConverter() .WithPropertyName(o => o.IsDefault, "default"); + options.AddDataObjectConverter(); + return options; } @@ -1186,6 +1213,12 @@ private static JsonSerializerOptions AddOAuth2ObjectConverters(this JsonSerializ options.AddDataObjectConverter(); + options.AddDataObjectConverter() + .WithPropertyName(a => a.OAuth2InstallParams, "oauth2_install_params"); + + options.AddDataObjectConverter(); + + options.Converters.Insert(0, new StringEnumConverter(options.PropertyNamingPolicy, true)); return options; } @@ -1197,7 +1230,29 @@ private static JsonSerializerOptions AddOAuth2ObjectConverters(this JsonSerializ private static JsonSerializerOptions AddTeamObjectConverters(this JsonSerializerOptions options) { options.AddDataObjectConverter(); - options.AddDataObjectConverter(); + options.AddDataObjectConverter() + .WithPropertyConverter(m => m.Role, new StringEnumConverter(new SnakeCaseNamingPolicy())); + + return options; + } + + /// + /// Adds the JSON converters that handle monetization objects. + /// + /// The serializer options. + /// The options, with the converters added. + private static JsonSerializerOptions AddMonetizationConverters(this JsonSerializerOptions options) + { + options.AddDataObjectConverter() + .WithPropertyName(e => e.SKUID, "sku_id") + .WithPropertyName(e => e.IsDeleted, "deleted") + .WithPropertyName(e => e.IsConsumed, "consumed"); + options.AddDataObjectConverter() + .WithPropertyName(e => e.SKUID, "sku_id") + .WithPropertyName(e => e.IsDeleted, "deleted") + .WithPropertyName(e => e.IsConsumed, "consumed"); + + options.AddDataObjectConverter(); return options; } diff --git a/Backend/Remora.Discord.API/Json/Converters/Internal/DiscriminatorConverter.cs b/Backend/Remora.Discord.API/Json/Converters/Internal/DiscriminatorConverter.cs index 3b7a2a3f15..d072cc4b7b 100644 --- a/Backend/Remora.Discord.API/Json/Converters/Internal/DiscriminatorConverter.cs +++ b/Backend/Remora.Discord.API/Json/Converters/Internal/DiscriminatorConverter.cs @@ -50,6 +50,13 @@ public override ushort Read(ref Utf8JsonReader reader, Type typeToConvert, JsonS /// public override void Write(Utf8JsonWriter writer, ushort value, JsonSerializerOptions options) { + if (value is 0) + { + // zeroes should not be padded + writer.WriteStringValue($"{value}"); + return; + } + writer.WriteStringValue($"{value:D4}"); } } diff --git a/Backend/Remora.Discord.API/Json/Converters/Internal/HexCodeColourConverter.cs b/Backend/Remora.Discord.API/Json/Converters/Internal/HexCodeColourConverter.cs new file mode 100644 index 0000000000..d506398dce --- /dev/null +++ b/Backend/Remora.Discord.API/Json/Converters/Internal/HexCodeColourConverter.cs @@ -0,0 +1,68 @@ +// +// HexCodeColourConverter.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; +using System.Drawing; +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; +using JetBrains.Annotations; + +namespace Remora.Discord.API.Json; + +/// +/// Converts instances of the struct to and from JSON. +/// +[PublicAPI] +public class HexCodeColourConverter : JsonConverter +{ + /// + public override Color Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.String) + { + throw new JsonException(); + } + + var text = reader.GetString(); + if (text is null) + { + throw new JsonException(); + } + + if (!uint.TryParse(text[1..], NumberStyles.HexNumber, null, out var value)) + { + throw new JsonException(); + } + + var clrValue = value | 0xFF000000; + + return Color.FromArgb((int)clrValue); + } + + /// + public override void Write(Utf8JsonWriter writer, Color value, JsonSerializerOptions options) + { + var val = value.ToArgb() & 0x00FFFFFF; + writer.WriteStringValue($"#{val:x6}"); + } +} diff --git a/Backend/Remora.Discord.API/Json/Converters/Internal/MessageComponentConverter.cs b/Backend/Remora.Discord.API/Json/Converters/Internal/MessageComponentConverter.cs index 37cdb57ec0..8ec15ee5ec 100644 --- a/Backend/Remora.Discord.API/Json/Converters/Internal/MessageComponentConverter.cs +++ b/Backend/Remora.Discord.API/Json/Converters/Internal/MessageComponentConverter.cs @@ -124,14 +124,14 @@ public override void Write(Utf8JsonWriter writer, IMessageComponent value, JsonS JsonSerializer.Serialize(writer, roleSelect, options); break; } - case IMentionableSelectComponent mentionableSelect: + case IChannelSelectComponent channelSelect: { - JsonSerializer.Serialize(writer, mentionableSelect, options); + JsonSerializer.Serialize(writer, channelSelect, options); break; } - case IChannelSelectComponent channelSelect: + case IMentionableSelectComponent mentionableSelect: { - JsonSerializer.Serialize(writer, channelSelect, options); + JsonSerializer.Serialize(writer, mentionableSelect, options); break; } default: diff --git a/Backend/Remora.Discord.API/Json/Converters/Internal/PartialMessageComponentConverter.cs b/Backend/Remora.Discord.API/Json/Converters/Internal/PartialMessageComponentConverter.cs index e1837a6a9a..171f1c97a6 100644 --- a/Backend/Remora.Discord.API/Json/Converters/Internal/PartialMessageComponentConverter.cs +++ b/Backend/Remora.Discord.API/Json/Converters/Internal/PartialMessageComponentConverter.cs @@ -124,14 +124,14 @@ public override void Write(Utf8JsonWriter writer, IPartialMessageComponent value JsonSerializer.Serialize(writer, roleSelect, options); break; } - case IPartialMentionableSelectComponent mentionableSelect: + case IPartialChannelSelectComponent channelSelect: { - JsonSerializer.Serialize(writer, mentionableSelect, options); + JsonSerializer.Serialize(writer, channelSelect, options); break; } - case IPartialChannelSelectComponent channelSelect: + case IPartialMentionableSelectComponent mentionableSelect: { - JsonSerializer.Serialize(writer, channelSelect, options); + JsonSerializer.Serialize(writer, mentionableSelect, options); break; } default: diff --git a/Backend/Remora.Discord.API/Json/Converters/Internal/PayloadConverter.cs b/Backend/Remora.Discord.API/Json/Converters/Internal/PayloadConverter.cs index c9199e634e..a8ba1bc4e5 100644 --- a/Backend/Remora.Discord.API/Json/Converters/Internal/PayloadConverter.cs +++ b/Backend/Remora.Discord.API/Json/Converters/Internal/PayloadConverter.cs @@ -385,7 +385,9 @@ JsonSerializerOptions options { eventData = JsonSerializer.Deserialize ( - dataProperty.GetRawText(), eventType, options + dataProperty.GetRawText(), + eventType, + options ); } catch diff --git a/Backend/Remora.Discord.API/Json/Converters/Internal/VoicePayloadConverter.cs b/Backend/Remora.Discord.API/Json/Converters/Internal/VoicePayloadConverter.cs index 5650777318..3d8e81a821 100644 --- a/Backend/Remora.Discord.API/Json/Converters/Internal/VoicePayloadConverter.cs +++ b/Backend/Remora.Discord.API/Json/Converters/Internal/VoicePayloadConverter.cs @@ -202,7 +202,6 @@ JsonSerializerOptions options ) => operationCode switch { - // Commands VoiceOperationCode.Identify => DeserializePayload ( diff --git a/Backend/Remora.Discord.API/Remora.Discord.API.csproj b/Backend/Remora.Discord.API/Remora.Discord.API.csproj index bbfd5f0447..ced8c422f8 100644 --- a/Backend/Remora.Discord.API/Remora.Discord.API.csproj +++ b/Backend/Remora.Discord.API/Remora.Discord.API.csproj @@ -1,19 +1,12 @@ - 75.0.0 + 77.0.0 Remora.Discord's implementation of Discord's API objects Update dependencies. - BREAKING: Implement support for join and mention raid protection - BREAKING: Implement new username field. - Fix conversion discrepancies between partial and nonpartial type. - BREAKING: Implement role flags. - BREAKING: Implement attachment flags. - BREAKING: Implement onboarding mode. - BREAKING: Implement MessageAuthorID. - BREAKING: Implement approximate guild cound and partial guild. - BREAKING: Implement avatar decorations. + BREAKING: Revert "Remove global_name from User objects" + BREAKING: fix: wrap GlobalName in Optional diff --git a/Backend/Remora.Discord.API/Remora.Discord.API.csproj.DotSettings b/Backend/Remora.Discord.API/Remora.Discord.API.csproj.DotSettings index 181fe038c9..de9c603347 100644 --- a/Backend/Remora.Discord.API/Remora.Discord.API.csproj.DotSettings +++ b/Backend/Remora.Discord.API/Remora.Discord.API.csproj.DotSettings @@ -10,6 +10,7 @@ True True True + True True True True @@ -36,6 +37,7 @@ True True True + True True True True diff --git a/Backend/Remora.Discord.Caching.Abstractions/Remora.Discord.Caching.Abstractions.csproj b/Backend/Remora.Discord.Caching.Abstractions/Remora.Discord.Caching.Abstractions.csproj index 9497550726..c495977100 100644 --- a/Backend/Remora.Discord.Caching.Abstractions/Remora.Discord.Caching.Abstractions.csproj +++ b/Backend/Remora.Discord.Caching.Abstractions/Remora.Discord.Caching.Abstractions.csproj @@ -1,7 +1,7 @@ - 1.1.3 + 1.1.4 Cache provider abstractions for Remora.Discord Update dependencies. diff --git a/Backend/Remora.Discord.Caching.Redis/Remora.Discord.Caching.Redis.csproj b/Backend/Remora.Discord.Caching.Redis/Remora.Discord.Caching.Redis.csproj index 703218f200..a4df3313ba 100644 --- a/Backend/Remora.Discord.Caching.Redis/Remora.Discord.Caching.Redis.csproj +++ b/Backend/Remora.Discord.Caching.Redis/Remora.Discord.Caching.Redis.csproj @@ -1,7 +1,7 @@ - 1.1.5 + 1.1.7 Redis-based cache provider for Remora.Discord Update dependencies. diff --git a/Backend/Remora.Discord.Caching/API/CachingDiscordRestChannelAPI.Delegations.cs b/Backend/Remora.Discord.Caching/API/CachingDiscordRestChannelAPI.Delegations.cs index 7b0649a309..0b228215d0 100644 --- a/Backend/Remora.Discord.Caching/API/CachingDiscordRestChannelAPI.Delegations.cs +++ b/Backend/Remora.Discord.Caching/API/CachingDiscordRestChannelAPI.Delegations.cs @@ -273,6 +273,48 @@ public Task> ModifyForumChannelAsync ); } + /// + public Task> ModifyMediaChannelAsync + ( + Snowflake channelID, + Optional name = default, + Optional position = default, + Optional topic = default, + Optional isNsfw = default, + Optional rateLimitPerUser = default, + Optional?> permissionOverwrites = default, + Optional parentID = default, + Optional defaultAutoArchiveDuration = default, + Optional flags = default, + Optional> availableTags = default, + Optional defaultReactionEmoji = default, + Optional defaultThreadRateLimitPerUser = default, + Optional defaultSortOrder = default, + Optional reason = default, + CancellationToken ct = default + ) + { + return _actual.ModifyMediaChannelAsync + ( + channelID, + name, + position, + topic, + isNsfw, + rateLimitPerUser, + permissionOverwrites, + parentID, + defaultAutoArchiveDuration, + flags, + availableTags, + defaultReactionEmoji, + defaultThreadRateLimitPerUser, + defaultSortOrder, + reason, + ct + ); + } + /// public Task CreateReactionAsync ( diff --git a/Backend/Remora.Discord.Caching/API/CachingDiscordRestChannelAPI.cs b/Backend/Remora.Discord.Caching/API/CachingDiscordRestChannelAPI.cs index 08b9629482..b1ed35b7df 100644 --- a/Backend/Remora.Discord.Caching/API/CachingDiscordRestChannelAPI.cs +++ b/Backend/Remora.Discord.Caching/API/CachingDiscordRestChannelAPI.cs @@ -228,6 +228,7 @@ public async Task> CreateMessageAsync Optional> stickerIds = default, Optional>> attachments = default, Optional flags = default, + Optional enforceNonce = default, CancellationToken ct = default ) { @@ -244,6 +245,7 @@ public async Task> CreateMessageAsync stickerIds, attachments, flags, + enforceNonce, ct ); diff --git a/Backend/Remora.Discord.Caching/API/CachingDiscordRestGuildAPI.Delegations.cs b/Backend/Remora.Discord.Caching/API/CachingDiscordRestGuildAPI.Delegations.cs index 65b2d29691..ea3b633c36 100644 --- a/Backend/Remora.Discord.Caching/API/CachingDiscordRestGuildAPI.Delegations.cs +++ b/Backend/Remora.Discord.Caching/API/CachingDiscordRestGuildAPI.Delegations.cs @@ -127,6 +127,19 @@ public Task CreateGuildBanAsync return _actual.CreateGuildBanAsync(guildID, userID, deleteMessageDays, reason, ct); } + /// + public Task> BulkGuildBanAsync + ( + Snowflake guildID, + IReadOnlyList userIDs, + Optional deleteMessageSeconds = default, + Optional reason = default, + CancellationToken ct = default + ) + { + return _actual.BulkGuildBanAsync(guildID, userIDs, deleteMessageSeconds, reason, ct); + } + /// public Task> ModifyGuildMFALevelAsync ( diff --git a/Backend/Remora.Discord.Caching/API/CachingDiscordRestGuildAPI.cs b/Backend/Remora.Discord.Caching/API/CachingDiscordRestGuildAPI.cs index 565ccda5ca..f6eed1c4d7 100644 --- a/Backend/Remora.Discord.Caching/API/CachingDiscordRestGuildAPI.cs +++ b/Backend/Remora.Discord.Caching/API/CachingDiscordRestGuildAPI.cs @@ -301,6 +301,7 @@ public async Task> CreateGuildChannelAsync Optional?> availableTags = default, Optional defaultSortOrder = default, Optional defaultForumLayout = default, + Optional defaultThreadRateLimitPerUser = default, Optional reason = default, CancellationToken ct = default ) @@ -325,6 +326,7 @@ public async Task> CreateGuildChannelAsync availableTags, defaultSortOrder, defaultForumLayout, + defaultThreadRateLimitPerUser, reason, ct ); @@ -353,6 +355,7 @@ public Task> CreateGuildTextChannelAsync Optional parentID = default, Optional isNsfw = default, Optional defaultAutoArchiveDuration = default, + Optional defaultThreadRateLimitPerUser = default, Optional reason = default, CancellationToken ct = default ) => CreateGuildChannelAsync @@ -367,6 +370,7 @@ public Task> CreateGuildTextChannelAsync parentID: parentID, isNsfw: isNsfw, defaultAutoArchiveDuration: defaultAutoArchiveDuration, + defaultThreadRateLimitPerUser: defaultThreadRateLimitPerUser, reason: reason, ct: ct ); @@ -382,6 +386,7 @@ public Task> CreateGuildAnnouncementChannelAsync Optional parentID = default, Optional isNsfw = default, Optional defaultAutoArchiveDuration = default, + Optional defaultThreadRateLimitPerUser = default, Optional reason = default, CancellationToken ct = default ) => CreateGuildChannelAsync @@ -395,6 +400,7 @@ public Task> CreateGuildAnnouncementChannelAsync parentID: parentID, isNsfw: isNsfw, defaultAutoArchiveDuration: defaultAutoArchiveDuration, + defaultThreadRateLimitPerUser: defaultThreadRateLimitPerUser, reason: reason, ct: ct ); @@ -405,6 +411,7 @@ public Task> CreateGuildForumChannelAsync Snowflake guildID, string name, Optional topic = default, + Optional rateLimitPerUser = default, Optional position = default, Optional?> permissionOverwrites = default, Optional parentID = default, @@ -414,6 +421,7 @@ public Task> CreateGuildForumChannelAsync Optional?> availableTags = default, Optional defaultSortOrder = default, Optional defaultForumLayout = default, + Optional defaultThreadRateLimitPerUser = default, Optional reason = default, CancellationToken ct = default ) => CreateGuildChannelAsync @@ -422,6 +430,7 @@ public Task> CreateGuildForumChannelAsync name, ChannelType.GuildForum, topic: topic, + rateLimitPerUser: rateLimitPerUser, position: position, permissionOverwrites: permissionOverwrites, parentID: parentID, @@ -431,6 +440,43 @@ public Task> CreateGuildForumChannelAsync availableTags: availableTags, defaultSortOrder: defaultSortOrder, defaultForumLayout: defaultForumLayout, + defaultThreadRateLimitPerUser: defaultThreadRateLimitPerUser, + reason: reason, + ct: ct + ); + + /// + public Task> CreateGuildMediaChannelAsync + ( + Snowflake guildID, + string name, + Optional topic = default, + Optional rateLimitPerUser = default, + Optional position = default, + Optional?> permissionOverwrites = default, + Optional parentID = default, + Optional defaultAutoArchiveDuration = default, + Optional defaultReactionEmoji = default, + Optional?> availableTags = default, + Optional defaultSortOrder = default, + Optional defaultThreadRateLimitPerUser = default, + Optional reason = default, + CancellationToken ct = default + ) => CreateGuildChannelAsync + ( + guildID, + name, + ChannelType.GuildMedia, + topic: topic, + rateLimitPerUser: rateLimitPerUser, + position: position, + permissionOverwrites: permissionOverwrites, + parentID: parentID, + defaultAutoArchiveDuration: defaultAutoArchiveDuration, + defaultReactionEmoji: defaultReactionEmoji, + availableTags: availableTags, + defaultSortOrder: defaultSortOrder, + defaultThreadRateLimitPerUser: defaultThreadRateLimitPerUser, reason: reason, ct: ct ); diff --git a/Backend/Remora.Discord.Caching/API/CachingDiscordRestUserAPI.cs b/Backend/Remora.Discord.Caching/API/CachingDiscordRestUserAPI.cs index be67453747..8e736ab895 100644 --- a/Backend/Remora.Discord.Caching/API/CachingDiscordRestUserAPI.cs +++ b/Backend/Remora.Discord.Caching/API/CachingDiscordRestUserAPI.cs @@ -133,7 +133,7 @@ public async Task> GetCurrentUserAsync(CancellationToken ct = defa } /// - public async Task>> GetUserConnectionsAsync + public async Task>> GetCurrentUserConnectionsAsync ( CancellationToken ct = default ) @@ -146,7 +146,7 @@ public async Task>> GetUserConnectionsAsync return cacheResult; } - var getUserConnections = await _actual.GetUserConnectionsAsync(ct); + var getUserConnections = await _actual.GetCurrentUserConnectionsAsync(ct); if (!getUserConnections.IsSuccess) { return getUserConnections; @@ -246,7 +246,7 @@ public async Task> GetCurrentUserGuildMemberAsync } /// - public async Task> GetUserApplicationRoleConnectionAsync + public async Task> GetCurrentUserApplicationRoleConnectionAsync ( Snowflake applicationID, CancellationToken ct = default @@ -260,7 +260,7 @@ public async Task> GetUserApplicationRoleConn return cacheResult; } - var getUserApplicationRoleConnection = await _actual.GetUserApplicationRoleConnectionAsync + var getUserApplicationRoleConnection = await _actual.GetCurrentUserApplicationRoleConnectionAsync ( applicationID, ct @@ -277,7 +277,7 @@ public async Task> GetUserApplicationRoleConn } /// - public async Task> UpdateUserApplicationRoleConnectionAsync + public async Task> UpdateCurrentUserApplicationRoleConnectionAsync ( Snowflake applicationID, Optional platformName = default, @@ -286,7 +286,7 @@ public async Task> UpdateUserApplicationRoleC CancellationToken ct = default ) { - var result = await _actual.UpdateUserApplicationRoleConnectionAsync + var result = await _actual.UpdateCurrentUserApplicationRoleConnectionAsync ( applicationID, platformName, diff --git a/Backend/Remora.Discord.Caching/API/CachingDiscordRestWebhookAPI.cs b/Backend/Remora.Discord.Caching/API/CachingDiscordRestWebhookAPI.cs index d8fde97607..50e1d0a7da 100644 --- a/Backend/Remora.Discord.Caching/API/CachingDiscordRestWebhookAPI.cs +++ b/Backend/Remora.Discord.Caching/API/CachingDiscordRestWebhookAPI.cs @@ -120,6 +120,7 @@ public async Task> ExecuteWebhookAsync Optional>> attachments = default, Optional flags = default, Optional threadName = default, + Optional> appliedTags = default, CancellationToken ct = default ) { @@ -139,6 +140,7 @@ public async Task> ExecuteWebhookAsync attachments, flags, threadName, + appliedTags, ct ); @@ -247,7 +249,8 @@ public async Task>> GetChannelWebhooksAsync /// public async Task>> GetGuildWebhooksAsync ( - Snowflake guildID, CancellationToken ct = default + Snowflake guildID, + CancellationToken ct = default ) { var key = new KeyHelpers.GuildWebhooksCacheKey(guildID); diff --git a/Backend/Remora.Discord.Caching/Remora.Discord.Caching.csproj b/Backend/Remora.Discord.Caching/Remora.Discord.Caching.csproj index 87e27f0cd2..f805afd242 100644 --- a/Backend/Remora.Discord.Caching/Remora.Discord.Caching.csproj +++ b/Backend/Remora.Discord.Caching/Remora.Discord.Caching.csproj @@ -1,15 +1,11 @@ - 37.0.0 + 38.0.1 Caching implementations of Remora.Discord's services - Update dependencies. - BREAKING: Implement support for join and mention raid protection - BREAKING: Add RateLimitPerUser to Voice and Stage channels. - Implement onboarding mode. - BREAKING: Add defaultForumLayout parameter. - BREAKING: Implement with_counts for user guild endpoint. + fix: do not use IsDefined when updating cached nullable properties + fix: fill CommunicationDisabledUntil property from new object when updating cached GuildMember diff --git a/Backend/Remora.Discord.Caching/Responders/EarlyCacheResponder.cs b/Backend/Remora.Discord.Caching/Responders/EarlyCacheResponder.cs index 85d76eeb6b..08720365b7 100644 --- a/Backend/Remora.Discord.Caching/Responders/EarlyCacheResponder.cs +++ b/Backend/Remora.Discord.Caching/Responders/EarlyCacheResponder.cs @@ -186,16 +186,17 @@ public async Task RespondAsync(IGuildMemberUpdate gatewayEvent, Cancella cachedInstance = new GuildMember ( new Optional(gatewayEvent.User), - gatewayEvent.Nickname.IsDefined(out var nickname) ? nickname : cachedInstance.Nickname, + gatewayEvent.Nickname.TryGet(out var nickname) ? nickname : cachedInstance.Nickname, gatewayEvent.Avatar, gatewayEvent.Roles, gatewayEvent.JoinedAt ?? cachedInstance.JoinedAt, - gatewayEvent.PremiumSince.IsDefined(out var premiumSince) ? premiumSince : cachedInstance.PremiumSince, + gatewayEvent.PremiumSince.TryGet(out var premiumSince) ? premiumSince : cachedInstance.PremiumSince, gatewayEvent.IsDeafened.TryGet(out var isDeafened) ? isDeafened : cachedInstance.IsDeafened, gatewayEvent.IsMuted.TryGet(out var isMuted) ? isMuted : cachedInstance.IsMuted, default, // TODO: this is probably on this event, but Discord hasn't documented it gatewayEvent.IsPending.TryGet(out var isPending) ? isPending : cachedInstance.IsPending, - cachedInstance.Permissions + cachedInstance.Permissions, + gatewayEvent.CommunicationDisabledUntil ); } else if (gatewayEvent.JoinedAt.HasValue) @@ -211,7 +212,9 @@ public async Task RespondAsync(IGuildMemberUpdate gatewayEvent, Cancella gatewayEvent.IsDeafened.TryGet(out var isDeafened) && isDeafened, gatewayEvent.IsMuted.TryGet(out var isMuted) && isMuted, default, // TODO: this is probably on this event, but Discord hasn't documented it - gatewayEvent.IsPending.TryGet(out var isPending) && isPending + gatewayEvent.IsPending.TryGet(out var isPending) && isPending, + default, + gatewayEvent.CommunicationDisabledUntil ); } else diff --git a/Backend/Remora.Discord.Gateway/DiscordGatewayClient.cs b/Backend/Remora.Discord.Gateway/DiscordGatewayClient.cs index 45ef73b30a..15ddcd1d20 100644 --- a/Backend/Remora.Discord.Gateway/DiscordGatewayClient.cs +++ b/Backend/Remora.Discord.Gateway/DiscordGatewayClient.cs @@ -238,7 +238,11 @@ public async Task RunAsync(CancellationToken stopRequested) // Something has gone wrong. Close the socket, and handle it // Terminate the send and receive tasks + #if NET8_0_OR_GREATER + await _disconnectRequestedSource.CancelAsync(); + #else _disconnectRequestedSource.Cancel(); + #endif // The results of the send and receive tasks are discarded here, because the iteration result will // contain whichever of them failed if any of them did @@ -562,6 +566,18 @@ private async Task RunConnectionIterationAsync(CancellationToken stopReq if (receiveHello.Entity is not IPayload hello) { + if (receiveHello.Entity is IPayload) + { + // Discord may spit out a reconnect if the node is we're connecting while the gateway node is + // shutting down, but before the node is labeled as unavailable. + return new GatewayError + ( + "The gateway requested a reconnect.", + false, + false + ); + } + // Not receiving a hello is a non-recoverable error return new GatewayError ( @@ -656,7 +672,11 @@ private async Task RunConnectionIterationAsync(CancellationToken stopReq } // Terminate the send and receive tasks + #if NET8_0_OR_GREATER + await _disconnectRequestedSource.CancelAsync(); + #else _disconnectRequestedSource.Cancel(); + #endif // The results of the send and receive tasks are discarded here, because we know that it's going to be a // cancellation @@ -984,7 +1004,7 @@ CancellationToken disconnectRequested { sendResult = await rateLimitPolicy.ExecuteAsync ( - () => _transportService.SendPayloadAsync(userPayload, disconnectRequested) + () => _transportService.SendPayloadAsync(userPayload, disconnectRequested).AsTask() ); if (sendResult.IsSuccess) diff --git a/Backend/Remora.Discord.Gateway/Remora.Discord.Gateway.csproj b/Backend/Remora.Discord.Gateway/Remora.Discord.Gateway.csproj index 51cd08560f..93abf12833 100644 --- a/Backend/Remora.Discord.Gateway/Remora.Discord.Gateway.csproj +++ b/Backend/Remora.Discord.Gateway/Remora.Discord.Gateway.csproj @@ -1,12 +1,10 @@ - 11.1.0 + 12.0.1 Remora.Discord's implementation of a Discord gateway client - Update dependencies. - Extract dispatch API to an interface. - Correct cancellation token usage. + Use async overloads when building for .NET 8. diff --git a/Backend/Remora.Discord.Gateway/Responders/IResponder.cs b/Backend/Remora.Discord.Gateway/Responders/IResponder.cs index 77f0d16314..b9be93b2bf 100644 --- a/Backend/Remora.Discord.Gateway/Responders/IResponder.cs +++ b/Backend/Remora.Discord.Gateway/Responders/IResponder.cs @@ -35,9 +35,7 @@ namespace Remora.Discord.Gateway.Responders; /// Represents a marker interface for event responders. /// [PublicAPI] -public interface IResponder -{ -} +public interface IResponder; /// /// Represents a type that can respond to certain gateway events. diff --git a/Backend/Remora.Discord.Gateway/Services/ResponderDispatchService.cs b/Backend/Remora.Discord.Gateway/Services/ResponderDispatchService.cs index d7e0204bf7..997bc03c12 100644 --- a/Backend/Remora.Discord.Gateway/Services/ResponderDispatchService.cs +++ b/Backend/Remora.Discord.Gateway/Services/ResponderDispatchService.cs @@ -422,7 +422,11 @@ public async ValueTask DisposeAsync() GC.SuppressFinalize(this); // Signal running responders that they should cancel + #if NET8_0_OR_GREATER + await _responderCancellationSource.CancelAsync(); + #else _responderCancellationSource.Cancel(); + #endif // Prevent further payloads from being written, signalling the readers that they should terminate _payloadsToDispatch.Writer.Complete(); diff --git a/Backend/Remora.Discord.Gateway/Transport/IPayloadTransportService.cs b/Backend/Remora.Discord.Gateway/Transport/IPayloadTransportService.cs index f90a0499e3..d56ef06505 100644 --- a/Backend/Remora.Discord.Gateway/Transport/IPayloadTransportService.cs +++ b/Backend/Remora.Discord.Gateway/Transport/IPayloadTransportService.cs @@ -66,7 +66,7 @@ public interface IPayloadTransportService /// The payload. /// The cancellation token for this operation. /// A send result which may or may not have succeeded. - Task SendPayloadAsync(IPayload payload, CancellationToken ct = default); + ValueTask SendPayloadAsync(IPayload payload, CancellationToken ct = default); /// /// Asynchronously receives a payload. @@ -76,7 +76,7 @@ public interface IPayloadTransportService /// /// The cancellation token for this operation. /// A receive result which may or may not have succeeded. - Task> ReceivePayloadAsync(CancellationToken ct = default); + ValueTask> ReceivePayloadAsync(CancellationToken ct = default); /// /// Disconnects from the transport endpoint. diff --git a/Backend/Remora.Discord.Gateway/Transport/WebSocketPayloadTransportService.cs b/Backend/Remora.Discord.Gateway/Transport/WebSocketPayloadTransportService.cs index ec39136ea7..c1e1e255a8 100644 --- a/Backend/Remora.Discord.Gateway/Transport/WebSocketPayloadTransportService.cs +++ b/Backend/Remora.Discord.Gateway/Transport/WebSocketPayloadTransportService.cs @@ -21,11 +21,13 @@ // using System; -using System.Buffers; using System.Collections.Generic; -using System.IO; +using System.Diagnostics; using System.Net.Http; using System.Net.WebSockets; +#if NET6_0_OR_GREATER +using System.Runtime.CompilerServices; +#endif using System.Text.Json; using System.Text.RegularExpressions; using System.Threading; @@ -149,7 +151,10 @@ public async Task ConnectAsync(Uri endpoint, CancellationToken ct = defa } /// - public async Task SendPayloadAsync(IPayload payload, CancellationToken ct = default) + #if NET6_0_OR_GREATER + [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] + #endif + public async ValueTask SendPayloadAsync(IPayload payload, CancellationToken ct = default) { if (_clientWebSocket is null) { @@ -161,60 +166,46 @@ public async Task SendPayloadAsync(IPayload payload, CancellationToken c return new InvalidOperationError("The socket was not open."); } - await using var memoryStream = new MemoryStream(); + // Most common case is heartbeat ({"op":1"}), so this is more than enough + // if it isn't, the writer resizes the array, which is rare, and a cost we're + // fine with eating. + using var bufferWriter = new ArrayPoolBufferWriter(128); - byte[]? buffer = null; - try - { - await JsonSerializer.SerializeAsync(memoryStream, payload, _jsonOptions, ct); - - if (memoryStream.Length > 4096) - { - return new NotSupportedError - ( - "The payload was too large to be accepted by the gateway." - ); - } - - buffer = ArrayPool.Shared.Rent((int)memoryStream.Length); - memoryStream.Seek(0, SeekOrigin.Begin); - - // Copy the data - var copiedBytes = 0; - while (copiedBytes < memoryStream.Length) - { - var bufferSegment = new ArraySegment(buffer, copiedBytes, (int)memoryStream.Length - copiedBytes); - copiedBytes += await memoryStream.ReadAsync(bufferSegment, ct); - } + // `SkipValidation = !Debugger.IsAttached` is to replicate what STJ does internally; + // it's worth noting however that STJ uses #if !DEBUG, while we simply check if + // there's a debugger, which is good enough. + await using var writer = new Utf8JsonWriter(bufferWriter, new JsonWriterOptions { Encoder = _jsonOptions.Encoder, Indented = false, SkipValidation = !Debugger.IsAttached }); + JsonSerializer.Serialize(writer, payload, _jsonOptions); - // Send the whole payload as one chunk - var segment = new ArraySegment(buffer, 0, (int)memoryStream.Length); - - await _clientWebSocket.SendAsync(segment, WebSocketMessageType.Text, true, ct); + if (bufferWriter.WrittenSpan.Length > 4096) + { + return new NotSupportedError + ( + "The payload was too large to be accepted by the gateway." + ); + } - if (_clientWebSocket.CloseStatus.HasValue) - { - if (Enum.IsDefined(typeof(GatewayCloseStatus), (int)_clientWebSocket.CloseStatus)) - { - return new GatewayDiscordError((GatewayCloseStatus)_clientWebSocket.CloseStatus); - } + // Send the whole payload as one chunk + await _clientWebSocket.SendAsync(bufferWriter.WrittenMemory, WebSocketMessageType.Text, true, ct); - return new GatewayWebSocketError(_clientWebSocket.CloseStatus.Value); - } + if (!_clientWebSocket.CloseStatus.HasValue) + { + return Result.FromSuccess(); } - finally + + if (Enum.IsDefined(typeof(GatewayCloseStatus), (int)_clientWebSocket.CloseStatus)) { - if (buffer is not null) - { - ArrayPool.Shared.Return(buffer); - } + return new GatewayDiscordError((GatewayCloseStatus)_clientWebSocket.CloseStatus); } - return Result.FromSuccess(); + return new GatewayWebSocketError(_clientWebSocket.CloseStatus.Value); } /// - public async Task> ReceivePayloadAsync(CancellationToken ct = default) + #if NET6_0_OR_GREATER + [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] + #endif + public async ValueTask> ReceivePayloadAsync(CancellationToken ct = default) { if (_clientWebSocket is null) { diff --git a/Backend/Remora.Discord.Rest/API/Applications/DiscordRestApplicationAPI.cs b/Backend/Remora.Discord.Rest/API/Applications/DiscordRestApplicationAPI.cs index 8a8e158f0f..dc97d60fc6 100644 --- a/Backend/Remora.Discord.Rest/API/Applications/DiscordRestApplicationAPI.cs +++ b/Backend/Remora.Discord.Rest/API/Applications/DiscordRestApplicationAPI.cs @@ -20,7 +20,9 @@ // along with this program. If not, see . // +using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text.Json; using System.Threading; @@ -30,6 +32,7 @@ using Remora.Discord.API.Abstractions.Rest; using Remora.Discord.Caching.Abstractions.Services; using Remora.Discord.Rest.Extensions; +using Remora.Discord.Rest.Utility; using Remora.Rest; using Remora.Rest.Core; using Remora.Rest.Extensions; @@ -100,6 +103,8 @@ public virtual async Task> CreateGlobalApplicationCo Optional defaultMemberPermissions = default, Optional dmPermission = default, Optional isNsfw = default, + Optional> allowedIntegrationTypes = default, + Optional> allowedContextTypes = default, CancellationToken ct = default ) { @@ -148,6 +153,8 @@ public virtual async Task> CreateGlobalApplicationCo json.Write("default_member_permissions", defaultMemberPermissions, this.JsonOptions); json.Write("dm_permission", dmPermission, this.JsonOptions); json.Write("nsfw", isNsfw, this.JsonOptions); + json.Write("contexts", allowedContextTypes, this.JsonOptions); + json.Write("integration_types", allowedIntegrationTypes, this.JsonOptions); } ) .WithRateLimitContext(this.RateLimitCache), @@ -227,6 +234,8 @@ public virtual async Task> EditGlobalApplicationComm Optional defaultMemberPermissions = default, Optional dmPermission = default, Optional isNsfw = default, + Optional> allowedIntegrationTypes = default, + Optional> allowedContextTypes = default, CancellationToken ct = default ) { @@ -264,6 +273,8 @@ public virtual async Task> EditGlobalApplicationComm json.Write("default_member_permissions", defaultMemberPermissions, this.JsonOptions); json.Write("dm_permission", dmPermission, this.JsonOptions); json.Write("nsfw", isNsfw, this.JsonOptions); + json.Write("contexts", allowedContextTypes, this.JsonOptions); + json.Write("integration_types", allowedIntegrationTypes, this.JsonOptions); } ) .WithRateLimitContext(this.RateLimitCache), @@ -376,6 +387,8 @@ public virtual async Task> CreateGuildApplicationCom Optional?> descriptionLocalizations = default, Optional defaultMemberPermissions = default, Optional isNsfw = default, + Optional> allowedIntegrationTypes = default, + Optional> allowedContextTypes = default, CancellationToken ct = default ) { @@ -423,6 +436,8 @@ public virtual async Task> CreateGuildApplicationCom json.Write("description_localizations", descriptionLocalizations, this.JsonOptions); json.Write("default_member_permissions", defaultMemberPermissions, this.JsonOptions); json.Write("nsfw", isNsfw, this.JsonOptions); + json.Write("contexts", allowedContextTypes, this.JsonOptions); + json.Write("integration_types", allowedIntegrationTypes, this.JsonOptions); } ) .WithRateLimitContext(this.RateLimitCache), @@ -460,6 +475,8 @@ public virtual async Task> EditGuildApplicationComma Optional?> descriptionLocalizations = default, Optional defaultMemberPermissions = default, Optional isNsfw = default, + Optional> allowedIntegrationTypes = default, + Optional> allowedContextTypes = default, CancellationToken ct = default ) { @@ -495,6 +512,8 @@ public virtual async Task> EditGuildApplicationComma json.Write("description_localizations", descriptionLocalizations, this.JsonOptions); json.Write("default_member_permissions", defaultMemberPermissions, this.JsonOptions); json.Write("nsfw", isNsfw, this.JsonOptions); + json.Write("contexts", allowedContextTypes, this.JsonOptions); + json.Write("integration_types", allowedIntegrationTypes, this.JsonOptions); } ) .WithRateLimitContext(this.RateLimitCache), @@ -651,4 +670,69 @@ public Task> GetCurrentApplicationAsync(CancellationToken c ct: ct ); } + + /// + public async Task> EditCurrentApplicationAsync + ( + Optional customInstallUrl = default, + Optional description = default, + Optional roleConnectionsVerificationUrl = default, + Optional installParams = default, + Optional flags = default, + Optional icon = default, + Optional coverImage = default, + Optional interactionsEndpointUrl = default, + Optional> tags = default, + Optional> integrationTypes = default, + CancellationToken ct = default + ) + { + var packIcon = await ImagePacker.PackImageAsync(icon!, ct); + if (!packIcon.IsSuccess) + { + return Result.FromError(packIcon); + } + + Optional base64EncodedIcon = packIcon.Entity!; + + var packCover = await ImagePacker.PackImageAsync(coverImage!, ct); + if (!packCover.IsSuccess) + { + return Result.FromError(packCover); + } + + Optional base64EncodedCover = packCover.Entity!; + + return await this.RestHttpClient.PatchAsync + ( + "applications/@me", + b => + { + b.WithJson + ( + json => + { + json.Write("custom_install_url", customInstallUrl, this.JsonOptions); + json.Write("description", description, this.JsonOptions); + json.Write + ( + "role_connections_verification_url", + roleConnectionsVerificationUrl, + this.JsonOptions + ); + json.Write("install_params", installParams, this.JsonOptions); + json.Write("flags", flags, this.JsonOptions); + json.Write("icon", base64EncodedIcon, this.JsonOptions); + json.Write("cover_image", base64EncodedCover, this.JsonOptions); + json.Write("interactions_endpoint_url", interactionsEndpointUrl, this.JsonOptions); + json.Write("tags", tags, this.JsonOptions); + json.Write("integration_types", integrationTypes, this.JsonOptions); + } + ); + + b.WithRateLimitContext(this.RateLimitCache); + }, + ct: ct + ); + } } diff --git a/Backend/Remora.Discord.Rest/API/Channels/DiscordRestChannelAPI.cs b/Backend/Remora.Discord.Rest/API/Channels/DiscordRestChannelAPI.cs index 33be7ea2cb..76c599fbb9 100644 --- a/Backend/Remora.Discord.Rest/API/Channels/DiscordRestChannelAPI.cs +++ b/Backend/Remora.Discord.Rest/API/Channels/DiscordRestChannelAPI.cs @@ -451,6 +451,48 @@ public Task> ModifyForumChannelAsync ); } + /// + public Task> ModifyMediaChannelAsync + ( + Snowflake channelID, + Optional name = default, + Optional position = default, + Optional topic = default, + Optional isNsfw = default, + Optional rateLimitPerUser = default, + Optional?> permissionOverwrites = default, + Optional parentID = default, + Optional defaultAutoArchiveDuration = default, + Optional flags = default, + Optional> availableTags = default, + Optional defaultReactionEmoji = default, + Optional defaultThreadRateLimitPerUser = default, + Optional defaultSortOrder = default, + Optional reason = default, + CancellationToken ct = default + ) + { + return ModifyChannelAsync + ( + channelID, + name, + position: position, + topic: topic, + isNsfw: isNsfw, + rateLimitPerUser: rateLimitPerUser, + permissionOverwrites: permissionOverwrites, + parentID: parentID, + defaultAutoArchiveDuration: defaultAutoArchiveDuration, + flags: flags, + availableTags: availableTags, + defaultReactionEmoji: defaultReactionEmoji, + defaultThreadRateLimitPerUser: defaultThreadRateLimitPerUser, + defaultSortOrder: defaultSortOrder, + reason: reason, + ct: ct + ); + } + /// public virtual Task DeleteChannelAsync ( @@ -479,7 +521,7 @@ public virtual async Task>> GetChannelMessagesAsy ) { var hasAny = around.HasValue || before.HasValue || after.HasValue; - var hasStrictlyOne = (around.HasValue ^ before.HasValue ^ after.HasValue) + var hasStrictlyOne = around.HasValue ^ before.HasValue ^ after.HasValue && !(around.HasValue && before.HasValue && after.HasValue); if (hasAny && !hasStrictlyOne) @@ -560,6 +602,7 @@ public virtual async Task> CreateMessageAsync Optional> stickerIDs = default, Optional>> attachments = default, Optional flags = default, + Optional enforceNonce = default, CancellationToken ct = default ) { @@ -629,6 +672,7 @@ public virtual async Task> CreateMessageAsync json.Write("sticker_ids", stickerIDs, this.JsonOptions); json.Write("attachments", attachmentList, this.JsonOptions); json.Write("flags", flags, this.JsonOptions); + json.Write("enforce_nonce", enforceNonce, this.JsonOptions); } ) .WithRateLimitContext(this.RateLimitCache); diff --git a/Backend/Remora.Discord.Rest/API/Guilds/DiscordRestGuildAPI.cs b/Backend/Remora.Discord.Rest/API/Guilds/DiscordRestGuildAPI.cs index 6567313ce0..8bedf78465 100644 --- a/Backend/Remora.Discord.Rest/API/Guilds/DiscordRestGuildAPI.cs +++ b/Backend/Remora.Discord.Rest/API/Guilds/DiscordRestGuildAPI.cs @@ -319,6 +319,7 @@ public virtual Task> CreateGuildChannelAsync Optional?> availableTags = default, Optional defaultSortOrder = default, Optional defaultForumLayout = default, + Optional defaultThreadRateLimitPerUser = default, Optional reason = default, CancellationToken ct = default ) @@ -349,6 +350,7 @@ public virtual Task> CreateGuildChannelAsync json.Write("available_tags", availableTags, this.JsonOptions); json.Write("default_sort_order", defaultSortOrder, this.JsonOptions); json.Write("default_forum_layout", defaultForumLayout, this.JsonOptions); + json.Write("default_thread_rate_limit_per_user", defaultThreadRateLimitPerUser, this.JsonOptions); } ) .WithRateLimitContext(this.RateLimitCache), @@ -368,6 +370,7 @@ public Task> CreateGuildTextChannelAsync Optional parentID = default, Optional isNsfw = default, Optional defaultAutoArchiveDuration = default, + Optional defaultThreadRateLimitPerUser = default, Optional reason = default, CancellationToken ct = default ) => CreateGuildChannelAsync @@ -382,6 +385,7 @@ public Task> CreateGuildTextChannelAsync parentID: parentID, isNsfw: isNsfw, defaultAutoArchiveDuration: defaultAutoArchiveDuration, + defaultThreadRateLimitPerUser: defaultThreadRateLimitPerUser, reason: reason, ct: ct ); @@ -397,6 +401,7 @@ public Task> CreateGuildAnnouncementChannelAsync Optional parentID = default, Optional isNsfw = default, Optional defaultAutoArchiveDuration = default, + Optional defaultThreadRateLimitPerUser = default, Optional reason = default, CancellationToken ct = default ) => CreateGuildChannelAsync @@ -410,6 +415,7 @@ public Task> CreateGuildAnnouncementChannelAsync parentID: parentID, isNsfw: isNsfw, defaultAutoArchiveDuration: defaultAutoArchiveDuration, + defaultThreadRateLimitPerUser: defaultThreadRateLimitPerUser, reason: reason, ct: ct ); @@ -420,6 +426,7 @@ public Task> CreateGuildForumChannelAsync Snowflake guildID, string name, Optional topic = default, + Optional rateLimitPerUser = default, Optional position = default, Optional?> permissionOverwrites = default, Optional parentID = default, @@ -429,6 +436,7 @@ public Task> CreateGuildForumChannelAsync Optional?> availableTags = default, Optional defaultSortOrder = default, Optional defaultForumLayout = default, + Optional defaultThreadRateLimitPerUser = default, Optional reason = default, CancellationToken ct = default ) => CreateGuildChannelAsync @@ -437,6 +445,7 @@ public Task> CreateGuildForumChannelAsync name, ChannelType.GuildForum, topic: topic, + rateLimitPerUser: rateLimitPerUser, position: position, permissionOverwrites: permissionOverwrites, parentID: parentID, @@ -446,6 +455,43 @@ public Task> CreateGuildForumChannelAsync availableTags: availableTags, defaultSortOrder: defaultSortOrder, defaultForumLayout: defaultForumLayout, + defaultThreadRateLimitPerUser: defaultThreadRateLimitPerUser, + reason: reason, + ct: ct + ); + + /// + public Task> CreateGuildMediaChannelAsync + ( + Snowflake guildID, + string name, + Optional topic = default, + Optional rateLimitPerUser = default, + Optional position = default, + Optional?> permissionOverwrites = default, + Optional parentID = default, + Optional defaultAutoArchiveDuration = default, + Optional defaultReactionEmoji = default, + Optional?> availableTags = default, + Optional defaultSortOrder = default, + Optional defaultThreadRateLimitPerUser = default, + Optional reason = default, + CancellationToken ct = default + ) => CreateGuildChannelAsync + ( + guildID, + name, + ChannelType.GuildMedia, + topic: topic, + rateLimitPerUser: rateLimitPerUser, + position: position, + permissionOverwrites: permissionOverwrites, + parentID: parentID, + defaultAutoArchiveDuration: defaultAutoArchiveDuration, + defaultReactionEmoji: defaultReactionEmoji, + availableTags: availableTags, + defaultSortOrder: defaultSortOrder, + defaultThreadRateLimitPerUser: defaultThreadRateLimitPerUser, reason: reason, ct: ct ); @@ -867,6 +913,33 @@ public virtual Task RemoveGuildBanAsync ); } + /// + public Task> BulkGuildBanAsync + ( + Snowflake guildID, + IReadOnlyList userIDs, + Optional deleteMessageSeconds = default, + Optional reason = default, + CancellationToken ct = default + ) + { + return this.RestHttpClient.PostAsync + ( + $"guilds/{guildID}/bulk-ban", + b => b.WithJson + ( + json => + { + json.Write("user_ids", userIDs, this.JsonOptions); + json.Write("delete_message_seconds", deleteMessageSeconds, this.JsonOptions); + } + ) + .WithRateLimitContext(this.RateLimitCache) + .AddAuditLogReason(reason), + ct: ct + ); + } + /// public virtual Task>> GetGuildRolesAsync ( diff --git a/Backend/Remora.Discord.Rest/API/Monetization/DiscordRestMonetizationAPI.cs b/Backend/Remora.Discord.Rest/API/Monetization/DiscordRestMonetizationAPI.cs new file mode 100644 index 0000000000..c368a89f92 --- /dev/null +++ b/Backend/Remora.Discord.Rest/API/Monetization/DiscordRestMonetizationAPI.cs @@ -0,0 +1,146 @@ +// +// DiscordRestMonetizationAPI.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Remora.Discord.API.Abstractions.Objects; +using Remora.Discord.API.Abstractions.Rest; +using Remora.Discord.Caching.Abstractions.Services; +using Remora.Discord.Rest.Extensions; +using Remora.Rest; +using Remora.Rest.Core; +using Remora.Rest.Extensions; +using Remora.Results; + +namespace Remora.Discord.Rest.API; + +/// +[PublicAPI] +public class DiscordRestMonetizationAPI : AbstractDiscordRestAPI, IDiscordRestMonetizationAPI +{ + /// + /// Initializes a new instance of the class. + /// + /// The Discord HTTP client. + /// The JSON options. + /// The memory cache used for rate limits. + public DiscordRestMonetizationAPI + ( + IRestHttpClient restHttpClient, + JsonSerializerOptions jsonOptions, + ICacheProvider rateLimitCache + ) + : base(restHttpClient, jsonOptions, rateLimitCache) + { + } + + /// + public Task>> ListEntitlementsAsync + ( + Snowflake applicationID, + Optional userID = default, + Optional> skuIDs = default, + Optional before = default, + Optional after = default, + Optional limit = default, + Optional guildID = default, + Optional excludeEnded = default, + CancellationToken ct = default + ) => this.RestHttpClient.GetAsync> + ( + $"applications/{applicationID}/entitlements", + b => b + .AddQueryParameter("user_id", userID) + .AddQueryParameter("sku_ids", skuIDs.Map(ids => string.Join(',', ids.Select(id => id.ToString())))) + .AddQueryParameter("before", before) + .AddQueryParameter("after", after) + .AddQueryParameter("limit", limit) + .AddQueryParameter("guild_id", guildID) + .AddQueryParameter("exclude_ended", excludeEnded) + .WithRateLimitContext(this.RateLimitCache), + ct: ct + ); + + /// + public Task ConsumeEntitlementAsync + ( + Snowflake applicationID, + Snowflake entitlementID, + CancellationToken ct = default + ) => this.RestHttpClient.PostAsync + ( + $"applications/{applicationID}/entitlements/{entitlementID}/consume", + b => b.WithRateLimitContext(this.RateLimitCache), + ct: ct + ); + + /// + public Task> CreateTestEntitlementAsync + ( + Snowflake applicationID, + Snowflake skuID, + Snowflake ownerID, + EntitlementOwnerType ownerType, + CancellationToken ct = default + ) => this.RestHttpClient.PostAsync + ( + $"applications/{applicationID}/entitlements", + b => b + .WithJson + ( + json => + { + json.Write("sku_id", skuID, this.JsonOptions); + json.Write("owner_id", ownerID, this.JsonOptions); + json.Write("owner_type", (int)ownerType, this.JsonOptions); + } + ) + .WithRateLimitContext(this.RateLimitCache), + ct: ct + ); + + /// + public Task DeleteTestEntitlementAsync + ( + Snowflake applicationID, + Snowflake entitlementID, + CancellationToken ct = default + ) => this.RestHttpClient.DeleteAsync + ( + $"applications/{applicationID}/entitlements/{entitlementID}", + b => b.WithRateLimitContext(this.RateLimitCache), + ct: ct + ); + + /// + public Task>> ListSKUsAsync(Snowflake applicationID, CancellationToken ct = default) + => this.RestHttpClient.GetAsync> + ( + $"applications/{applicationID}/skus", + b => b.WithRateLimitContext(this.RateLimitCache), + ct: ct + ); +} diff --git a/Backend/Remora.Discord.Rest/API/StageInstances/DiscordRestStageInstanceAPI.cs b/Backend/Remora.Discord.Rest/API/StageInstances/DiscordRestStageInstanceAPI.cs index 44a24ba798..bc07b9fd0d 100644 --- a/Backend/Remora.Discord.Rest/API/StageInstances/DiscordRestStageInstanceAPI.cs +++ b/Backend/Remora.Discord.Rest/API/StageInstances/DiscordRestStageInstanceAPI.cs @@ -62,6 +62,7 @@ public virtual Task> CreateStageInstanceAsync string topic, Optional privacyLevel = default, Optional sendStartNotification = default, + Optional guildScheduledEventID = default, Optional reason = default, CancellationToken ct = default ) @@ -79,6 +80,7 @@ public virtual Task> CreateStageInstanceAsync json.Write("topic", topic, this.JsonOptions); json.Write("privacy_level", privacyLevel, this.JsonOptions); json.Write("send_start_notification", sendStartNotification, this.JsonOptions); + json.Write("guild_scheduled_event_id", guildScheduledEventID, this.JsonOptions); } ) .WithRateLimitContext(this.RateLimitCache), diff --git a/Backend/Remora.Discord.Rest/API/Stickers/DiscordRestStickerAPI.cs b/Backend/Remora.Discord.Rest/API/Stickers/DiscordRestStickerAPI.cs index 968b45f499..a31ee8bd1d 100644 --- a/Backend/Remora.Discord.Rest/API/Stickers/DiscordRestStickerAPI.cs +++ b/Backend/Remora.Discord.Rest/API/Stickers/DiscordRestStickerAPI.cs @@ -131,7 +131,8 @@ public virtual async Task> CreateGuildStickerAsync { return new ArgumentOutOfRangeError ( - nameof(description), "The description must be either empty, or between 2 and 30 characters." + nameof(description), + "The description must be either empty, or between 2 and 30 characters." ); } @@ -175,7 +176,8 @@ public virtual async Task> ModifyGuildStickerAsync { return new ArgumentOutOfRangeError ( - nameof(description), "The description must be either empty, or between 2 and 30 characters." + nameof(description), + "The description must be either empty, or between 2 and 30 characters." ); } diff --git a/Backend/Remora.Discord.Rest/API/Users/DiscordRestUserAPI.cs b/Backend/Remora.Discord.Rest/API/Users/DiscordRestUserAPI.cs index d32a341a7d..ea65be06a6 100644 --- a/Backend/Remora.Discord.Rest/API/Users/DiscordRestUserAPI.cs +++ b/Backend/Remora.Discord.Rest/API/Users/DiscordRestUserAPI.cs @@ -230,7 +230,7 @@ public virtual Task> CreateDMAsync } /// - public virtual Task>> GetUserConnectionsAsync + public virtual Task>> GetCurrentUserConnectionsAsync ( CancellationToken ct = default ) @@ -244,7 +244,7 @@ public virtual Task>> GetUserConnectionsAsync } /// - public virtual Task> GetUserApplicationRoleConnectionAsync + public virtual Task> GetCurrentUserApplicationRoleConnectionAsync ( Snowflake applicationID, CancellationToken ct = default @@ -259,7 +259,7 @@ public virtual Task> GetUserApplicationRoleCo } /// - public virtual async Task> UpdateUserApplicationRoleConnectionAsync + public virtual async Task> UpdateCurrentUserApplicationRoleConnectionAsync ( Snowflake applicationID, Optional platformName = default, diff --git a/Backend/Remora.Discord.Rest/API/Webhooks/DiscordRestWebhookAPI.cs b/Backend/Remora.Discord.Rest/API/Webhooks/DiscordRestWebhookAPI.cs index e8872d896d..a2260e3c05 100644 --- a/Backend/Remora.Discord.Rest/API/Webhooks/DiscordRestWebhookAPI.cs +++ b/Backend/Remora.Discord.Rest/API/Webhooks/DiscordRestWebhookAPI.cs @@ -304,6 +304,7 @@ public virtual Task> ExecuteWebhookAsync Optional>> attachments = default, Optional flags = default, Optional threadName = default, + Optional> appliedTags = default, CancellationToken ct = default ) { @@ -363,6 +364,7 @@ public virtual Task> ExecuteWebhookAsync json.Write("attachments", attachmentList, this.JsonOptions); json.Write("flags", flags, this.JsonOptions); json.Write("thread_name", threadName, this.JsonOptions); + json.Write("applied_tags", appliedTags, this.JsonOptions); } ) .WithRateLimitContext(this.RateLimitCache); diff --git a/Backend/Remora.Discord.Rest/Extensions/RestRequestBuilderExtensions.cs b/Backend/Remora.Discord.Rest/Extensions/RestRequestBuilderExtensions.cs index 18cff206c6..208ab76a8a 100644 --- a/Backend/Remora.Discord.Rest/Extensions/RestRequestBuilderExtensions.cs +++ b/Backend/Remora.Discord.Rest/Extensions/RestRequestBuilderExtensions.cs @@ -92,4 +92,25 @@ this RestRequestBuilder builder return builder; } + + /// + /// Adds the given optional value as a query string parameter if the optional contains a value. + /// + /// + /// The value will be added as its string representation as returned by . + /// + /// The request builder. + /// The name of the parameter. + /// The value. + /// The type of the underlying value. + /// The builder. + public static RestRequestBuilder AddQueryParameter + ( + this RestRequestBuilder builder, + string name, + Optional value + ) + => value.HasValue + ? builder.AddQueryParameter(name, value.Value?.ToString() ?? string.Empty) + : builder; } diff --git a/Backend/Remora.Discord.Rest/Extensions/ServiceCollectionExtensions.cs b/Backend/Remora.Discord.Rest/Extensions/ServiceCollectionExtensions.cs index 9863a58c87..ae08d89d8f 100644 --- a/Backend/Remora.Discord.Rest/Extensions/ServiceCollectionExtensions.cs +++ b/Backend/Remora.Discord.Rest/Extensions/ServiceCollectionExtensions.cs @@ -218,6 +218,13 @@ public static IServiceCollection AddDiscordRest s.GetRequiredService() )); + serviceCollection.TryAddTransient(s => new DiscordRestMonetizationAPI + ( + s.GetRequiredService(), + s.GetRequiredService>().Get("Discord"), + s.GetRequiredService() + )); + var rateLimitPolicy = DiscordRateLimitPolicy.Create(); var retryDelay = Backoff.DecorrelatedJitterBackoffV2(TimeSpan.FromSeconds(1), 5); var clientBuilder = serviceCollection diff --git a/Backend/Remora.Discord.Rest/Remora.Discord.Rest.csproj b/Backend/Remora.Discord.Rest/Remora.Discord.Rest.csproj index 3fe604a167..67ecafc1be 100644 --- a/Backend/Remora.Discord.Rest/Remora.Discord.Rest.csproj +++ b/Backend/Remora.Discord.Rest/Remora.Discord.Rest.csproj @@ -1,16 +1,11 @@ - 48.0.0 + 50.0.0 Remora.Discord's implementation of Discord's REST API Update dependencies. - BREAKING: Implement support for join and mention raid protection - BREAKING: Add RateLimitPerUser to Voice and Stage channels. - Implement onboarding mode. - BREAKING: Add defaultForumLayout parameter. - Implement approximate guild cound and partial guild. - BREAKING: Implement with_counts for user guild endpoint. + BREAKING: Implement applied tags for webhooks. diff --git a/Backend/Remora.Discord.Rest/Remora.Discord.Rest.csproj.DotSettings b/Backend/Remora.Discord.Rest/Remora.Discord.Rest.csproj.DotSettings index 11d863b84d..f6a96f8ea2 100644 --- a/Backend/Remora.Discord.Rest/Remora.Discord.Rest.csproj.DotSettings +++ b/Backend/Remora.Discord.Rest/Remora.Discord.Rest.csproj.DotSettings @@ -9,6 +9,7 @@ True True True + True True True True diff --git a/Backend/Remora.Discord.Unstable/Remora.Discord.Unstable.csproj b/Backend/Remora.Discord.Unstable/Remora.Discord.Unstable.csproj index 57d45da9db..4730b1fe52 100644 --- a/Backend/Remora.Discord.Unstable/Remora.Discord.Unstable.csproj +++ b/Backend/Remora.Discord.Unstable/Remora.Discord.Unstable.csproj @@ -5,7 +5,7 @@ - 6.0.29 + 6.0.31 Unstable or experimental features of Remora.Discord Update dependencies. diff --git a/Backend/Remora.Discord.Unstable/Unstable/Extensions/ServiceCollectionExtensions.cs b/Backend/Remora.Discord.Unstable/Unstable/Extensions/ServiceCollectionExtensions.cs index d580fb126d..fd81143535 100644 --- a/Backend/Remora.Discord.Unstable/Unstable/Extensions/ServiceCollectionExtensions.cs +++ b/Backend/Remora.Discord.Unstable/Unstable/Extensions/ServiceCollectionExtensions.cs @@ -50,13 +50,17 @@ public static IServiceCollection AddExperimentalDiscordApi string? optionsName = "Discord" ) { - serviceCollection.Configure(optionsName, jsonOptions => - { - jsonOptions.AddConverter(); + serviceCollection.Configure + ( + optionsName, + jsonOptions => + { + jsonOptions.AddConverter(); - jsonOptions.AddDataObjectConverter(); - jsonOptions.AddDataObjectConverter(); - }); + jsonOptions.AddDataObjectConverter(); + jsonOptions.AddDataObjectConverter(); + } + ); return serviceCollection; } diff --git a/Directory.Packages.props b/Directory.Packages.props index c43e2f17bb..f54917a7c6 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,34 +4,34 @@ - + - - - - - - - - - + + + + + + + + + - - + + - - - - - - - - + + + + + + + + - - + + \ No newline at end of file diff --git a/Remora.Discord.Commands/Attributes/AllowedContextsAttribute.cs b/Remora.Discord.Commands/Attributes/AllowedContextsAttribute.cs new file mode 100644 index 0000000000..fa85204db0 --- /dev/null +++ b/Remora.Discord.Commands/Attributes/AllowedContextsAttribute.cs @@ -0,0 +1,43 @@ +// +// AllowedContextsAttribute.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; +using System.Collections.Generic; +using System.Linq; +using JetBrains.Annotations; +using Remora.Discord.API.Abstractions.Objects; + +namespace Remora.Discord.Commands.Attributes; + +/// +/// Defines the contexts in which a command can be invoked. +/// +/// The contexts the command can be invoked. +[PublicAPI] +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] +public class AllowedContextsAttribute(params InteractionContextType[] contexts) : Attribute +{ + /// + /// Gets a value specifying the allowed contexts. + /// + public IReadOnlyList Contexts { get; } = contexts.ToArray(); +} diff --git a/Remora.Discord.Commands/Attributes/AutocompleteAttribute.cs b/Remora.Discord.Commands/Attributes/AutocompleteAttribute.cs index f45c6d6212..77bc5ac55e 100644 --- a/Remora.Discord.Commands/Attributes/AutocompleteAttribute.cs +++ b/Remora.Discord.Commands/Attributes/AutocompleteAttribute.cs @@ -30,6 +30,4 @@ namespace Remora.Discord.Commands.Attributes; /// [PublicAPI] [AttributeUsage(AttributeTargets.Parameter)] -public class AutocompleteAttribute : Attribute -{ -} +public class AutocompleteAttribute : Attribute; diff --git a/Remora.Discord.Commands/Attributes/DiscordInstallContextAttribute.cs b/Remora.Discord.Commands/Attributes/DiscordInstallContextAttribute.cs new file mode 100644 index 0000000000..8b6aca80d7 --- /dev/null +++ b/Remora.Discord.Commands/Attributes/DiscordInstallContextAttribute.cs @@ -0,0 +1,42 @@ +// +// DiscordInstallContextAttribute.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; +using System.Collections.Generic; +using JetBrains.Annotations; +using Remora.Discord.API.Abstractions.Objects; + +namespace Remora.Discord.Commands.Attributes; + +/// +/// Specifies that the command is valid to install in the given contexts. +/// +/// The contexts that the command can be installed into. +[PublicAPI] +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] +public class DiscordInstallContextAttribute(params ApplicationIntegrationType[] installTypes) : Attribute +{ + /// + /// Gets a value specifying the contexts that the command can be installed into. + /// + public IReadOnlyList InstallTypes { get; } = installTypes; +} diff --git a/Remora.Discord.Commands/Attributes/ExcludeFromChoicesAttribute.cs b/Remora.Discord.Commands/Attributes/ExcludeFromChoicesAttribute.cs index c031df77c9..cd6d544eba 100644 --- a/Remora.Discord.Commands/Attributes/ExcludeFromChoicesAttribute.cs +++ b/Remora.Discord.Commands/Attributes/ExcludeFromChoicesAttribute.cs @@ -30,6 +30,4 @@ namespace Remora.Discord.Commands.Attributes; /// [PublicAPI] [AttributeUsage(AttributeTargets.Field)] -public class ExcludeFromChoicesAttribute : Attribute -{ -} +public class ExcludeFromChoicesAttribute : Attribute; diff --git a/Remora.Discord.Commands/Attributes/ExcludeFromSlashCommandsAttribute.cs b/Remora.Discord.Commands/Attributes/ExcludeFromSlashCommandsAttribute.cs index d53882ad76..4b83ae5285 100644 --- a/Remora.Discord.Commands/Attributes/ExcludeFromSlashCommandsAttribute.cs +++ b/Remora.Discord.Commands/Attributes/ExcludeFromSlashCommandsAttribute.cs @@ -30,6 +30,4 @@ namespace Remora.Discord.Commands.Attributes; /// [PublicAPI] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] -public class ExcludeFromSlashCommandsAttribute : Attribute -{ -} +public class ExcludeFromSlashCommandsAttribute : Attribute; diff --git a/Remora.Discord.Commands/Conditions/Attributes/RequireOwnerAttribute.cs b/Remora.Discord.Commands/Conditions/Attributes/RequireOwnerAttribute.cs index e5b0516660..dfca653c1c 100644 --- a/Remora.Discord.Commands/Conditions/Attributes/RequireOwnerAttribute.cs +++ b/Remora.Discord.Commands/Conditions/Attributes/RequireOwnerAttribute.cs @@ -31,6 +31,4 @@ namespace Remora.Discord.Commands.Conditions; /// [PublicAPI] [AttributeUsage(AttributeTargets.Method)] -public class RequireOwnerAttribute : ConditionAttribute -{ -} +public class RequireOwnerAttribute : ConditionAttribute; diff --git a/Remora.Discord.Commands/Contexts/Interfaces/IInteractionCommandContext.cs b/Remora.Discord.Commands/Contexts/Interfaces/IInteractionCommandContext.cs index 43aba52242..b19c8e7c84 100644 --- a/Remora.Discord.Commands/Contexts/Interfaces/IInteractionCommandContext.cs +++ b/Remora.Discord.Commands/Contexts/Interfaces/IInteractionCommandContext.cs @@ -28,6 +28,4 @@ namespace Remora.Discord.Commands.Contexts; /// Represents contextual information about a currently executing interaction-based command. /// [PublicAPI] -public interface IInteractionCommandContext : IInteractionContext, ICommandContext -{ -} +public interface IInteractionCommandContext : IInteractionContext, ICommandContext; diff --git a/Remora.Discord.Commands/Contexts/Interfaces/IOperationContext.cs b/Remora.Discord.Commands/Contexts/Interfaces/IOperationContext.cs index 30d0d41628..135c88660c 100644 --- a/Remora.Discord.Commands/Contexts/Interfaces/IOperationContext.cs +++ b/Remora.Discord.Commands/Contexts/Interfaces/IOperationContext.cs @@ -28,6 +28,4 @@ namespace Remora.Discord.Commands.Contexts; /// Marker interface for various context types related to commands and interactions. /// [PublicAPI] -public interface IOperationContext -{ -} +public interface IOperationContext; diff --git a/Remora.Discord.Commands/Contexts/Interfaces/ITextCommandContext.cs b/Remora.Discord.Commands/Contexts/Interfaces/ITextCommandContext.cs index 240496e04c..8e8c7caabc 100644 --- a/Remora.Discord.Commands/Contexts/Interfaces/ITextCommandContext.cs +++ b/Remora.Discord.Commands/Contexts/Interfaces/ITextCommandContext.cs @@ -28,6 +28,4 @@ namespace Remora.Discord.Commands.Contexts; /// Represents contextual information about a currently executing text-based command. /// [PublicAPI] -public interface ITextCommandContext : IMessageContext, ICommandContext -{ -} +public interface ITextCommandContext : IMessageContext, ICommandContext; diff --git a/Remora.Discord.Commands/Extensions/ApplicationCommandTypeExtensions.cs b/Remora.Discord.Commands/Extensions/ApplicationCommandTypeExtensions.cs index 4a0530edbb..7f3ef0a7ff 100644 --- a/Remora.Discord.Commands/Extensions/ApplicationCommandTypeExtensions.cs +++ b/Remora.Discord.Commands/Extensions/ApplicationCommandTypeExtensions.cs @@ -42,7 +42,8 @@ public static class ApplicationCommandTypeExtensions /// public static string AsParameterName(this ApplicationCommandType commandType) { - return commandType switch { + return commandType switch + { ApplicationCommandType.Message => "message", ApplicationCommandType.User => "user", _ => throw new NotSupportedException($"Command type {commandType} is not supported as parameter name") diff --git a/Remora.Discord.Commands/Extensions/CommandTreeExtensions.cs b/Remora.Discord.Commands/Extensions/CommandTreeExtensions.cs index 9938ac491d..b2e47c9294 100644 --- a/Remora.Discord.Commands/Extensions/CommandTreeExtensions.cs +++ b/Remora.Discord.Commands/Extensions/CommandTreeExtensions.cs @@ -51,13 +51,13 @@ public static class CommandTreeExtensions * Various Discord-imposed limits. */ - private const int MaxRootCommandsOrGroups = 100; - private const int MaxGroupCommands = 25; - private const int MaxChoiceValues = 25; - private const int MaxCommandParameters = 25; - private const int MaxCommandStringifiedLength = 4000; - private const int MaxCommandDescriptionLength = 100; - private const int MaxTreeDepth = 3; // Top level is a depth of 1 + private const int _maxRootCommandsOrGroups = 100; + private const int _maxGroupCommands = 25; + private const int _maxChoiceValues = 25; + private const int _maxCommandParameters = 25; + private const int _maxCommandStringifiedLength = 8000; + private const int _maxCommandDescriptionLength = 100; + private const int _maxTreeDepth = 3; // Top level is a depth of 1 /// /// Maps a set of Discord application commands to their respective command nodes. @@ -188,18 +188,18 @@ public static IReadOnlyList CreateApplicationComman throw new UnsupportedFeatureException("Overloads are not supported.", node); } - if (GetCommandStringifiedLength(option) > MaxCommandStringifiedLength) + if (GetCommandStringifiedLength(option) > _maxCommandStringifiedLength) { throw new UnsupportedFeatureException ( "One or more commands is too long (combined length of name, description, and value " + - $"properties), max {MaxCommandStringifiedLength}).", + $"properties), max {_maxCommandStringifiedLength}).", node ); } // Translate from options to bulk data - var (commandType, directMessagePermission, defaultMemberPermissions, isNsfw) = GetNodeMetadata(node); + var (commandType, directMessagePermission, defaultMemberPermissions, isNsfw, allowedInstalls, allowedContexts) = GetNodeMetadata(node); var localizedNames = localizationProvider.GetStrings(option.Name); var localizedDescriptions = localizationProvider.GetStrings(option.Description); @@ -217,17 +217,19 @@ public static IReadOnlyList CreateApplicationComman localizedDescriptions.Count > 0 ? new(localizedDescriptions) : default, defaultMemberPermissions, directMessagePermission, - isNsfw + isNsfw, + allowedInstalls, + allowedContexts ) ); } // Perform validations - if (commands.Count > MaxRootCommandsOrGroups) + if (commands.Count > _maxRootCommandsOrGroups) { throw new UnsupportedFeatureException ( - $"Too many root-level commands or groups (max {MaxRootCommandsOrGroups}, found {commands.Count}).", + $"Too many root-level commands or groups (max {_maxRootCommandsOrGroups}, found {commands.Count}).", tree.Root ); } @@ -237,132 +239,220 @@ public static IReadOnlyList CreateApplicationComman private static TopLevelMetadata GetNodeMetadata(IChildNode node) { - Optional commandType = default; - Optional directMessagePermission = default; - IDiscordPermissionSet? defaultMemberPermissions = default; - Optional isNsfw = default; + return node switch + { + GroupNode groupNode => GetGroupNodeMetadata(node, groupNode), + CommandNode commandNode => GetCommandNodeMetadata(commandNode), + _ => new(default, default, default, default, default, default) + }; + } - switch (node) + private static TopLevelMetadata GetCommandNodeMetadata(CommandNode commandNode) + { + var commandType = commandNode.GetCommandType(); + + IDiscordPermissionSet? defaultMemberPermissions = null; + var memberPermissionsAttribute = + commandNode.GroupType.GetCustomAttribute() ?? + commandNode.CommandMethod.GetCustomAttribute(); + + if (memberPermissionsAttribute is not null) { - case GroupNode groupNode: - { - var memberPermissionAttributes = groupNode.GroupTypes.Select - ( - t => t.GetCustomAttribute() - ) - .Where(attribute => attribute is not null) - .ToArray(); + defaultMemberPermissions = new DiscordPermissionSet + ( + memberPermissionsAttribute.Permissions.ToArray() + ); + } - if (memberPermissionAttributes.Length > 1) - { - throw new InvalidNodeException - ( - "In a set of groups with the same name, only one may be marked with a default " + - $"member permissions attribute, but {memberPermissionAttributes.Length} were found.", - node - ); - } + var directMessagePermission = default(Optional); + var directMessagePermissionAttribute = + commandNode.GroupType.GetCustomAttribute() ?? + commandNode.CommandMethod.GetCustomAttribute(); - var defaultMemberPermissionsAttribute = memberPermissionAttributes.SingleOrDefault(); + if (directMessagePermissionAttribute is not null) + { + directMessagePermission = directMessagePermissionAttribute.IsExecutableInDMs; + } - if (defaultMemberPermissionsAttribute is not null) - { - defaultMemberPermissions = new DiscordPermissionSet - ( - defaultMemberPermissionsAttribute.Permissions.ToArray() - ); - } + var isNsfw = default(Optional); + var nsfwAttribute = + commandNode.GroupType.GetCustomAttribute() ?? + commandNode.CommandMethod.GetCustomAttribute(); - var directMessagePermissionAttributes = groupNode.GroupTypes.Select - ( - t => t.GetCustomAttribute() - ) - .Where(attribute => attribute is not null) - .ToArray(); + if (nsfwAttribute is not null) + { + isNsfw = nsfwAttribute.IsNsfw; + } - if (directMessagePermissionAttributes.Length > 1) - { - throw new InvalidNodeException - ( - "In a set of groups with the same name, only one may be marked with a default " + - $"DM permissions attribute, but {directMessagePermissionAttributes.Length} were found.", - node - ); - } + var allowedContextTypes = default(Optional>); + var contextsAttribute = + commandNode.GroupType.GetCustomAttribute() ?? + commandNode.CommandMethod.GetCustomAttribute(); - var directMessagePermissionAttribute = directMessagePermissionAttributes.SingleOrDefault(); + if (contextsAttribute is not null) + { + allowedContextTypes = contextsAttribute.Contexts.AsOptional(); + } - if (directMessagePermissionAttribute is not null) - { - directMessagePermission = directMessagePermissionAttribute.IsExecutableInDMs; - } + var allowedIntegrationTypes = default(Optional>); + var integrationAttribute = + commandNode.GroupType.GetCustomAttribute() ?? + commandNode.CommandMethod.GetCustomAttribute(); - var isNsfwAttributes = groupNode.GroupTypes.Select - ( - t => t.GetCustomAttribute() - ) - .Where(attribute => attribute is not null) - .ToArray(); + if (integrationAttribute is not null) + { + allowedIntegrationTypes = integrationAttribute.InstallTypes.AsOptional(); + } - if (isNsfwAttributes.Length > 1) - { - throw new InvalidNodeException - ( - $"In a set of groups with the same name, only one may be marked with a NSFW attribute, but " - + $"{isNsfwAttributes.Length} were found.", - node - ); - } + return new + ( + commandType, + directMessagePermission, + defaultMemberPermissions, + isNsfw, + allowedIntegrationTypes, + allowedContextTypes + ); + } - var nsfwAttribute = isNsfwAttributes.SingleOrDefault(); + private static TopLevelMetadata GetGroupNodeMetadata(IChildNode node, GroupNode groupNode) + { + var memberPermissionAttributes = groupNode.GroupTypes.Select + ( + t => t.GetCustomAttribute() + ) + .Where(attribute => attribute is not null) + .ToArray(); - if (nsfwAttribute is not null) - { - isNsfw = nsfwAttribute.IsNsfw; - } + if (memberPermissionAttributes.Length > 1) + { + throw new InvalidNodeException + ( + "In a set of groups with the same name, only one may be marked with a default " + + $"member permissions attribute, but {memberPermissionAttributes.Length} were found.", + node + ); + } - break; - } - case CommandNode commandNode: - { - commandType = commandNode.GetCommandType(); + IDiscordPermissionSet? defaultMemberPermissions = null; + var defaultMemberPermissionsAttribute = memberPermissionAttributes.SingleOrDefault(); + if (defaultMemberPermissionsAttribute is not null) + { + defaultMemberPermissions = new DiscordPermissionSet + ( + defaultMemberPermissionsAttribute.Permissions.ToArray() + ); + } - // Top-level command outside of a group - var memberPermissionsAttribute = - commandNode.GroupType.GetCustomAttribute() ?? - commandNode.CommandMethod.GetCustomAttribute(); + var directMessagePermission = default(Optional); + var directMessagePermissionAttributes = groupNode.GroupTypes.Select + ( + t => t.GetCustomAttribute() + ) + .Where(attribute => attribute is not null) + .ToArray(); - if (memberPermissionsAttribute is not null) - { - defaultMemberPermissions = new DiscordPermissionSet - ( - memberPermissionsAttribute.Permissions.ToArray() - ); - } + if (directMessagePermissionAttributes.Length > 1) + { + throw new InvalidNodeException + ( + "In a set of groups with the same name, only one may be marked with a default " + + $"DM permissions attribute, but {directMessagePermissionAttributes.Length} were found.", + node + ); + } - var directMessagePermissionAttribute = - commandNode.GroupType.GetCustomAttribute() ?? - commandNode.CommandMethod.GetCustomAttribute(); + var directMessagePermissionAttribute = directMessagePermissionAttributes.SingleOrDefault(); + if (directMessagePermissionAttribute is not null) + { + directMessagePermission = directMessagePermissionAttribute.IsExecutableInDMs; + } - if (directMessagePermissionAttribute is not null) - { - directMessagePermission = directMessagePermissionAttribute.IsExecutableInDMs; - } + var isNsfw = default(Optional); + var isNsfwAttributes = groupNode.GroupTypes.Select + ( + t => t.GetCustomAttribute() + ) + .Where(attribute => attribute is not null) + .ToArray(); - var nsfwAttribute = - commandNode.GroupType.GetCustomAttribute() ?? - commandNode.CommandMethod.GetCustomAttribute(); + if (isNsfwAttributes.Length > 1) + { + throw new InvalidNodeException + ( + $"In a set of groups with the same name, only one may be marked with a NSFW attribute, but " + + $"{isNsfwAttributes.Length} were found.", + node + ); + } - if (nsfwAttribute is not null) - { - isNsfw = nsfwAttribute.IsNsfw; - } + var nsfwAttribute = isNsfwAttributes.SingleOrDefault(); + if (nsfwAttribute is not null) + { + isNsfw = nsfwAttribute.IsNsfw; + } - break; - } + var allowedContextTypes = default(Optional>); + var contextsAttributes = groupNode.GroupTypes.Select + ( + t => t.GetCustomAttribute() + ); + + var contexts = contextsAttributes + .Where(attribute => attribute is not null) + .ToArray(); + + if (contexts.Length > 1) + { + throw new InvalidNodeException + ( + $"In a set of groups with the same name, only one may be marked with a context attribute, but " + + $"{contexts.Length} were found.", + node + ); } - return new(commandType, directMessagePermission, defaultMemberPermissions, isNsfw); + var context = contexts.SingleOrDefault(); + if (context is not null) + { + allowedContextTypes = context.Contexts.AsOptional(); + } + + var allowedIntegrationTypes = default(Optional>); + var installAttributes = groupNode.GroupTypes.Select + ( + t => t.GetCustomAttribute() + ); + + var installs = installAttributes + .Where(attribute => attribute is not null) + .ToArray(); + + if (installs.Length > 1) + { + throw new InvalidNodeException + ( + $"In a set of groups with the same name, only one may be marked with an install attribute, " + + $"but {installs.Length} were found.", + node + ); + } + + var install = installs.SingleOrDefault(); + if (install is not null) + { + allowedIntegrationTypes = install.InstallTypes.AsOptional(); + } + + return new + ( + default, + directMessagePermission, + defaultMemberPermissions, + isNsfw, + allowedIntegrationTypes, + allowedContextTypes + ); } private static IApplicationCommandOption? TranslateCommandNode @@ -372,11 +462,11 @@ private static IApplicationCommandOption? TranslateCommandNode ILocalizationProvider localizationProvider ) { - if (treeDepth > MaxTreeDepth) + if (treeDepth > _maxTreeDepth) { throw new UnsupportedFeatureException ( - $"A sub-command or group was nested too deeply (depth {treeDepth}, max {MaxTreeDepth}).", + $"A sub-command or group was nested too deeply (depth {treeDepth}, max {_maxTreeDepth}).", node ); } @@ -468,11 +558,11 @@ ILocalizationProvider localizationProvider return null; } - if (subCommandCount > MaxGroupCommands) + if (subCommandCount > _maxGroupCommands) { throw new UnsupportedFeatureException ( - $"Too many commands under a group ({subCommandCount}, max {MaxGroupCommands}).", + $"Too many commands under a group ({subCommandCount}, max {_maxGroupCommands}).", group ); } @@ -670,11 +760,11 @@ ILocalizationProvider localizationProvider parameterOptions.Add(parameterOption); } - if (parameterOptions.Count > MaxCommandParameters) + if (parameterOptions.Count > _maxCommandParameters) { throw new UnsupportedFeatureException ( - $"Too many parameters in a command (had {parameterOptions.Count}, max {MaxCommandParameters}).", + $"Too many parameters in a command (had {parameterOptions.Count}, max {_maxCommandParameters}).", command ); } @@ -696,7 +786,7 @@ ILocalizationProvider localizationProvider if (actualParameterType.IsEnum) { // Add the choices directly - if (Enum.GetValues(actualParameterType).Length <= MaxChoiceValues) + if (Enum.GetValues(actualParameterType).Length <= _maxChoiceValues) { choices = new(EnumExtensions.GetEnumChoices(actualParameterType, localizationProvider)); } @@ -810,7 +900,7 @@ private static void ValidateNodeDescription(string description, IChildNode node) var type = command.GetCommandType(); if (type is ApplicationCommandType.ChatInput) { - if (description.Length <= MaxCommandDescriptionLength) + if (description.Length <= _maxCommandDescriptionLength) { return; } @@ -818,7 +908,7 @@ private static void ValidateNodeDescription(string description, IChildNode node) throw new UnsupportedFeatureException ( $"A command description was too long (length {description.Length}, " - + $"max {MaxCommandDescriptionLength}).", + + $"max {_maxCommandDescriptionLength}).", node ); } @@ -837,7 +927,7 @@ private static void ValidateNodeDescription(string description, IChildNode node) default: { // Assume it uses the default limits - if (description.Length <= MaxCommandDescriptionLength) + if (description.Length <= _maxCommandDescriptionLength) { return; } @@ -845,7 +935,7 @@ private static void ValidateNodeDescription(string description, IChildNode node) throw new UnsupportedFeatureException ( $"A group or parameter description was too long (length {description.Length}, " - + $"max {MaxCommandDescriptionLength}).", + + $"max {_maxCommandDescriptionLength}).", node ); } @@ -1043,11 +1133,15 @@ Optional MaxLength /// The DM permission requested for the node. /// The default member permission requested for the node. /// The age restriction requested for the node. + /// The integration types allowed for the node. + /// The context types allowed for the node. private sealed record TopLevelMetadata ( Optional CommandType, Optional DirectMessagePermission, IDiscordPermissionSet? DefaultMemberPermission, - Optional IsNsfw + Optional IsNsfw, + Optional> AllowedIntegrationTypes, + Optional> AllowedContextTypes ); } diff --git a/Remora.Discord.Commands/Extensions/EnumExtensions.cs b/Remora.Discord.Commands/Extensions/EnumExtensions.cs index 1f0cddeb02..71296214ff 100644 --- a/Remora.Discord.Commands/Extensions/EnumExtensions.cs +++ b/Remora.Discord.Commands/Extensions/EnumExtensions.cs @@ -43,8 +43,8 @@ namespace Remora.Discord.Commands.Extensions; /// internal static class EnumExtensions { - private const int MaxChoiceNameLength = 100; - private const int MaxChoiceValueLength = 100; + private const int _maxChoiceNameLength = 100; + private const int _maxChoiceValueLength = 100; private static readonly ConcurrentDictionary> _choiceCache = new(); @@ -75,7 +75,7 @@ ILocalizationProvider localizationProvider /// /// /// Thrown if the enum has more than 25 members, or if any of its members translated names or values exceeds one of - /// or . + /// or . /// /// The enumeration type. /// The localization provider. @@ -108,19 +108,19 @@ ILocalizationProvider localizationProvider var localizedDisplayNames = provider.GetStrings(displayString); foreach (var (locale, localizedDisplayName) in localizedDisplayNames) { - if (localizedDisplayName.Length > MaxChoiceNameLength) + if (localizedDisplayName.Length > _maxChoiceNameLength) { throw new ArgumentOutOfRangeException ( nameof(enumType), $"The localized display name for the locale {locale} of the enumeration member " - + $"{type.Name}::{enumName} is too long (max {MaxChoiceNameLength})." + + $"{type.Name}::{enumName} is too long (max {_maxChoiceNameLength})." ); } } var valueString = enumName; - if (valueString.Length <= MaxChoiceValueLength) + if (valueString.Length <= _maxChoiceValueLength) { choices.Add ( @@ -137,13 +137,13 @@ ILocalizationProvider localizationProvider // Try converting the enum's value representation valueString = value.ToString() ?? throw new InvalidOperationException(); - if (valueString.Length > MaxChoiceValueLength) + if (valueString.Length > _maxChoiceValueLength) { throw new ArgumentOutOfRangeException ( nameof(enumType), $"The length of the enumeration member {type.Name}::{enumName} value is too long " + - $"(max {MaxChoiceValueLength})." + $"(max {_maxChoiceValueLength})." ); } diff --git a/Remora.Discord.Commands/Extensions/ServiceCollectionExtensions.cs b/Remora.Discord.Commands/Extensions/ServiceCollectionExtensions.cs index 5918d60999..b8f2ba06a6 100644 --- a/Remora.Discord.Commands/Extensions/ServiceCollectionExtensions.cs +++ b/Remora.Discord.Commands/Extensions/ServiceCollectionExtensions.cs @@ -237,6 +237,7 @@ public static IServiceCollection AddDiscordCommands .AddParser() .AddParser() .AddParser() + .AddParser() .AddParser() .AddParser(); diff --git a/Remora.Discord.Commands/Parsers/EmojiParser.cs b/Remora.Discord.Commands/Parsers/EmojiParser.cs index 918191dc74..de569a1bca 100644 --- a/Remora.Discord.Commands/Parsers/EmojiParser.cs +++ b/Remora.Discord.Commands/Parsers/EmojiParser.cs @@ -151,7 +151,7 @@ private static bool TryParseEmoji(string input, [NotNullWhen(true)] out IEmoji? return false; } - emoji = new Emoji(emojiID, inputParts[^2], IsAnimated: inputParts is["a", _, _]); + emoji = new Emoji(emojiID, inputParts[^2], IsAnimated: inputParts is ["a", _, _]); return true; } diff --git a/Remora.Discord.Commands/Parsers/MessageParser.cs b/Remora.Discord.Commands/Parsers/MessageParser.cs index 386a85e35d..168a9d3a6e 100644 --- a/Remora.Discord.Commands/Parsers/MessageParser.cs +++ b/Remora.Discord.Commands/Parsers/MessageParser.cs @@ -20,7 +20,6 @@ // along with this program. If not, see . // -using System.Collections.Generic; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -32,16 +31,15 @@ using Remora.Discord.API.Abstractions.Rest; using Remora.Discord.Commands.Contexts; using Remora.Discord.Commands.Extensions; -using Remora.Rest.Core; using Remora.Results; namespace Remora.Discord.Commands.Parsers; /// -/// Parses instances of and from command-line inputs. +/// Parses instances of from command-line inputs. /// [PublicAPI] -public class MessageParser : AbstractTypeParser, ITypeParser +public class MessageParser : AbstractTypeParser { private static readonly Regex _messageLinkRegex = new ( @@ -100,65 +98,4 @@ public override async ValueTask> TryParseAsync return await _channelAPI.GetChannelMessageAsync(channelId.Value, messageId.Value, ct); } - - /// - async ValueTask> ITypeParser.TryParseAsync - ( - IReadOnlyList tokens, - CancellationToken ct - ) - { - return (await (this as ITypeParser).TryParseAsync(tokens, ct)).Map(a => a as IPartialMessage); - } - - /// - async ValueTask> ITypeParser.TryParseAsync - ( - string token, - CancellationToken ct - ) - { - _ = DiscordSnowflake.TryParse(token.Unmention(), out var messageID); - if (messageID is null) - { - var messageLinkMatch = _messageLinkRegex.Match(token); - if (!messageLinkMatch.Success) - { - return new ParsingError(token, "Unrecognized input format."); - } - - var messageIdRaw = messageLinkMatch.Groups["message_id"].Value; - if (!DiscordSnowflake.TryParse(messageIdRaw, out messageID)) - { - return new ParsingError(token, "Unrecognized input format."); - } - } - - var resolvedMessage = GetResolvedMessageOrDefault(messageID.Value); - return resolvedMessage is null - ? (await (this as ITypeParser).TryParseAsync(token, ct)).Map(a => a as IPartialMessage) - : Result.FromSuccess(resolvedMessage); - } - - private IPartialMessage? GetResolvedMessageOrDefault(Snowflake messageID) - { - if (_context is not IInteractionContext interactionContext) - { - return null; - } - - if (!interactionContext.Interaction.Data.TryGet(out var data)) - { - return null; - } - - var resolvedData = data.Match(a => a.Resolved, _ => default, _ => default); - if (!resolvedData.TryGet(out var resolved) || !resolved.Messages.TryGet(out var messages)) - { - return null; - } - - _ = messages.TryGetValue(messageID, out var message); - return message; - } } diff --git a/Remora.Discord.Commands/Parsers/PartialMessageParser.cs b/Remora.Discord.Commands/Parsers/PartialMessageParser.cs new file mode 100644 index 0000000000..a9d44892af --- /dev/null +++ b/Remora.Discord.Commands/Parsers/PartialMessageParser.cs @@ -0,0 +1,92 @@ +// +// PartialMessageParser.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Remora.Commands.Parsers; +using Remora.Discord.API; +using Remora.Discord.API.Abstractions.Objects; +using Remora.Discord.Commands.Contexts; +using Remora.Discord.Commands.Extensions; +using Remora.Results; + +namespace Remora.Discord.Commands.Parsers; + +/// +/// Parses instances of from command-line inputs. +/// +[PublicAPI] +public class PartialMessageParser : AbstractTypeParser +{ + private readonly IInteractionContext? _context; + private readonly ITypeParser _messageParser; + + /// + /// Initializes a new instance of the class. + /// + /// The interaction context, if available. + /// The message parser to delegate to, if necessary. + public PartialMessageParser(IInteractionContext? context, ITypeParser messageParser) + { + _context = context; + _messageParser = messageParser; + } + + /// + public override async ValueTask> TryParseAsync + ( + string value, + CancellationToken ct = default + ) + { + var resolvedValue = ResolveMessageFromContext(value); + + return resolvedValue is not null ? Result.FromSuccess(resolvedValue) : (await _messageParser.TryParseAsync(value, ct)).Map(m => m); + } + + private IPartialMessage? ResolveMessageFromContext(string value) + { + if (_context is null) + { + return null; + } + + if (!DiscordSnowflake.TryParse(value.Unmention(), out var messageID)) + { + return null; + } + + if (!_context.Interaction.Data.TryGet(out var interactionData) || interactionData.TryPickT0(out var strongData, out _)) + { + return null; + } + + if (!strongData.Resolved.TryGet(out var resolved) || !resolved.Messages.TryGet(out var messages)) + { + return null; + } + + _ = messages.TryGetValue(messageID.Value, out var message); + return message; + } +} diff --git a/Remora.Discord.Commands/Remora.Discord.Commands.csproj b/Remora.Discord.Commands/Remora.Discord.Commands.csproj index 11cfc64c21..5d396a4712 100644 --- a/Remora.Discord.Commands/Remora.Discord.Commands.csproj +++ b/Remora.Discord.Commands/Remora.Discord.Commands.csproj @@ -1,12 +1,10 @@ - 28.0.0 + 28.0.2 Glue code for using Remora.Commands with Remora.Discord Update dependencies. - BREAKING: Change Snowflakes in TryGet methods to non-nullable. - Ensure results aren't swallowed when no events are registered. diff --git a/Remora.Discord.Commands/Responders/InteractionResponder.cs b/Remora.Discord.Commands/Responders/InteractionResponder.cs index caa8fb7ac0..1949631d92 100644 --- a/Remora.Discord.Commands/Responders/InteractionResponder.cs +++ b/Remora.Discord.Commands/Responders/InteractionResponder.cs @@ -202,8 +202,8 @@ private async Task TryExecuteCommandAsync var shouldSendResponse = !( - suppressResponseAttribute?.Suppress ?? _options.SuppressAutomaticResponses || - commandContext.HasRespondedToInteraction + suppressResponseAttribute?.Suppress ?? + (_options.SuppressAutomaticResponses || commandContext.HasRespondedToInteraction) ); if (shouldSendResponse) diff --git a/Remora.Discord.Extensions/Formatting/AnsiStringBuilder.cs b/Remora.Discord.Extensions/Formatting/AnsiStringBuilder.cs index 698fcff70c..92567f4d64 100644 --- a/Remora.Discord.Extensions/Formatting/AnsiStringBuilder.cs +++ b/Remora.Discord.Extensions/Formatting/AnsiStringBuilder.cs @@ -141,7 +141,7 @@ public string Build() /// private sealed class StyleState { - private const char EscapeChar = '\u001b'; + private const char _escapeChar = '\u001b'; private bool _hasChanged; private bool _isBold; @@ -246,7 +246,7 @@ public void AppendToStringBuilder(StringBuilder stringBuilder) _hasChanged = false; - stringBuilder.Append($"{EscapeChar}[{AnsiStyle.Reset}"); + stringBuilder.Append($"{_escapeChar}[{AnsiStyle.Reset}"); if (this.IsBold) { diff --git a/Remora.Discord.Extensions/Formatting/GuildNavigationType.cs b/Remora.Discord.Extensions/Formatting/GuildNavigationType.cs new file mode 100644 index 0000000000..304e081a41 --- /dev/null +++ b/Remora.Discord.Extensions/Formatting/GuildNavigationType.cs @@ -0,0 +1,47 @@ +// +// GuildNavigationType.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using JetBrains.Annotations; + +namespace Remora.Discord.Extensions.Formatting; + +/// +/// Enumerates various navigation types in a guild. +/// +[PublicAPI] +public enum GuildNavigationType +{ + /// + /// The "Customize" tab with the server's onboarding prompts. + /// + Customize, + + /// + /// The "Browse Channels" tab. + /// + Browse, + + /// + /// The server guide. + /// + Guide +} diff --git a/Remora.Discord.Extensions/Formatting/Navigation.cs b/Remora.Discord.Extensions/Formatting/Navigation.cs new file mode 100644 index 0000000000..30f42b0757 --- /dev/null +++ b/Remora.Discord.Extensions/Formatting/Navigation.cs @@ -0,0 +1,39 @@ +// +// Navigation.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using JetBrains.Annotations; + +namespace Remora.Discord.Extensions.Formatting; + +/// +/// Provides quick access to various navigation links. +/// +[PublicAPI] +public static class Navigation +{ + /// + /// Creates a guild navigation link. + /// + /// The navigation target. + /// The formatted link. + public static string Guild(GuildNavigationType target) => $""; +} diff --git a/Remora.Discord.Extensions/Remora.Discord.Extensions.csproj b/Remora.Discord.Extensions/Remora.Discord.Extensions.csproj index 74845c5b8c..a2efb4269f 100644 --- a/Remora.Discord.Extensions/Remora.Discord.Extensions.csproj +++ b/Remora.Discord.Extensions/Remora.Discord.Extensions.csproj @@ -1,7 +1,7 @@  - 5.3.2 + 5.3.4 Utilities and components which extend upon Remora.Discord's base resources Update dependencies. diff --git a/Remora.Discord.Hosting/Remora.Discord.Hosting.csproj b/Remora.Discord.Hosting/Remora.Discord.Hosting.csproj index 9205a539d3..0ab829d25b 100644 --- a/Remora.Discord.Hosting/Remora.Discord.Hosting.csproj +++ b/Remora.Discord.Hosting/Remora.Discord.Hosting.csproj @@ -1,7 +1,7 @@ - 6.0.7 + 6.0.9 Implementation of a hosted Discord gateway service for the .NET Generic Host Update dependencies. diff --git a/Remora.Discord.Interactivity/Bases/InteractionGroup.cs b/Remora.Discord.Interactivity/Bases/InteractionGroup.cs index ed9cc544cb..f44cffb130 100644 --- a/Remora.Discord.Interactivity/Bases/InteractionGroup.cs +++ b/Remora.Discord.Interactivity/Bases/InteractionGroup.cs @@ -29,6 +29,4 @@ namespace Remora.Discord.Interactivity; /// Represents the base type for command groups that handle Discord's message component interactions. /// [PublicAPI] -public abstract class InteractionGroup : CommandGroup -{ -} +public abstract class InteractionGroup : CommandGroup; diff --git a/Remora.Discord.Interactivity/CustomIDHelpers.cs b/Remora.Discord.Interactivity/CustomIDHelpers.cs index be5a2df6c4..95dd623965 100644 --- a/Remora.Discord.Interactivity/CustomIDHelpers.cs +++ b/Remora.Discord.Interactivity/CustomIDHelpers.cs @@ -38,7 +38,7 @@ public static class CustomIDHelpers /// Gets the name used to build IDs for select menu components. /// This may include text, role, channel etc. select menus. /// - private const string SelectComponentName = "select-menu"; + private const string _selectComponentName = "select-menu"; /// /// Creates an ID string that can be used with button components. @@ -98,7 +98,7 @@ public static string CreateButtonIDWithState(string name, string state, params s /// /// The custom ID. public static string CreateSelectMenuID(string name) - => FormatID(SelectComponentName, name, null, Array.Empty()); + => FormatID(_selectComponentName, name, null, Array.Empty()); /// /// Creates an ID string with state information that can be used with select menu components. @@ -109,7 +109,7 @@ public static string CreateSelectMenuID(string name) /// The state value passed with the custom ID. /// The custom ID. public static string CreateSelectMenuIDWithState(string name, string state) - => FormatID(SelectComponentName, name, state, Array.Empty()); + => FormatID(_selectComponentName, name, state, Array.Empty()); /// /// Creates an ID string that can be used with select menu components. @@ -123,7 +123,7 @@ public static string CreateSelectMenuIDWithState(string name, string state) /// /// The custom ID. public static string CreateSelectMenuID(string name, params string[] path) - => FormatID(SelectComponentName, name, null, path); + => FormatID(_selectComponentName, name, null, path); /// /// Creates an ID string with state information that can be used with select menu components. @@ -138,7 +138,7 @@ public static string CreateSelectMenuID(string name, params string[] path) /// /// The custom ID. public static string CreateSelectMenuIDWithState(string name, string state, params string[] path) - => FormatID(SelectComponentName, name, state, path); + => FormatID(_selectComponentName, name, state, path); /// /// Creates an ID string that can be used with modals. diff --git a/Remora.Discord.Interactivity/Remora.Discord.Interactivity.csproj b/Remora.Discord.Interactivity/Remora.Discord.Interactivity.csproj index 5b11004ed9..1f9506669f 100644 --- a/Remora.Discord.Interactivity/Remora.Discord.Interactivity.csproj +++ b/Remora.Discord.Interactivity/Remora.Discord.Interactivity.csproj @@ -1,7 +1,7 @@ - 4.5.1 + 4.5.3 Framework for using Discord's interaction-driven message components Update dependencies. diff --git a/Remora.Discord.Interactivity/Responders/InteractivityResponder.cs b/Remora.Discord.Interactivity/Responders/InteractivityResponder.cs index 6bc56d2f29..ae588ceb71 100644 --- a/Remora.Discord.Interactivity/Responders/InteractivityResponder.cs +++ b/Remora.Discord.Interactivity/Responders/InteractivityResponder.cs @@ -402,8 +402,8 @@ private async Task TryExecuteCommandAsync var shouldSendResponse = !( - suppressResponseAttribute?.Suppress ?? _options.SuppressAutomaticResponses || - commandContext.HasRespondedToInteraction + suppressResponseAttribute?.Suppress ?? + (_options.SuppressAutomaticResponses || commandContext.HasRespondedToInteraction) ); if (shouldSendResponse) diff --git a/Remora.Discord.Pagination/Interactions/PaginationInteractions.cs b/Remora.Discord.Pagination/Interactions/PaginationInteractions.cs index a32b0582f5..40f27e5e02 100644 --- a/Remora.Discord.Pagination/Interactions/PaginationInteractions.cs +++ b/Remora.Discord.Pagination/Interactions/PaginationInteractions.cs @@ -28,6 +28,7 @@ using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Rest; using Remora.Discord.Commands.Contexts; +using Remora.Discord.Commands.Extensions; using Remora.Discord.Commands.Feedback.Messages; using Remora.Discord.Commands.Feedback.Services; using Remora.Discord.Interactivity; @@ -112,6 +113,11 @@ public async Task CloseAsync() return new InvalidOperationError("No message available for the interaction."); } + if (!_context.TryGetUserID(out var userID)) + { + return new InvalidOperationError("No user ID available for the interaction."); + } + var leaseData = await _paginationData.LeaseDataAsync(message.ID, this.CancellationToken); if (!leaseData.IsSuccess) { @@ -119,6 +125,12 @@ public async Task CloseAsync() } await using var lease = leaseData.Entity; + + if (lease.Data.SourceUserID != userID) + { + return Result.FromSuccess(); + } + lease.Delete(); if (lease.Data.IsInteractionDriven) @@ -146,6 +158,11 @@ public async Task HelpAsync() return new InvalidOperationError("No message available for the interaction."); } + if (!_context.TryGetUserID(out var userID)) + { + return new InvalidOperationError("No user ID available for the interaction."); + } + var leaseData = await _paginationData.LeaseDataAsync(message.ID, this.CancellationToken); if (!leaseData.IsSuccess) { @@ -154,12 +171,15 @@ public async Task HelpAsync() await using var lease = leaseData.Entity; - return (Result)await _feedback.SendContextualInfoAsync + if (lease.Data.SourceUserID != userID) + { + return Result.FromSuccess(); + } + return (Result)await _feedback.SendContextualEmbedAsync ( - lease.Data.Appearance.HelpText, - lease.Data.SourceUserID, + lease.Data.Appearance.HelpEmbed, new FeedbackMessageOptions(MessageFlags: MessageFlags.Ephemeral), - this.CancellationToken + ct: this.CancellationToken ); } @@ -170,6 +190,11 @@ private async Task UpdateAsync(Action action, Canc return new InvalidOperationError("No message available for the interaction."); } + if (!_context.TryGetUserID(out var userID)) + { + return new InvalidOperationError("No user ID available for the interaction."); + } + var leaseData = await _paginationData.LeaseDataAsync(message.ID, this.CancellationToken); if (!leaseData.IsSuccess) { @@ -178,6 +203,11 @@ private async Task UpdateAsync(Action action, Canc await using var lease = leaseData.Entity; + if (lease.Data.SourceUserID != userID) + { + return Result.FromSuccess(); + } + action(lease.Data); var newPage = lease.Data.GetCurrentPage(); diff --git a/Remora.Discord.Pagination/PaginatedAppearanceOptions.cs b/Remora.Discord.Pagination/PaginatedAppearanceOptions.cs index 528d157045..69141e218c 100644 --- a/Remora.Discord.Pagination/PaginatedAppearanceOptions.cs +++ b/Remora.Discord.Pagination/PaginatedAppearanceOptions.cs @@ -39,8 +39,8 @@ public sealed record PaginatedAppearanceOptions ButtonComponent Last, ButtonComponent Close, ButtonComponent Help, - string FooterFormat = "Page {0}/{1}", - string HelpText = "This is a paginated message. Use the buttons to change page." + Embed HelpEmbed, + string FooterFormat = "Page {0}/{1}" ) { /// @@ -83,6 +83,11 @@ public sealed record PaginatedAppearanceOptions ButtonComponentStyle.Secondary, Emoji: new PartialEmoji(Name: "ℹ"), Label: nameof(Help) + ), + new Embed + ( + Title: "Pagination Help", + Description: "Use the buttons below to navigate the pages." ) ); diff --git a/Remora.Discord.Pagination/Remora.Discord.Pagination.csproj b/Remora.Discord.Pagination/Remora.Discord.Pagination.csproj index 0daa7e1be3..10aa60321b 100644 --- a/Remora.Discord.Pagination/Remora.Discord.Pagination.csproj +++ b/Remora.Discord.Pagination/Remora.Discord.Pagination.csproj @@ -1,7 +1,7 @@ - 3.0.18 + 3.0.20 Button-based pagination of messages for Remora.Discord Update dependencies. diff --git a/Remora.Discord.sln.DotSettings b/Remora.Discord.sln.DotSettings index c36d519b28..c1b6756ce6 100644 --- a/Remora.Discord.sln.DotSettings +++ b/Remora.Discord.sln.DotSettings @@ -9,7 +9,9 @@ WARNING ERROR WARNING + WARNING WARNING + HINT WARNING WARNING DO_NOT_SHOW @@ -31,14 +33,42 @@ True True WARNING + False + True Required Required Required Required + False + Remove Property, Event + 0 + 0 USUAL_INDENT USUAL_INDENT + False + True True + EXPANDED + False + ALWAYS + ALWAYS + NEVER + True + True + True + CHOP_IF_LONG + CHOP_IF_LONG + True + True + True + True + True + CHOP_IF_LONG + CHOP_IF_LONG + CHOP_IF_LONG + CHOP_IF_LONG + CHOP_IF_LONG False False True @@ -69,6 +99,7 @@ CDN DM DND + DTO GDKGL GIF GIFV @@ -86,9 +117,12 @@ NEST NSFW OS + PICS PNG + REM RPC RTC + SK SKU SKUID SSRC @@ -103,15 +137,28 @@ YZ False <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Private" Description="Constant fields (private)"><ElementKinds><Kind Name="CONSTANT_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Instance fields (not private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Constant fields (not private)"><ElementKinds><Kind Name="CONSTANT_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static fields (not private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static readonly fields (not private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy> True True True True + True True True + True True True True @@ -119,6 +166,7 @@ True True True + True True True True @@ -161,10 +209,12 @@ True True True + True True True True True + True True True True @@ -178,10 +228,14 @@ True True True + True True + True True + True True True + True True True True @@ -195,7 +249,9 @@ True True True + True True + True True True True @@ -208,5 +264,6 @@ True True True + True True True \ No newline at end of file diff --git a/Remora.Discord/Remora.Discord.csproj b/Remora.Discord/Remora.Discord.csproj index 9cb371b080..99da9de798 100644 --- a/Remora.Discord/Remora.Discord.csproj +++ b/Remora.Discord/Remora.Discord.csproj @@ -1,29 +1,17 @@ - 2023.4 + 2024.1 Metapackage for Remora.Discord's various components Update dependencies. - Add external sound permission. - Add several new error codes. - BREAKING: Add defaultForumLayout parameter. - BREAKING: Add more audit log events. - BREAKING: Add RateLimitPerUser to Voice and Stage channels. - BREAKING: Change Snowflakes in TryGet methods to non-nullable. - BREAKING: Implement approximate guild count and partial guild. - BREAKING: Implement attachment flags. - BREAKING: Implement avatar decorations. - BREAKING: Implement MessageAuthorID. - BREAKING: Implement new username field. - BREAKING: Implement onboarding mode. - BREAKING: Implement role flags. - BREAKING: Implement support for join and mention raid protection. - BREAKING: Implement with_counts for user guild endpoint. - Correct cancellation token usage. - Ensure results aren't swallowed when no events are registered. - Extract dispatch API to an interface. - Fix conversion discrepancies between partial and nonpartial type. + Use async overloads when building for .NET 8. + fix: do not use IsDefined when updating cached nullable properties + fix: fill CommunicationDisabledUntil property from new object when updating cached GuildMember + BREAKING: Revert "Remove global_name from User objects" + BREAKING: fix: wrap GlobalName in Optional + Update permissions. + BREAKING: Implement applied tags for webhooks. diff --git a/Samples/LoadRespondersFromAssembly/LoadRespondersFromAssembly.csproj b/Samples/LoadRespondersFromAssembly/LoadRespondersFromAssembly.csproj index f5b7d39a98..c742d19737 100644 --- a/Samples/LoadRespondersFromAssembly/LoadRespondersFromAssembly.csproj +++ b/Samples/LoadRespondersFromAssembly/LoadRespondersFromAssembly.csproj @@ -10,6 +10,7 @@ + diff --git a/Samples/LoadRespondersFromAssembly/Program.cs b/Samples/LoadRespondersFromAssembly/Program.cs index 27f9641c49..c94a52ad3a 100644 --- a/Samples/LoadRespondersFromAssembly/Program.cs +++ b/Samples/LoadRespondersFromAssembly/Program.cs @@ -5,13 +5,12 @@ // using System; -using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Remora.Discord.Gateway.Extensions; +using Remora.Discord.Extensions.Extensions; using Remora.Discord.Hosting.Extensions; namespace Remora.Discord.Samples.LoadRespondersFromAssembly; @@ -52,14 +51,7 @@ private static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefau ( (_, services) => { - var responderTypes = typeof(Program).Assembly - .GetExportedTypes() - .Where(t => t.IsResponder()); - - foreach (var responderType in responderTypes) - { - services.AddResponder(responderType); - } + services.AddRespondersFromAssembly(typeof(Program).Assembly); } ) .ConfigureLogging diff --git a/Samples/Localization/Commands/HttpCatCommands.cs b/Samples/Localization/Commands/HttpCatCommands.cs index 6bc2bda11e..1e2f7d0fe8 100644 --- a/Samples/Localization/Commands/HttpCatCommands.cs +++ b/Samples/Localization/Commands/HttpCatCommands.cs @@ -111,7 +111,7 @@ public Task PostUserHttpCatAsync([Description("The user to cattify")] I [Description("Posts a cat image that matches the provided channel.")] public Task PostChannelHttpCatAsync ( - [Description("The channel to cattify")][ChannelTypes(ChannelType.GuildText)] IChannel channel + [Description("The channel to cattify")] [ChannelTypes(ChannelType.GuildText)] IChannel channel ) { var values = Enum.GetValues(); diff --git a/Samples/SlashCommands/Commands/HttpCatCommands.cs b/Samples/SlashCommands/Commands/HttpCatCommands.cs index 7b3f15fe37..bccf5109b3 100644 --- a/Samples/SlashCommands/Commands/HttpCatCommands.cs +++ b/Samples/SlashCommands/Commands/HttpCatCommands.cs @@ -111,7 +111,7 @@ public Task PostUserHttpCatAsync([Description("The user to cattify")] I [Description("Posts a cat image that matches the provided channel.")] public Task PostChannelHttpCatAsync ( - [Description("The channel to cattify")][ChannelTypes(ChannelType.GuildText)] IChannel channel + [Description("The channel to cattify")] [ChannelTypes(ChannelType.GuildText)] IChannel channel ) { var values = Enum.GetValues(); diff --git a/Samples/UnknownEventLogger/Responders/UnknownEventResponder.cs b/Samples/UnknownEventLogger/Responders/UnknownEventResponder.cs index d65da5e527..7cb454c3b2 100644 --- a/Samples/UnknownEventLogger/Responders/UnknownEventResponder.cs +++ b/Samples/UnknownEventLogger/Responders/UnknownEventResponder.cs @@ -78,10 +78,14 @@ public async Task RespondAsync var filePath = Path.Combine(eventDirectory, filename); await using var file = File.OpenWrite(filePath); - await using var jsonWriter = new Utf8JsonWriter(file, new JsonWriterOptions - { - Indented = true - }); + await using var jsonWriter = new Utf8JsonWriter + ( + file, + new JsonWriterOptions + { + Indented = true + } + ); jsonDocument.WriteTo(jsonWriter); diff --git a/Tests/Remora.Discord.API.Tests/API/Gateway/Events/Monetization/EntitlementCreateTests.cs b/Tests/Remora.Discord.API.Tests/API/Gateway/Events/Monetization/EntitlementCreateTests.cs new file mode 100644 index 0000000000..73fa5bfdf7 --- /dev/null +++ b/Tests/Remora.Discord.API.Tests/API/Gateway/Events/Monetization/EntitlementCreateTests.cs @@ -0,0 +1,39 @@ +// +// EntitlementCreateTests.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Remora.Discord.API.Abstractions.Gateway.Events; +using Remora.Discord.API.Tests.TestBases; + +namespace Remora.Discord.API.Tests.Gateway.Events; + +/// +public class EntitlementCreateTests : GatewayEventTestBase +{ + /// + /// Initializes a new instance of the class. + /// + /// The test fixture. + public EntitlementCreateTests(JsonBackedTypeTestFixture fixture) + : base(fixture) + { + } +} diff --git a/Tests/Remora.Discord.API.Tests/API/Gateway/Events/Monetization/EntitlementDeleteTests.cs b/Tests/Remora.Discord.API.Tests/API/Gateway/Events/Monetization/EntitlementDeleteTests.cs new file mode 100644 index 0000000000..ac1a8ce44a --- /dev/null +++ b/Tests/Remora.Discord.API.Tests/API/Gateway/Events/Monetization/EntitlementDeleteTests.cs @@ -0,0 +1,39 @@ +// +// EntitlementDeleteTests.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Remora.Discord.API.Abstractions.Gateway.Events; +using Remora.Discord.API.Tests.TestBases; + +namespace Remora.Discord.API.Tests.Gateway.Events; + +/// +public class EntitlementDeleteTests : GatewayEventTestBase +{ + /// + /// Initializes a new instance of the class. + /// + /// The test fixture. + public EntitlementDeleteTests(JsonBackedTypeTestFixture fixture) + : base(fixture) + { + } +} diff --git a/Tests/Remora.Discord.API.Tests/API/Gateway/Events/Monetization/EntitlementUpdateTests.cs b/Tests/Remora.Discord.API.Tests/API/Gateway/Events/Monetization/EntitlementUpdateTests.cs new file mode 100644 index 0000000000..5f472c05d5 --- /dev/null +++ b/Tests/Remora.Discord.API.Tests/API/Gateway/Events/Monetization/EntitlementUpdateTests.cs @@ -0,0 +1,39 @@ +// +// EntitlementUpdateTests.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Remora.Discord.API.Abstractions.Gateway.Events; +using Remora.Discord.API.Tests.TestBases; + +namespace Remora.Discord.API.Tests.Gateway.Events; + +/// +public class EntitlementUpdateTests : GatewayEventTestBase +{ + /// + /// Initializes a new instance of the class. + /// + /// The test fixture. + public EntitlementUpdateTests(JsonBackedTypeTestFixture fixture) + : base(fixture) + { + } +} diff --git a/Tests/Remora.Discord.API.Tests/API/Gateway/Events/Presences/PresenceUpdateTests.cs b/Tests/Remora.Discord.API.Tests/API/Gateway/Events/Presences/PresenceUpdateTests.cs index 905d4b86a7..fb78a9db3c 100644 --- a/Tests/Remora.Discord.API.Tests/API/Gateway/Events/Presences/PresenceUpdateTests.cs +++ b/Tests/Remora.Discord.API.Tests/API/Gateway/Events/Presences/PresenceUpdateTests.cs @@ -43,8 +43,8 @@ public PresenceUpdateTests(JsonBackedTypeTestFixture fixture) { AllowMissing = new[] { - "id", // undocumented field in "activities[]" objects - "sync_id", // undocumented field in "activities[]" objects + "id", // undocumented field in "activities[]" objects + "sync_id", // undocumented field in "activities[]" objects "session_id" } }; diff --git a/Tests/Remora.Discord.API.Tests/API/Objects/Activities/ActivityTests.cs b/Tests/Remora.Discord.API.Tests/API/Objects/Activities/ActivityTests.cs index ac8a0220f2..f9829b6816 100644 --- a/Tests/Remora.Discord.API.Tests/API/Objects/Activities/ActivityTests.cs +++ b/Tests/Remora.Discord.API.Tests/API/Objects/Activities/ActivityTests.cs @@ -46,7 +46,7 @@ public ActivityTests(JsonBackedTypeTestFixture fixture) { AllowMissing = new[] { - "id", // undocumented field + "id", // undocumented field "session_id" // undocumented field } }; diff --git a/Tests/Remora.Discord.API.Tests/API/Objects/Interactions/Components/SelectDefaultValueTests.cs b/Tests/Remora.Discord.API.Tests/API/Objects/Interactions/Components/SelectDefaultValueTests.cs new file mode 100644 index 0000000000..7b358074f0 --- /dev/null +++ b/Tests/Remora.Discord.API.Tests/API/Objects/Interactions/Components/SelectDefaultValueTests.cs @@ -0,0 +1,39 @@ +// +// SelectDefaultValueTests.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Remora.Discord.API.Abstractions.Objects; +using Remora.Discord.API.Tests.TestBases; + +namespace Remora.Discord.API.Tests.Objects; + +/// +public class SelectDefaultValueTests : ObjectTestBase +{ + /// + /// Initializes a new instance of the class. + /// + /// The test fixture. + public SelectDefaultValueTests(JsonBackedTypeTestFixture fixture) + : base(fixture) + { + } +} diff --git a/Tests/Remora.Discord.API.Tests/API/Objects/Monetization/EntitlementTests.cs b/Tests/Remora.Discord.API.Tests/API/Objects/Monetization/EntitlementTests.cs new file mode 100644 index 0000000000..ed4c9986a3 --- /dev/null +++ b/Tests/Remora.Discord.API.Tests/API/Objects/Monetization/EntitlementTests.cs @@ -0,0 +1,39 @@ +// +// EntitlementTests.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Remora.Discord.API.Abstractions.Objects; +using Remora.Discord.API.Tests.TestBases; + +namespace Remora.Discord.API.Tests.Objects; + +/// +public class EntitlementTests : ObjectTestBase +{ + /// + /// Initializes a new instance of the class. + /// + /// The test fixture. + public EntitlementTests(JsonBackedTypeTestFixture fixture) + : base(fixture) + { + } +} diff --git a/Tests/Remora.Discord.API.Tests/API/Objects/Monetization/SKUTests.cs b/Tests/Remora.Discord.API.Tests/API/Objects/Monetization/SKUTests.cs new file mode 100644 index 0000000000..fd9e674535 --- /dev/null +++ b/Tests/Remora.Discord.API.Tests/API/Objects/Monetization/SKUTests.cs @@ -0,0 +1,39 @@ +// +// SKUTests.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Remora.Discord.API.Abstractions.Objects; +using Remora.Discord.API.Tests.TestBases; + +namespace Remora.Discord.API.Tests.Objects; + +/// +public class SKUTests : ObjectTestBase +{ + /// + /// Initializes a new instance of the class. + /// + /// The test fixture. + public SKUTests(JsonBackedTypeTestFixture fixture) + : base(fixture) + { + } +} diff --git a/Tests/Remora.Discord.API.Tests/API/Objects/Reactions/ReactionCountDetailsTests.cs b/Tests/Remora.Discord.API.Tests/API/Objects/Reactions/ReactionCountDetailsTests.cs new file mode 100644 index 0000000000..b1d966b4e7 --- /dev/null +++ b/Tests/Remora.Discord.API.Tests/API/Objects/Reactions/ReactionCountDetailsTests.cs @@ -0,0 +1,39 @@ +// +// ReactionCountDetailsTests.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Remora.Discord.API.Abstractions.Objects; +using Remora.Discord.API.Tests.TestBases; + +namespace Remora.Discord.API.Tests.Objects; + +/// +public class ReactionCountDetailsTests : ObjectTestBase +{ + /// + /// Initializes a new instance of the class. + /// + /// The test fixture. + public ReactionCountDetailsTests(JsonBackedTypeTestFixture fixture) + : base(fixture) + { + } +} diff --git a/Tests/Remora.Discord.API.Tests/API/Rest/BulkBanResponseTests.cs b/Tests/Remora.Discord.API.Tests/API/Rest/BulkBanResponseTests.cs new file mode 100644 index 0000000000..f77fbb7bf2 --- /dev/null +++ b/Tests/Remora.Discord.API.Tests/API/Rest/BulkBanResponseTests.cs @@ -0,0 +1,39 @@ +// +// BulkBanResponseTests.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Remora.Discord.API.Abstractions.Rest; +using Remora.Discord.API.Tests.TestBases; + +namespace Remora.Discord.API.Tests.Rest; + +/// +public class BulkBanResponseTests : ObjectTestBase +{ + /// + /// Initializes a new instance of the class. + /// + /// The test fixture. + public BulkBanResponseTests(JsonBackedTypeTestFixture fixture) + : base(fixture) + { + } +} diff --git a/Tests/Remora.Discord.API.Tests/Remora.Discord.API.Tests.csproj.DotSettings b/Tests/Remora.Discord.API.Tests/Remora.Discord.API.Tests.csproj.DotSettings index b2e8a172af..1e4547268e 100644 --- a/Tests/Remora.Discord.API.Tests/Remora.Discord.API.Tests.csproj.DotSettings +++ b/Tests/Remora.Discord.API.Tests/Remora.Discord.API.Tests.csproj.DotSettings @@ -11,6 +11,7 @@ True True True + True True True True @@ -31,6 +32,7 @@ True True True + True True True True diff --git a/Tests/Remora.Discord.API.Tests/Services/SampleDataService.cs b/Tests/Remora.Discord.API.Tests/Services/SampleDataService.cs index 9f4b2fc66b..896f501316 100644 --- a/Tests/Remora.Discord.API.Tests/Services/SampleDataService.cs +++ b/Tests/Remora.Discord.API.Tests/Services/SampleDataService.cs @@ -115,7 +115,7 @@ private static Result> GetSampleDataSet [CollectionDefinition("JSON-backed type tests")] -public class JsonBackedTypeTestCollection : ICollectionFixture -{ -} +public class JsonBackedTypeTestCollection : ICollectionFixture; diff --git a/Tests/Remora.Discord.Caching.Tests/CacheTests.cs b/Tests/Remora.Discord.Caching.Tests/CacheTests.cs index bfd4a1b6bb..d9df054f4e 100644 --- a/Tests/Remora.Discord.Caching.Tests/CacheTests.cs +++ b/Tests/Remora.Discord.Caching.Tests/CacheTests.cs @@ -44,9 +44,7 @@ public class CacheTests /// A test fixture for caching. /// [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] - public class Fixture - { - } + public class Fixture; /// /// Initializes a new instance of the class. diff --git a/Tests/Remora.Discord.Commands.Tests/Data/DiscordLimits/TooLongCommand.cs b/Tests/Remora.Discord.Commands.Tests/Data/DiscordLimits/TooLongCommand.cs index a0d43d022f..aad920a593 100644 --- a/Tests/Remora.Discord.Commands.Tests/Data/DiscordLimits/TooLongCommand.cs +++ b/Tests/Remora.Discord.Commands.Tests/Data/DiscordLimits/TooLongCommand.cs @@ -32,7 +32,7 @@ namespace Remora.Discord.Commands.Tests.Data.DiscordLimits; public class TooLongCommand : CommandGroup { - [Command("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")] + [Command("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")] public Task A() { throw new NotImplementedException(); diff --git a/Tests/Remora.Discord.Commands.Tests/Data/Events/ComplexGroup.cs b/Tests/Remora.Discord.Commands.Tests/Data/Events/ComplexGroup.cs index dc5081cffa..650e0cd783 100644 --- a/Tests/Remora.Discord.Commands.Tests/Data/Events/ComplexGroup.cs +++ b/Tests/Remora.Discord.Commands.Tests/Data/Events/ComplexGroup.cs @@ -56,9 +56,7 @@ public class ComplexGroup : CommandGroup /// /// Marks a command as always failing no matter what. /// -public class AlwaysFailConditionAttribute : ConditionAttribute -{ -} +public class AlwaysFailConditionAttribute : ConditionAttribute; /// /// Always fails no matter what. diff --git a/Tests/Remora.Discord.Commands.Tests/Extensions/CommandTreeExtensionTests.cs b/Tests/Remora.Discord.Commands.Tests/Extensions/CommandTreeExtensionTests.cs index 940daf4ab8..edca340e69 100644 --- a/Tests/Remora.Discord.Commands.Tests/Extensions/CommandTreeExtensionTests.cs +++ b/Tests/Remora.Discord.Commands.Tests/Extensions/CommandTreeExtensionTests.cs @@ -1743,10 +1743,13 @@ public void CanMapDeeplyNestedCommand() string.Empty, new[] { - new ApplicationCommandOption(SubCommandGroup, subGroupNode.Key, string.Empty, Options: new[] - { - new ApplicationCommandOption(SubCommand, commandNode.Key, string.Empty) - }) + new ApplicationCommandOption + ( + SubCommandGroup, + subGroupNode.Key, + string.Empty, + Options: new[] { new ApplicationCommandOption(SubCommand, commandNode.Key, string.Empty) } + ) }, default ) @@ -1802,11 +1805,17 @@ public void CanMapComplexTree() string.Empty, new[] { - new ApplicationCommandOption(SubCommandGroup, subGroupNode.Key, string.Empty, Options: new[] - { - new ApplicationCommandOption(SubCommand, commandNodeC.Key, string.Empty), - new ApplicationCommandOption(SubCommand, commandNodeD.Key, string.Empty) - }), + new ApplicationCommandOption + ( + SubCommandGroup, + subGroupNode.Key, + string.Empty, + Options: new[] + { + new ApplicationCommandOption(SubCommand, commandNodeC.Key, string.Empty), + new ApplicationCommandOption(SubCommand, commandNodeD.Key, string.Empty) + } + ), new ApplicationCommandOption(SubCommand, commandNodeE.Key, string.Empty), new ApplicationCommandOption(SubCommand, commandNodeF.Key, string.Empty) }, diff --git a/Tests/Remora.Discord.Commands.Tests/Extensions/InteractionDataExtensionsTests.cs b/Tests/Remora.Discord.Commands.Tests/Extensions/InteractionDataExtensionsTests.cs index dc04b4145f..54ca66e9cc 100644 --- a/Tests/Remora.Discord.Commands.Tests/Extensions/InteractionDataExtensionsTests.cs +++ b/Tests/Remora.Discord.Commands.Tests/Extensions/InteractionDataExtensionsTests.cs @@ -267,7 +267,7 @@ public void CanUnpackUserCommand() command.UnpackInteraction(out var commandPath, out var parameters); Assert.Equal(new[] { "user" }, commandPath); - Assert.Equal(1, parameters.Count); + Assert.Single(parameters); Assert.True(parameters.ContainsKey("user")); Assert.Equal("1", parameters["user"][0]); } @@ -289,7 +289,7 @@ public void CanUnpackMessageCommand() command.UnpackInteraction(out var commandPath, out var parameters); Assert.Equal(new[] { "message" }, commandPath); - Assert.Equal(1, parameters.Count); + Assert.Single(parameters); Assert.True(parameters.ContainsKey("message")); Assert.Equal("1", parameters["message"][0]); } diff --git a/Tests/Remora.Discord.Commands.Tests/Parsers/MessageParserTests.cs b/Tests/Remora.Discord.Commands.Tests/Parsers/MessageParserTests.cs index 5d96db5971..849e6f7c7a 100644 --- a/Tests/Remora.Discord.Commands.Tests/Parsers/MessageParserTests.cs +++ b/Tests/Remora.Discord.Commands.Tests/Parsers/MessageParserTests.cs @@ -94,7 +94,7 @@ public async Task CanParseMessageByNumber() var tryParse = await _parser.TryParseAsync(messageID.ToString()); ResultAssert.Successful(tryParse); - _channelAPIMock.Verify(_ => _ + _channelAPIMock.Verify(c => c .GetChannelMessageAsync ( It.IsAny(), @@ -124,7 +124,7 @@ public async Task CanParseMessageByMessageLink(string value) var tryParse = await _parser.TryParseAsync(value); ResultAssert.Successful(tryParse); - _channelAPIMock.Verify(_ => _ + _channelAPIMock.Verify(c => c .GetChannelMessageAsync ( It.Is(s => s == channelID), diff --git a/Tests/Remora.Discord.Extensions.Tests/AnsiStringBuilderTests.cs b/Tests/Remora.Discord.Extensions.Tests/AnsiStringBuilderTests.cs index b0124ed0c1..cedae9fc99 100644 --- a/Tests/Remora.Discord.Extensions.Tests/AnsiStringBuilderTests.cs +++ b/Tests/Remora.Discord.Extensions.Tests/AnsiStringBuilderTests.cs @@ -31,7 +31,7 @@ namespace Remora.Discord.Extensions.Tests; /// public class AnsiStringBuilderTests { - private const char AnsiEscapeChar = '\u001b'; + private const char _ansiEscapeChar = '\u001b'; /// /// Tests to see if the method bolds the input text. @@ -42,7 +42,7 @@ public class AnsiStringBuilderTests [InlineData("Remora.Discord")] public void BoldDefaultSuccess(string data) { - var expected = $"{AnsiEscapeChar}[{AnsiStyle.Reset};{AnsiStyle.Bold}m{data}"; + var expected = $"{_ansiEscapeChar}[{AnsiStyle.Reset};{AnsiStyle.Bold}m{data}"; var actual = new AnsiStringBuilder().Bold().Append(data).Build(); Assert.Equal(expected, actual); } @@ -57,8 +57,8 @@ public void BoldDefaultSuccess(string data) [InlineData("Remora.Discord", 7)] public void BoldLeadingCharsSuccess(string data, int charCount) { - var expected = $"{AnsiEscapeChar}[{AnsiStyle.Reset};{AnsiStyle.Bold}m{data.Remove(charCount)}" + - $"{AnsiEscapeChar}[{AnsiStyle.Reset}m{data[charCount..]}"; + var expected = $"{_ansiEscapeChar}[{AnsiStyle.Reset};{AnsiStyle.Bold}m{data.Remove(charCount)}" + + $"{_ansiEscapeChar}[{AnsiStyle.Reset}m{data[charCount..]}"; var actual = new AnsiStringBuilder().Bold().Append(data.Remove(charCount)) .Bold(false).Append(data[charCount..]) .Build(); @@ -74,7 +74,7 @@ public void BoldLeadingCharsSuccess(string data, int charCount) [InlineData("Remora.Discord")] public void UnderlineDefaultSuccess(string data) { - var expected = $"{AnsiEscapeChar}[{AnsiStyle.Reset};{AnsiStyle.Underline}m{data}"; + var expected = $"{_ansiEscapeChar}[{AnsiStyle.Reset};{AnsiStyle.Underline}m{data}"; var actual = new AnsiStringBuilder().Underline().Append(data).Build(); Assert.Equal(expected, actual); } @@ -89,8 +89,8 @@ public void UnderlineDefaultSuccess(string data) [InlineData("Remora.Discord", 7)] public void UnderlineLeadingCharsSuccess(string data, int charCount) { - var expected = $"{AnsiEscapeChar}[{AnsiStyle.Reset};{AnsiStyle.Underline}m{data.Remove(charCount)}" + - $"{AnsiEscapeChar}[{AnsiStyle.Reset}m{data[charCount..]}"; + var expected = $"{_ansiEscapeChar}[{AnsiStyle.Reset};{AnsiStyle.Underline}m{data.Remove(charCount)}" + + $"{_ansiEscapeChar}[{AnsiStyle.Reset}m{data[charCount..]}"; var actual = new AnsiStringBuilder().Underline().Append(data.Remove(charCount)) .Underline(false).Append(data[charCount..]) .Build(); @@ -114,7 +114,7 @@ public void ForegroundDefaultSuccess(string data) // Foreground color None has some special handling var expected = foregroundColor is AnsiForegroundColor.None ? $"{data}" - : $"{AnsiEscapeChar}[{AnsiStyle.Reset};{(int)foregroundColor}m{data}"; + : $"{_ansiEscapeChar}[{AnsiStyle.Reset};{(int)foregroundColor}m{data}"; var actual = new AnsiStringBuilder().Foreground(foregroundColor).Append(data).Build(); Assert.Equal(expected, actual); @@ -145,8 +145,8 @@ public void ForegroundLeadingCharsSuccess(string data, int charCount) } else { - expected = $"{AnsiEscapeChar}[{AnsiStyle.Reset};{(int)foregroundColor}m{data.Remove(charCount)}" + - $"{AnsiEscapeChar}[{AnsiStyle.Reset}m{data[charCount..]}"; + expected = $"{_ansiEscapeChar}[{AnsiStyle.Reset};{(int)foregroundColor}m{data.Remove(charCount)}" + + $"{_ansiEscapeChar}[{AnsiStyle.Reset}m{data[charCount..]}"; } var actual = new AnsiStringBuilder().Foreground(foregroundColor).Append(data.Remove(charCount)) @@ -173,7 +173,7 @@ public void BackgroundDefaultSuccess(string data) // Background color None has some special handling var expected = backgroundColor is AnsiBackgroundColor.None ? $"{data}" - : $"{AnsiEscapeChar}[{AnsiStyle.Reset};{(int)backgroundColor}m{data}"; + : $"{_ansiEscapeChar}[{AnsiStyle.Reset};{(int)backgroundColor}m{data}"; var actual = new AnsiStringBuilder().Background(backgroundColor).Append(data).Build(); Assert.Equal(expected, actual); @@ -204,8 +204,8 @@ public void BackgroundLeadingCharsSuccess(string data, int charCount) } else { - expected = $"{AnsiEscapeChar}[{AnsiStyle.Reset};{(int)backgroundColor}m{data.Remove(charCount)}" + - $"{AnsiEscapeChar}[{AnsiStyle.Reset}m{data[charCount..]}"; + expected = $"{_ansiEscapeChar}[{AnsiStyle.Reset};{(int)backgroundColor}m{data.Remove(charCount)}" + + $"{_ansiEscapeChar}[{AnsiStyle.Reset}m{data[charCount..]}"; } var actual = new AnsiStringBuilder().Background(backgroundColor).Append(data.Remove(charCount)) @@ -223,11 +223,11 @@ public void BackgroundLeadingCharsSuccess(string data, int charCount) public void MixBoldAndUnderlineSuccess() { var expected = "Does " + - $"{AnsiEscapeChar}[{AnsiStyle.Reset};{AnsiStyle.Bold}mbold{AnsiEscapeChar}[{AnsiStyle.Reset}m" + + $"{_ansiEscapeChar}[{AnsiStyle.Reset};{AnsiStyle.Bold}mbold{_ansiEscapeChar}[{AnsiStyle.Reset}m" + " and " + - $"{AnsiEscapeChar}[{AnsiStyle.Reset};{AnsiStyle.Underline}munderline{AnsiEscapeChar}[{AnsiStyle.Reset}m" + + $"{_ansiEscapeChar}[{AnsiStyle.Reset};{AnsiStyle.Underline}munderline{_ansiEscapeChar}[{AnsiStyle.Reset}m" + " " + - $"{AnsiEscapeChar}[{AnsiStyle.Reset};{AnsiStyle.Bold};{AnsiStyle.Underline}mmix{AnsiEscapeChar}[{AnsiStyle.Reset}m" + + $"{_ansiEscapeChar}[{AnsiStyle.Reset};{AnsiStyle.Bold};{AnsiStyle.Underline}mmix{_ansiEscapeChar}[{AnsiStyle.Reset}m" + "?"; var actual = new AnsiStringBuilder() @@ -260,11 +260,11 @@ AnsiBackgroundColor backgroundColor ) { var expected = "Does " + - $"{AnsiEscapeChar}[{AnsiStyle.Reset};{(int)foregroundColor}mforeground{AnsiEscapeChar}[{AnsiStyle.Reset}m" + + $"{_ansiEscapeChar}[{AnsiStyle.Reset};{(int)foregroundColor}mforeground{_ansiEscapeChar}[{AnsiStyle.Reset}m" + " and " + - $"{AnsiEscapeChar}[{AnsiStyle.Reset};{(int)backgroundColor}mbackground{AnsiEscapeChar}[{AnsiStyle.Reset}m" + + $"{_ansiEscapeChar}[{AnsiStyle.Reset};{(int)backgroundColor}mbackground{_ansiEscapeChar}[{AnsiStyle.Reset}m" + " " + - $"{AnsiEscapeChar}[{AnsiStyle.Reset};{(int)backgroundColor};{(int)foregroundColor}mmix{AnsiEscapeChar}[{AnsiStyle.Reset}m" + + $"{_ansiEscapeChar}[{AnsiStyle.Reset};{(int)backgroundColor};{(int)foregroundColor}mmix{_ansiEscapeChar}[{AnsiStyle.Reset}m" + "?"; var actual = new AnsiStringBuilder() @@ -294,17 +294,17 @@ AnsiBackgroundColor backgroundColor public void MixAllSuccess(AnsiForegroundColor foregroundColor, AnsiBackgroundColor backgroundColor) { var expected = "Does " + - $"{AnsiEscapeChar}[{AnsiStyle.Reset};{AnsiStyle.Bold}mbold{AnsiEscapeChar}[{AnsiStyle.Reset}m" + + $"{_ansiEscapeChar}[{AnsiStyle.Reset};{AnsiStyle.Bold}mbold{_ansiEscapeChar}[{AnsiStyle.Reset}m" + " and " + - $"{AnsiEscapeChar}[{AnsiStyle.Reset};{AnsiStyle.Underline}munderline{AnsiEscapeChar}[{AnsiStyle.Reset}m" + + $"{_ansiEscapeChar}[{AnsiStyle.Reset};{AnsiStyle.Underline}munderline{_ansiEscapeChar}[{AnsiStyle.Reset}m" + " and " + - $"{AnsiEscapeChar}[{AnsiStyle.Reset};{(int)foregroundColor}mforeground{AnsiEscapeChar}[{AnsiStyle.Reset}m" + + $"{_ansiEscapeChar}[{AnsiStyle.Reset};{(int)foregroundColor}mforeground{_ansiEscapeChar}[{AnsiStyle.Reset}m" + " and " + - $"{AnsiEscapeChar}[{AnsiStyle.Reset};{(int)backgroundColor}mbackground{AnsiEscapeChar}[{AnsiStyle.Reset}m" + + $"{_ansiEscapeChar}[{AnsiStyle.Reset};{(int)backgroundColor}mbackground{_ansiEscapeChar}[{AnsiStyle.Reset}m" + " " + - $"{AnsiEscapeChar}[{AnsiStyle.Reset};{AnsiStyle.Bold};{AnsiStyle.Underline};{(int)backgroundColor};{(int)foregroundColor}m" + + $"{_ansiEscapeChar}[{AnsiStyle.Reset};{AnsiStyle.Bold};{AnsiStyle.Underline};{(int)backgroundColor};{(int)foregroundColor}m" + "mix" + - $"{AnsiEscapeChar}[{AnsiStyle.Reset}m" + + $"{_ansiEscapeChar}[{AnsiStyle.Reset}m" + "?"; var actual = new AnsiStringBuilder() @@ -332,17 +332,17 @@ public void MixAllSuccess(AnsiForegroundColor foregroundColor, AnsiBackgroundCol [Fact] public void ResetSuccess() { - var expected = $"{AnsiEscapeChar}[{AnsiStyle.Reset};{AnsiStyle.Bold}mBold{AnsiEscapeChar}[{AnsiStyle.Reset}m" + + var expected = $"{_ansiEscapeChar}[{AnsiStyle.Reset};{AnsiStyle.Bold}mBold{_ansiEscapeChar}[{AnsiStyle.Reset}m" + " and " + - $"{AnsiEscapeChar}[{AnsiStyle.Reset};{AnsiStyle.Underline}munderlined{AnsiEscapeChar}[{AnsiStyle.Reset}m" + + $"{_ansiEscapeChar}[{AnsiStyle.Reset};{AnsiStyle.Underline}munderlined{_ansiEscapeChar}[{AnsiStyle.Reset}m" + " " + - $"{AnsiEscapeChar}[{AnsiStyle.Reset};{AnsiStyle.Bold};{AnsiStyle.Underline}mcombined{AnsiEscapeChar}[{AnsiStyle.Reset}m" + + $"{_ansiEscapeChar}[{AnsiStyle.Reset};{AnsiStyle.Bold};{AnsiStyle.Underline}mcombined{_ansiEscapeChar}[{AnsiStyle.Reset}m" + "!\n" + - $"{AnsiEscapeChar}[{AnsiStyle.Reset};{(int)AnsiForegroundColor.Red}mShould " + - $"{AnsiEscapeChar}[{AnsiStyle.Reset};{(int)AnsiBackgroundColor.Base0};{(int)AnsiForegroundColor.Red}mreset " + - $"{AnsiEscapeChar}[{AnsiStyle.Reset};{AnsiStyle.Underline};{(int)AnsiBackgroundColor.Base0};{(int)AnsiForegroundColor.Red}malso " + - $"{AnsiEscapeChar}[{AnsiStyle.Reset};{AnsiStyle.Bold};{AnsiStyle.Underline};{(int)AnsiBackgroundColor.Base0};{(int)AnsiForegroundColor.Red}mcolors" + - $"{AnsiEscapeChar}[{AnsiStyle.Reset}m!"; + $"{_ansiEscapeChar}[{AnsiStyle.Reset};{(int)AnsiForegroundColor.Red}mShould " + + $"{_ansiEscapeChar}[{AnsiStyle.Reset};{(int)AnsiBackgroundColor.Base0};{(int)AnsiForegroundColor.Red}mreset " + + $"{_ansiEscapeChar}[{AnsiStyle.Reset};{AnsiStyle.Underline};{(int)AnsiBackgroundColor.Base0};{(int)AnsiForegroundColor.Red}malso " + + $"{_ansiEscapeChar}[{AnsiStyle.Reset};{AnsiStyle.Bold};{AnsiStyle.Underline};{(int)AnsiBackgroundColor.Base0};{(int)AnsiForegroundColor.Red}mcolors" + + $"{_ansiEscapeChar}[{AnsiStyle.Reset}m!"; var actual = new AnsiStringBuilder() .Bold().Append("Bold").Reset() diff --git a/Tests/Remora.Discord.Extensions.Tests/NavigationTests.cs b/Tests/Remora.Discord.Extensions.Tests/NavigationTests.cs new file mode 100644 index 0000000000..3f02900e22 --- /dev/null +++ b/Tests/Remora.Discord.Extensions.Tests/NavigationTests.cs @@ -0,0 +1,47 @@ +// +// NavigationTests.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Remora.Discord.Extensions.Formatting; +using Xunit; + +namespace Remora.Discord.Extensions.Tests; + +/// +/// Tests the class. +/// +public class NavigationTests +{ + /// + /// Tests whether the method produces correct results. + /// + /// The navigation target. + /// The expected navigation string. + [Theory] + [InlineData(GuildNavigationType.Customize, "")] + [InlineData(GuildNavigationType.Browse, "")] + [InlineData(GuildNavigationType.Guide, "")] + public void FormatsGuildNavigationCorrectly(GuildNavigationType target, string expected) + { + var actual = Navigation.Guild(target); + Assert.Equal(expected, actual); + } +} diff --git a/Tests/Remora.Discord.Gateway.Tests/Tests/DiscordGatewayClientTests.cs b/Tests/Remora.Discord.Gateway.Tests/Tests/DiscordGatewayClientTests.cs index c0e0e6ca0a..4a35400944 100644 --- a/Tests/Remora.Discord.Gateway.Tests/Tests/DiscordGatewayClientTests.cs +++ b/Tests/Remora.Discord.Gateway.Tests/Tests/DiscordGatewayClientTests.cs @@ -711,6 +711,76 @@ public async Task CanReconnectAfterExceptionAsync() ResultAssert.Successful(runResult); } + /// + /// Tests whether the client can reconnect after being sent a Reconnect instead of Hello. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task CanReconnectAfterReconnectInsteadOfHelloAsync() + { + var tokenSource = new CancellationTokenSource(); + var transportMock = new MockedTransportServiceBuilder(_testOutput) + .WithTimeout(TimeSpan.FromSeconds(30)) + .Sequence + ( + s => s + .ExpectConnection + ( + new Uri($"wss://gateway.discord.gg/?v={(int)DiscordAPIVersion.V10}&encoding=json") + ) + .Send(new Reconnect()) + .ExpectDisconnect() + .ExpectConnection + ( + new Uri($"wss://gateway.discord.gg/?v={(int)DiscordAPIVersion.V10}&encoding=json") + ) + .Send(new Hello(TimeSpan.FromMilliseconds(200))) + .Expect + ( + i => + { + Assert.Equal(Constants.MockToken, i?.Token); + return true; + } + ) + .Send + ( + new Ready + ( + 8, + Constants.BotUser, + new List(), + Constants.MockSessionID, + Constants.MockResumeGatewayUrl, + default, + new PartialApplication() + ) + ) + ) + .Continuously + ( + c => c + .Expect() + .Send() + ) + .Finish(tokenSource) + .Build(); + + var transportMockDescriptor = ServiceDescriptor.Singleton(typeof(IPayloadTransportService), transportMock); + + var services = new ServiceCollection() + .AddDiscordGateway(_ => Constants.MockToken) + .Replace(transportMockDescriptor) + .Replace(CreateMockedGatewayAPI()) + .AddSingleton() + .BuildServiceProvider(true); + + var client = services.GetRequiredService(); + var runResult = await client.RunAsync(tokenSource.Token); + + ResultAssert.Successful(runResult); + } + private static ServiceDescriptor CreateMockedGatewayAPI() { var gatewayAPIMock = new Mock(); diff --git a/Tests/Remora.Discord.Gateway.Tests/Transport/Events/IEvent.cs b/Tests/Remora.Discord.Gateway.Tests/Transport/Events/IEvent.cs index efd3ee8a05..ba1712742a 100644 --- a/Tests/Remora.Discord.Gateway.Tests/Transport/Events/IEvent.cs +++ b/Tests/Remora.Discord.Gateway.Tests/Transport/Events/IEvent.cs @@ -28,6 +28,4 @@ namespace Remora.Discord.Gateway.Tests.Transport.Events; /// Represents a marker interface for events. /// [PublicAPI] -public interface IEvent -{ -} +public interface IEvent; diff --git a/Tests/Remora.Discord.Gateway.Tests/Transport/MockedTransportSequenceBuilder.cs b/Tests/Remora.Discord.Gateway.Tests/Transport/MockedTransportSequenceBuilder.cs index 01bd29f6bc..ef9831d5cd 100644 --- a/Tests/Remora.Discord.Gateway.Tests/Transport/MockedTransportSequenceBuilder.cs +++ b/Tests/Remora.Discord.Gateway.Tests/Transport/MockedTransportSequenceBuilder.cs @@ -59,7 +59,7 @@ public MockedTransportSequenceBuilder ExpectConnection(Uri? connectionUri = null if (connectionUri != u) { - throw new EqualException(connectionUri, u); + throw EqualException.ForMismatchedValues(connectionUri, u); } return EventMatch.Pass; @@ -106,7 +106,7 @@ public MockedTransportSequenceBuilder Expect(Func? ? p.GetType().GetGenericArguments()[0].Name : p.GetType().Name; - throw new IsTypeException(typeof(TExpected).Name, actualTypename); + throw IsTypeException.ForMismatchedType(typeof(TExpected).Name, actualTypename); } if (expectation is null) diff --git a/Tests/Remora.Discord.Gateway.Tests/Transport/MockedTransportService.cs b/Tests/Remora.Discord.Gateway.Tests/Transport/MockedTransportService.cs index 1f07a3c5c1..f812fa03ff 100644 --- a/Tests/Remora.Discord.Gateway.Tests/Transport/MockedTransportService.cs +++ b/Tests/Remora.Discord.Gateway.Tests/Transport/MockedTransportService.cs @@ -113,7 +113,7 @@ public async Task ConnectAsync(Uri endpoint, CancellationToken ct = defa } case EventMatch.Fail: { - throw new TrueException("An event in a sequence failed.", null); + throw TrueException.ForNonTrueValue("An event in a sequence failed.", null); } case EventMatch.Ignore: { @@ -148,7 +148,7 @@ public async Task ConnectAsync(Uri endpoint, CancellationToken ct = defa } case EventMatch.Fail: { - throw new TrueException("An event in a continuous sequence failed.", null); + throw TrueException.ForNonTrueValue("An event in a continuous sequence failed.", null); } case EventMatch.Ignore: { @@ -165,7 +165,7 @@ public async Task ConnectAsync(Uri endpoint, CancellationToken ct = defa var remainingSequences = _sequences.Except(_finishedSequences).ToList(); if (remainingSequences.Count == 0) { - _finisher.Cancel(); + await _finisher.CancelAsync(); } if (!sequenceAdvanced) @@ -183,7 +183,7 @@ public async Task ConnectAsync(Uri endpoint, CancellationToken ct = defa } /// - public async Task SendPayloadAsync(IPayload payload, CancellationToken ct = default) + public async ValueTask SendPayloadAsync(IPayload payload, CancellationToken ct = default) { try { @@ -215,7 +215,7 @@ public async Task SendPayloadAsync(IPayload payload, CancellationToken c } case EventMatch.Fail: { - throw new TrueException("An event in a sequence failed.", null); + throw TrueException.ForNonTrueValue("An event in a sequence failed.", null); } case EventMatch.Ignore: { @@ -255,7 +255,7 @@ public async Task SendPayloadAsync(IPayload payload, CancellationToken c } case EventMatch.Fail: { - throw new TrueException("An event in a continuous sequence failed.", null); + throw TrueException.ForNonTrueValue("An event in a continuous sequence failed.", null); } case EventMatch.Ignore: { @@ -272,13 +272,13 @@ public async Task SendPayloadAsync(IPayload payload, CancellationToken c if (!hadExpectedEvent && !_serviceOptions.IgnoreUnexpected) { - throw new IsTypeException("[sequence]", payload.GetType().ToString()); + throw IsTypeException.ForMismatchedType("[sequence]", payload.GetType().ToString()); } var remainingSequences = _sequences.Except(_finishedSequences).ToList(); if (remainingSequences.Count == 0) { - _finisher.Cancel(); + await _finisher.CancelAsync(); } if (!sequenceAdvanced) @@ -295,7 +295,7 @@ public async Task SendPayloadAsync(IPayload payload, CancellationToken c } /// - public async Task> ReceivePayloadAsync(CancellationToken ct = default) + public async ValueTask> ReceivePayloadAsync(CancellationToken ct = default) { while (!ct.IsCancellationRequested) { @@ -362,7 +362,7 @@ public async Task> ReceivePayloadAsync(CancellationToken ct = d var remainingSequences = _sequences.Except(_finishedSequences).ToList(); if (remainingSequences.Count == 0) { - _finisher.Cancel(); + await _finisher.CancelAsync(); } if (!sequenceAdvanced) @@ -421,7 +421,7 @@ public async Task DisconnectAsync } case EventMatch.Fail: { - throw new TrueException("An event in a sequence failed.", null); + throw TrueException.ForNonTrueValue("An event in a sequence failed.", null); } case EventMatch.Ignore: { @@ -456,7 +456,7 @@ public async Task DisconnectAsync } case EventMatch.Fail: { - throw new TrueException("An event in a continuous sequence failed.", null); + throw TrueException.ForNonTrueValue("An event in a continuous sequence failed.", null); } case EventMatch.Ignore: { @@ -473,7 +473,7 @@ public async Task DisconnectAsync var remainingSequences = _sequences.Except(_finishedSequences).ToList(); if (remainingSequences.Count == 0) { - _finisher.Cancel(); + await _finisher.CancelAsync(); } if (!sequenceAdvanced) diff --git a/Tests/Remora.Discord.Rest.Tests/API/Applications/DiscordRestApplicationAPITests.cs b/Tests/Remora.Discord.Rest.Tests/API/Applications/DiscordRestApplicationAPITests.cs index 944a98c43f..ca636ebaa7 100644 --- a/Tests/Remora.Discord.Rest.Tests/API/Applications/DiscordRestApplicationAPITests.cs +++ b/Tests/Remora.Discord.Rest.Tests/API/Applications/DiscordRestApplicationAPITests.cs @@ -22,6 +22,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Net; using System.Net.Http; using System.Numerics; @@ -953,7 +954,8 @@ public async Task PerformsRequestCorrectly() b => b .Expect ( - HttpMethod.Delete, $"{Constants.BaseURL}applications/{applicationID}/commands/{commandID}" + HttpMethod.Delete, + $"{Constants.BaseURL}applications/{applicationID}/commands/{commandID}" ) .WithNoContent() .Respond(HttpStatusCode.NoContent) @@ -2447,4 +2449,128 @@ public async Task PerformsRequestCorrectly() ResultAssert.Successful(result); } } + + /// + /// Tests the method. + /// + public class EditCurrentApplicationAsync : RestAPITestBase + { + /// + /// Initializes a new instance of the class. + /// + /// The test fixture. + public EditCurrentApplicationAsync(RestAPITestFixture fixture) + : base(fixture) + { + } + + /// + /// Tests whether the API method performs its request correctly. + /// + /// A representing the result of the asynchronous operation. + [Fact] + public async Task PerformsRequestCorrectly() + { + var customInstallUrl = new Uri("https://example.org/install"); + var description = "aaa"; + var roleConnectionsVerificationUrl = new Uri("https://example.org/verify"); + var installParams = new ApplicationInstallParameters + ( + Array.Empty(), + new DiscordPermissionSet(DiscordPermission.Administrator) + ); + var flags = ApplicationFlags.ApplicationCommandBadge; + + // Create a dummy PNG image + await using var icon = new MemoryStream(); + await using var binaryWriter = new BinaryWriter(icon); + binaryWriter.Write(new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }); + icon.Position = 0; + + // Create a dummy PNG image + await using var cover = new MemoryStream(); + await using var coverBinaryWriter = new BinaryWriter(cover); + coverBinaryWriter.Write(new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }); + cover.Position = 0; + + var interactionsEndpointUrl = new Uri("https://example.org/interact"); + var tags = new[] { "ooga", "booga" }; + + var integrationTypes = new[] + { + ApplicationIntegrationType.UserInstallable, + ApplicationIntegrationType.GuildInstallable + }; + + var api = CreateAPI + ( + b => b + .Expect + ( + HttpMethod.Patch, + $"{Constants.BaseURL}applications/@me" + ) + .WithJson + ( + j => j.IsObject + ( + o => o + .WithProperty("custom_install_url", p => p.Is(customInstallUrl.ToString())) + .WithProperty("description", p => p.Is(description)) + .WithProperty + ( + "role_connections_verification_url", + p => p.Is(roleConnectionsVerificationUrl.ToString()) + ) + .WithProperty("install_params", p => p.IsObject()) + .WithProperty("flags", p => p.Is((int)flags)) + .WithProperty("icon", p => p.Is("data:image/png;base64,iVBORw0KGgo=")) + .WithProperty("cover_image", p => p.Is("data:image/png;base64,iVBORw0KGgo=")) + .WithProperty + ( + "interactions_endpoint_url", + p => p.Is(interactionsEndpointUrl.ToString()) + ) + .WithProperty + ( + "tags", + p => p.IsArray + ( + a => a + .WithElement(0, e => e.Is("ooga")) + .WithElement(1, e => e.Is("booga")) + ) + ) + .WithProperty + ( + "integration_types", + p => p.IsArray + ( + a => a.WithCount(2) + .WithElement(0, e => e.Is("1")) + .WithElement(1, e => e.Is("0")) + ) + ) + ) + ) + .Respond("application/json", SampleRepository.Samples[typeof(IApplication)]) + ); + + var result = await api.EditCurrentApplicationAsync + ( + customInstallUrl, + description, + roleConnectionsVerificationUrl, + installParams, + flags, + icon, + cover, + interactionsEndpointUrl, + tags, + integrationTypes + ); + + ResultAssert.Successful(result); + } + } } diff --git a/Tests/Remora.Discord.Rest.Tests/API/AutoModeration/DiscordRestAutoModerationAPITests.cs b/Tests/Remora.Discord.Rest.Tests/API/AutoModeration/DiscordRestAutoModerationAPITests.cs index 166fb70aab..e834f3bb9a 100644 --- a/Tests/Remora.Discord.Rest.Tests/API/AutoModeration/DiscordRestAutoModerationAPITests.cs +++ b/Tests/Remora.Discord.Rest.Tests/API/AutoModeration/DiscordRestAutoModerationAPITests.cs @@ -162,12 +162,16 @@ public async Task PerformsRequestCorrectly() .WithProperty("name", p => p.Is(name)) .WithProperty("event_type", p => p.Is((int)eventType)) .WithProperty("trigger_type", p => p.Is((int)triggerType)) - .WithProperty("trigger_metadata", p => p.IsObject + .WithProperty ( - oo => oo - .WithProperty("keyword_filter", pp => pp.IsArray(a => a.WithCount(0))) - .WithProperty("presets", pp => pp.IsArray(a => a.WithCount(0))) - )) + "trigger_metadata", + p => p.IsObject + ( + oo => oo + .WithProperty("keyword_filter", pp => pp.IsArray(a => a.WithCount(0))) + .WithProperty("presets", pp => pp.IsArray(a => a.WithCount(0))) + ) + ) .WithProperty("actions", p => p.IsArray(a => a.WithCount(0))) .WithProperty("enabled", p => p.Is(enabled)) .WithProperty("exempt_roles", p => p.IsArray(a => a.WithCount(0))) @@ -242,12 +246,16 @@ public async Task PerformsRequestCorrectly() .WithProperty("name", p => p.Is(name)) .WithProperty("event_type", p => p.Is((int)eventType)) .WithProperty("trigger_type", p => p.Is((int)triggerType)) - .WithProperty("trigger_metadata", p => p.IsObject + .WithProperty ( - oo => oo - .WithProperty("keyword_filter", pp => pp.IsArray(a => a.WithCount(0))) - .WithProperty("presets", pp => pp.IsArray(a => a.WithCount(0))) - )) + "trigger_metadata", + p => p.IsObject + ( + oo => oo + .WithProperty("keyword_filter", pp => pp.IsArray(a => a.WithCount(0))) + .WithProperty("presets", pp => pp.IsArray(a => a.WithCount(0))) + ) + ) .WithProperty("actions", p => p.IsArray(a => a.WithCount(0))) .WithProperty("enabled", p => p.Is(enabled)) .WithProperty("exempt_roles", p => p.IsArray(a => a.WithCount(0))) diff --git a/Tests/Remora.Discord.Rest.Tests/API/Channels/DiscordRestChannelAPITests.cs b/Tests/Remora.Discord.Rest.Tests/API/Channels/DiscordRestChannelAPITests.cs index a454b97f98..c6af7029a8 100644 --- a/Tests/Remora.Discord.Rest.Tests/API/Channels/DiscordRestChannelAPITests.cs +++ b/Tests/Remora.Discord.Rest.Tests/API/Channels/DiscordRestChannelAPITests.cs @@ -548,6 +548,88 @@ public async Task PerformsForumChannelRequestCorrectly() ResultAssert.Successful(result); } + /// + /// Tests whether the API method performs its request correctly. + /// + /// A representing the result of the asynchronous operation. + [Fact] + public async Task PerformsMediaChannelRequestCorrectly() + { + var channelId = DiscordSnowflake.New(0); + var name = "brr"; + var position = 1; + var topic = "wooga"; + var isNsfw = true; + var rateLimitPerUser = 10; + var permissionOverwrites = new List(); + var parentID = new Snowflake(1); + var defaultAutoArchiveDuration = AutoArchiveDuration.Day; + var flags = ChannelFlags.RequireTag; + var availableTags = new List(); + var defaultReactionEmoji = new DefaultReaction(new Snowflake(1)); + var defaultThreadRateLimitPerUser = 2; + var defaultSortOrder = SortOrder.CreationDate; + var reason = "test"; + + var api = CreateAPI + ( + b => b + .Expect(HttpMethod.Patch, $"{Constants.BaseURL}channels/{channelId.ToString()}") + .WithHeaders(Constants.AuditLogHeaderName, reason) + .WithJson + ( + j => j + .IsObject + ( + o => o + .WithProperty("name", p => p.Is(name)) + .WithProperty("position", p => p.Is(position)) + .WithProperty("topic", p => p.Is(topic)) + .WithProperty("nsfw", p => p.Is(isNsfw)) + .WithProperty("rate_limit_per_user", p => p.Is(rateLimitPerUser)) + .WithProperty("permission_overwrites", p => p.IsArray(a => a.WithCount(0))) + .WithProperty("parent_id", p => p.Is(parentID.ToString())) + .WithProperty + ( + "default_auto_archive_duration", + p => p.Is((int)defaultAutoArchiveDuration) + ) + .WithProperty("flags", p => p.Is((int)flags)) + .WithProperty("available_tags", p => p.IsArray(a => a.WithCount(0))) + .WithProperty("default_reaction_emoji", p => p.IsObject()) + .WithProperty + ( + "default_thread_rate_limit_per_user", + p => p.Is(defaultThreadRateLimitPerUser) + ) + .WithProperty("default_sort_order", p => p.Is((int)defaultSortOrder)) + ) + ) + .Respond("application/json", SampleRepository.Samples[typeof(IChannel)]) + ); + + var result = await api.ModifyMediaChannelAsync + ( + channelId, + name, + position, + topic, + isNsfw, + rateLimitPerUser, + permissionOverwrites, + parentID, + defaultAutoArchiveDuration, + flags, + availableTags, + defaultReactionEmoji, + defaultThreadRateLimitPerUser, + defaultSortOrder, + reason + ); + + ResultAssert.Successful(result); + } + /// /// Tests whether the API method performs its request correctly. /// @@ -1025,6 +1107,7 @@ public async Task PerformsNormalRequestCorrectly() var tts = false; var allowedMentions = new AllowedMentions(); var flags = MessageFlags.SuppressEmbeds; + var enforceNonce = true; var api = CreateAPI ( @@ -1040,6 +1123,7 @@ public async Task PerformsNormalRequestCorrectly() .WithProperty("tts", p => p.Is(tts)) .WithProperty("allowed_mentions", p => p.IsObject()) .WithProperty("flags", p => p.Is((int)flags)) + .WithProperty("enforce_nonce", p => p.Is(enforceNonce)) ) ) .Respond("application/json", SampleRepository.Samples[typeof(IMessage)]) @@ -1052,7 +1136,8 @@ public async Task PerformsNormalRequestCorrectly() nonce, tts, allowedMentions: allowedMentions, - flags: flags + flags: flags, + enforceNonce: enforceNonce ); ResultAssert.Successful(result); @@ -1071,6 +1156,7 @@ public async Task PerformsEmbedRequestCorrectly() var nonce = "aasda"; var tts = false; var allowedMentions = new AllowedMentions(); + var enforceNonce = true; var api = CreateAPI ( @@ -1085,6 +1171,7 @@ public async Task PerformsEmbedRequestCorrectly() .WithProperty("nonce", p => p.Is(nonce)) .WithProperty("tts", p => p.Is(tts)) .WithProperty("allowed_mentions", p => p.IsObject()) + .WithProperty("enforce_nonce", p => p.Is(enforceNonce)) ) ) .Respond("application/json", SampleRepository.Samples[typeof(IMessage)]) @@ -1096,7 +1183,8 @@ public async Task PerformsEmbedRequestCorrectly() nonce: nonce, isTTS: tts, embeds: embeds, - allowedMentions: allowedMentions + allowedMentions: allowedMentions, + enforceNonce: enforceNonce ); ResultAssert.Successful(result); @@ -1116,6 +1204,7 @@ public async Task PerformsComponentRequestCorrectly() var tts = false; var allowedMentions = new AllowedMentions(); var components = new List(); + var enforceNonce = true; var api = CreateAPI ( @@ -1131,6 +1220,7 @@ public async Task PerformsComponentRequestCorrectly() .WithProperty("tts", p => p.Is(tts)) .WithProperty("allowed_mentions", p => p.IsObject()) .WithProperty("components", p => p.IsArray()) + .WithProperty("enforce_nonce", p => p.Is(enforceNonce)) ) ) .Respond("application/json", SampleRepository.Samples[typeof(IMessage)]) @@ -1143,7 +1233,8 @@ public async Task PerformsComponentRequestCorrectly() isTTS: tts, embeds: embeds, allowedMentions: allowedMentions, - components: components + components: components, + enforceNonce: enforceNonce ); ResultAssert.Successful(result); @@ -1165,6 +1256,7 @@ public async Task PerformsFileUploadRequestCorrectly() var nonce = "aasda"; var tts = false; + var enforceNonce = true; var api = CreateAPI ( @@ -1178,21 +1270,26 @@ public async Task PerformsFileUploadRequestCorrectly() o => o .WithProperty("nonce", p => p.Is(nonce)) .WithProperty("tts", p => p.Is(tts)) - .WithProperty("attachments", p => p.IsArray + .WithProperty ( - a => a - .WithElement - ( - 0, - e => e.IsObject + "attachments", + p => p.IsArray + ( + a => a + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(0.ToString())) - .WithProperty("filename", ep => ep.Is(fileName)) - .WithProperty("description", ep => ep.Is(description)) + 0, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(0.ToString())) + .WithProperty("filename", ep => ep.Is(fileName)) + .WithProperty("description", ep => ep.Is(description)) + ) ) - ) - )) + ) + ) + .WithProperty("enforce_nonce", p => p.Is(enforceNonce)) ) ) .Respond("application/json", SampleRepository.Samples[typeof(IMessage)]) @@ -1203,7 +1300,8 @@ public async Task PerformsFileUploadRequestCorrectly() channelId, nonce: nonce, isTTS: tts, - attachments: new OneOf[] { new FileData(fileName, file, description) } + attachments: new OneOf[] { new FileData(fileName, file, description) }, + enforceNonce: enforceNonce ); ResultAssert.Successful(result); @@ -1229,6 +1327,7 @@ public async Task PerformsMultiFileUploadRequestCorrectly() var nonce = "aasda"; var tts = false; + var enforceNonce = true; var api = CreateAPI ( @@ -1243,32 +1342,37 @@ public async Task PerformsMultiFileUploadRequestCorrectly() o => o .WithProperty("nonce", p => p.Is(nonce)) .WithProperty("tts", p => p.Is(tts)) - .WithProperty("attachments", p => p.IsArray + .WithProperty ( - a => a - .WithElement - ( - 0, - e => e.IsObject + "attachments", + p => p.IsArray + ( + a => a + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(0.ToString())) - .WithProperty("filename", ep => ep.Is(fileName1)) - .WithProperty("description", ep => ep.Is(description1)) + 0, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(0.ToString())) + .WithProperty("filename", ep => ep.Is(fileName1)) + .WithProperty("description", ep => ep.Is(description1)) + ) ) - ) - .WithElement - ( - 1, - e => e.IsObject + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(1.ToString())) - .WithProperty("filename", ep => ep.Is(fileName2)) - .WithProperty("description", ep => ep.Is(description2)) + 1, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(1.ToString())) + .WithProperty("filename", ep => ep.Is(fileName2)) + .WithProperty("description", ep => ep.Is(description2)) + ) ) - ) - )) + ) + ) + .WithProperty("enforce_nonce", p => p.Is(enforceNonce)) ) ) .Respond("application/json", SampleRepository.Samples[typeof(IMessage)]) @@ -1283,7 +1387,8 @@ public async Task PerformsMultiFileUploadRequestCorrectly() { new FileData(fileName1, file1, description1), new FileData(fileName2, file2, description2) - } + }, + enforceNonce: enforceNonce ); ResultAssert.Successful(result); @@ -1306,6 +1411,7 @@ public async Task PerformsRetainingFileUploadRequestCorrectly() var nonce = "aasda"; var tts = false; + var enforceNonce = true; var api = CreateAPI ( @@ -1319,30 +1425,35 @@ public async Task PerformsRetainingFileUploadRequestCorrectly() o => o .WithProperty("nonce", p => p.Is(nonce)) .WithProperty("tts", p => p.Is(tts)) - .WithProperty("attachments", p => p.IsArray + .WithProperty ( - a => a - .WithElement - ( - 0, - e => e.IsObject + "attachments", + p => p.IsArray + ( + a => a + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(0.ToString())) - .WithProperty("filename", ep => ep.Is(fileName)) - .WithProperty("description", ep => ep.Is(description)) + 0, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(0.ToString())) + .WithProperty("filename", ep => ep.Is(fileName)) + .WithProperty("description", ep => ep.Is(description)) + ) ) - ) - .WithElement - ( - 1, - e => e.IsObject + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(999.ToString())) + 1, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(999.ToString())) + ) ) - ) - )) + ) + ) + .WithProperty("enforce_nonce", p => p.Is(enforceNonce)) ) ) .Respond("application/json", SampleRepository.Samples[typeof(IMessage)]) @@ -1357,7 +1468,8 @@ public async Task PerformsRetainingFileUploadRequestCorrectly() { new FileData(fileName, file, description), new PartialAttachment(DiscordSnowflake.New(999)) - } + }, + enforceNonce: enforceNonce ); ResultAssert.Successful(result); @@ -1895,21 +2007,25 @@ public async Task PerformsFileUploadRequestCorrectly() j => j.IsObject ( o => o - .WithProperty("attachments", p => p.IsArray + .WithProperty ( - a => a - .WithElement - ( - 0, - e => e.IsObject + "attachments", + p => p.IsArray + ( + a => a + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(0.ToString())) - .WithProperty("filename", ep => ep.Is(fileName)) - .WithProperty("description", ep => ep.Is(description)) + 0, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(0.ToString())) + .WithProperty("filename", ep => ep.Is(fileName)) + .WithProperty("description", ep => ep.Is(description)) + ) ) - ) - )) + ) + ) ) ) .Respond("application/json", SampleRepository.Samples[typeof(IMessage)]) @@ -1955,32 +2071,36 @@ public async Task PerformsMultiFileUploadRequestCorrectly() j => j.IsObject ( o => o - .WithProperty("attachments", p => p.IsArray + .WithProperty ( - a => a - .WithElement - ( - 0, - e => e.IsObject + "attachments", + p => p.IsArray + ( + a => a + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(0.ToString())) - .WithProperty("filename", ep => ep.Is(fileName1)) - .WithProperty("description", ep => ep.Is(description1)) + 0, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(0.ToString())) + .WithProperty("filename", ep => ep.Is(fileName1)) + .WithProperty("description", ep => ep.Is(description1)) + ) ) - ) - .WithElement - ( - 1, - e => e.IsObject + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(1.ToString())) - .WithProperty("filename", ep => ep.Is(fileName2)) - .WithProperty("description", ep => ep.Is(description2)) + 1, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(1.ToString())) + .WithProperty("filename", ep => ep.Is(fileName2)) + .WithProperty("description", ep => ep.Is(description2)) + ) ) - ) - )) + ) + ) ) ) .Respond("application/json", SampleRepository.Samples[typeof(IMessage)]) @@ -2026,30 +2146,34 @@ public async Task PerformsRetainingFileUploadRequestCorrectly() j => j.IsObject ( o => o - .WithProperty("attachments", p => p.IsArray + .WithProperty ( - a => a - .WithElement - ( - 0, - e => e.IsObject + "attachments", + p => p.IsArray + ( + a => a + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(0.ToString())) - .WithProperty("filename", ep => ep.Is(fileName)) - .WithProperty("description", ep => ep.Is(description)) + 0, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(0.ToString())) + .WithProperty("filename", ep => ep.Is(fileName)) + .WithProperty("description", ep => ep.Is(description)) + ) ) - ) - .WithElement - ( - 1, - e => e.IsObject + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(999.ToString())) + 1, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(999.ToString())) + ) ) - ) - )) + ) + ) ) ) .Respond("application/json", SampleRepository.Samples[typeof(IMessage)]) @@ -3069,25 +3193,33 @@ public async Task PerformsFileUploadRequestCorrectly() ( o => o .WithProperty("name", p => p.Is(name)) - .WithProperty("message", p => p.IsObject + .WithProperty ( - po => po - .WithProperty("attachments", poa => poa.IsArray - ( - a => a - .WithElement + "message", + p => p.IsObject + ( + po => po + .WithProperty + ( + "attachments", + poa => poa.IsArray ( - 0, - e => e.IsObject - ( - eo => eo - .WithProperty("id", ep => ep.Is(0.ToString())) - .WithProperty("filename", ep => ep.Is(fileName)) - .WithProperty("description", ep => ep.Is(description)) - ) + a => a + .WithElement + ( + 0, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(0.ToString())) + .WithProperty("filename", ep => ep.Is(fileName)) + .WithProperty("description", ep => ep.Is(description)) + ) + ) ) - )) - )) + ) + ) + ) ) ) .Respond("application/json", SampleRepository.Samples[typeof(IMessage)]) @@ -3134,36 +3266,44 @@ public async Task PerformsMultiFileUploadRequestCorrectly() ( o => o .WithProperty("name", p => p.Is(name)) - .WithProperty("message", p => p.IsObject + .WithProperty ( - po => po - .WithProperty("attachments", poa => poa.IsArray - ( - a => a - .WithElement - ( - 0, - e => e.IsObject - ( - eo => eo - .WithProperty("id", ep => ep.Is(0.ToString())) - .WithProperty("filename", ep => ep.Is(fileName1)) - .WithProperty("description", ep => ep.Is(description1)) - ) - ) - .WithElement + "message", + p => p.IsObject + ( + po => po + .WithProperty + ( + "attachments", + poa => poa.IsArray ( - 1, - e => e.IsObject - ( - eo => eo - .WithProperty("id", ep => ep.Is(1.ToString())) - .WithProperty("filename", ep => ep.Is(fileName2)) - .WithProperty("description", ep => ep.Is(description2)) - ) + a => a + .WithElement + ( + 0, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(0.ToString())) + .WithProperty("filename", ep => ep.Is(fileName1)) + .WithProperty("description", ep => ep.Is(description1)) + ) + ) + .WithElement + ( + 1, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(1.ToString())) + .WithProperty("filename", ep => ep.Is(fileName2)) + .WithProperty("description", ep => ep.Is(description2)) + ) + ) ) - )) - )) + ) + ) + ) ) ) .Respond("application/json", SampleRepository.Samples[typeof(IMessage)]) diff --git a/Tests/Remora.Discord.Rest.Tests/API/Guild/DiscordRestGuildAPITests.cs b/Tests/Remora.Discord.Rest.Tests/API/Guild/DiscordRestGuildAPITests.cs index c00cfcddf7..91a0fc33ed 100644 --- a/Tests/Remora.Discord.Rest.Tests/API/Guild/DiscordRestGuildAPITests.cs +++ b/Tests/Remora.Discord.Rest.Tests/API/Guild/DiscordRestGuildAPITests.cs @@ -660,6 +660,7 @@ public async Task PerformsTextRequestCorrectly() var parentID = DiscordSnowflake.New(1); var isNsfw = true; var defaultAutoArchiveDuration = AutoArchiveDuration.Day; + var defaultThreadRateLimitPerUser = 20; var reason = "test"; var api = CreateAPI @@ -685,6 +686,7 @@ public async Task PerformsTextRequestCorrectly() "default_auto_archive_duration", p => p.Is((int)defaultAutoArchiveDuration) ) + .WithProperty("default_thread_rate_limit_per_user", p => p.Is(defaultThreadRateLimitPerUser)) ) ) .Respond("application/json", SampleRepository.Samples[typeof(IChannel)]) @@ -701,6 +703,7 @@ public async Task PerformsTextRequestCorrectly() parentID, isNsfw, defaultAutoArchiveDuration, + defaultThreadRateLimitPerUser, reason ); @@ -723,6 +726,7 @@ public async Task PerformsAnnouncementRequestCorrectly() var parentID = DiscordSnowflake.New(1); var isNsfw = true; var defaultAutoArchiveDuration = AutoArchiveDuration.Day; + var defaultThreadRateLimitPerUser = 20; var reason = "test"; var api = CreateAPI @@ -747,6 +751,7 @@ public async Task PerformsAnnouncementRequestCorrectly() "default_auto_archive_duration", p => p.Is((int)defaultAutoArchiveDuration) ) + .WithProperty("default_thread_rate_limit_per_user", p => p.Is(defaultThreadRateLimitPerUser)) ) ) .Respond("application/json", SampleRepository.Samples[typeof(IChannel)]) @@ -762,6 +767,7 @@ public async Task PerformsAnnouncementRequestCorrectly() parentID, isNsfw, defaultAutoArchiveDuration, + defaultThreadRateLimitPerUser, reason ); @@ -779,6 +785,7 @@ public async Task PerformsForumRequestCorrectly() var name = "dd"; var type = ChannelType.GuildForum; var topic = "aaa"; + var rateLimitPerUser = 10; var position = 1; var permissionOverwrites = new List(); var parentID = DiscordSnowflake.New(1); @@ -788,6 +795,7 @@ public async Task PerformsForumRequestCorrectly() var availableTags = Array.Empty(); var defaultSortOrder = SortOrder.CreationDate; var defaultLayout = ForumLayout.GalleryView; + var defaultThreadRateLimitPerUser = 20; var reason = "test"; var api = CreateAPI @@ -803,6 +811,7 @@ public async Task PerformsForumRequestCorrectly() .WithProperty("name", p => p.Is(name)) .WithProperty("type", p => p.Is((int)type)) .WithProperty("topic", p => p.Is(topic)) + .WithProperty("rate_limit_per_user", p => p.Is(rateLimitPerUser)) .WithProperty("position", p => p.Is(position)) .WithProperty("permission_overwrites", p => p.IsArray(a => a.WithCount(0))) .WithProperty("parent_id", p => p.Is(parentID.ToString())) @@ -816,6 +825,7 @@ public async Task PerformsForumRequestCorrectly() .WithProperty("available_tags", p => p.IsArray(a => a.WithCount(0))) .WithProperty("default_sort_order", p => p.Is((int)defaultSortOrder)) .WithProperty("default_forum_layout", p => p.Is((int)defaultLayout)) + .WithProperty("default_thread_rate_limit_per_user", p => p.Is(defaultThreadRateLimitPerUser)) ) ) .Respond("application/json", SampleRepository.Samples[typeof(IChannel)]) @@ -826,6 +836,7 @@ public async Task PerformsForumRequestCorrectly() guildId, name, topic, + rateLimitPerUser, position, permissionOverwrites, parentID, @@ -835,6 +846,80 @@ public async Task PerformsForumRequestCorrectly() availableTags, defaultSortOrder, defaultLayout, + defaultThreadRateLimitPerUser, + reason + ); + + ResultAssert.Successful(result); + } + + /// + /// Tests whether the API method performs its request correctly. + /// + /// A representing the result of the asynchronous operation. + [Fact] + public async Task PerformsMediaRequestCorrectly() + { + var guildId = DiscordSnowflake.New(0); + var name = "dd"; + var type = ChannelType.GuildMedia; + var topic = "aaa"; + var rateLimitPerUser = 10; + var position = 1; + var permissionOverwrites = new List(); + var parentID = DiscordSnowflake.New(1); + var defaultAutoArchiveDuration = AutoArchiveDuration.Day; + var defaultReactionEmoji = new DefaultReaction(EmojiName: "booga"); + var availableTags = Array.Empty(); + var defaultSortOrder = SortOrder.CreationDate; + var defaultThreadRateLimitPerUser = 20; + var reason = "test"; + + var api = CreateAPI + ( + b => b + .Expect(HttpMethod.Post, $"{Constants.BaseURL}guilds/{guildId}/channels") + .WithHeaders(Constants.AuditLogHeaderName, reason) + .WithJson + ( + j => j.IsObject + ( + o => o + .WithProperty("name", p => p.Is(name)) + .WithProperty("type", p => p.Is((int)type)) + .WithProperty("topic", p => p.Is(topic)) + .WithProperty("rate_limit_per_user", p => p.Is(rateLimitPerUser)) + .WithProperty("position", p => p.Is(position)) + .WithProperty("permission_overwrites", p => p.IsArray(a => a.WithCount(0))) + .WithProperty("parent_id", p => p.Is(parentID.ToString())) + .WithProperty + ( + "default_auto_archive_duration", + p => p.Is((int)defaultAutoArchiveDuration) + ) + .WithProperty("default_reaction_emoji", p => p.IsObject()) + .WithProperty("available_tags", p => p.IsArray(a => a.WithCount(0))) + .WithProperty("default_sort_order", p => p.Is((int)defaultSortOrder)) + .WithProperty("default_thread_rate_limit_per_user", p => p.Is(defaultThreadRateLimitPerUser)) + ) + ) + .Respond("application/json", SampleRepository.Samples[typeof(IChannel)]) + ); + + var result = await api.CreateGuildMediaChannelAsync + ( + guildId, + name, + topic, + rateLimitPerUser, + position, + permissionOverwrites, + parentID, + defaultAutoArchiveDuration, + defaultReactionEmoji, + availableTags, + defaultSortOrder, + defaultThreadRateLimitPerUser, reason ); @@ -2005,6 +2090,61 @@ public async Task PerformsRequestCorrectly() } } + /// + /// Tests the method. + /// + public class BulkGuildBanAsync : RestAPITestBase + { + /// + /// Initializes a new instance of the class. + /// + /// The test fixture. + public BulkGuildBanAsync(RestAPITestFixture fixture) + : base(fixture) + { + } + + /// + /// Tests whether the API method performs its request correctly. + /// + /// A representing the result of the asynchronous operation. + [Fact] + public async Task PerformsRequestCorrectly() + { + var guildId = DiscordSnowflake.New(0); + var userIds = new[] { DiscordSnowflake.New(1) }; + var deleteMessageSeconds = 864000; + var reason = "ddd"; + + var api = CreateAPI + ( + b => b + .Expect(HttpMethod.Post, $"{Constants.BaseURL}guilds/{guildId}/bulk-ban") + .WithHeaders(Constants.AuditLogHeaderName, reason) + .WithJson + ( + j => j.IsObject + ( + o => o + .WithProperty("user_ids", p => p.IsArray(a => a.WithElement(0, e => e.Is(userIds[0])))) + .WithProperty("delete_message_seconds", p => p.Is(deleteMessageSeconds)) + ) + ) + .Respond("application/json", SampleRepository.Samples[typeof(IBulkBanResponse)]) + ); + + var result = await api.BulkGuildBanAsync + ( + guildId, + userIds, + deleteMessageSeconds, + reason + ); + + ResultAssert.Successful(result); + } + } + /// /// Tests the method. /// diff --git a/Tests/Remora.Discord.Rest.Tests/API/Interactions/DiscordRestInteractionAPITests.cs b/Tests/Remora.Discord.Rest.Tests/API/Interactions/DiscordRestInteractionAPITests.cs index 8bf6854df5..3e854bd5f4 100644 --- a/Tests/Remora.Discord.Rest.Tests/API/Interactions/DiscordRestInteractionAPITests.cs +++ b/Tests/Remora.Discord.Rest.Tests/API/Interactions/DiscordRestInteractionAPITests.cs @@ -427,21 +427,25 @@ public async Task PerformsFileUploadRequestCorrectly() j => j.IsObject ( o => o - .WithProperty("attachments", p => p.IsArray + .WithProperty ( - a => a - .WithElement - ( - 0, - e => e.IsObject + "attachments", + p => p.IsArray + ( + a => a + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(0.ToString())) - .WithProperty("filename", ep => ep.Is(fileName)) - .WithProperty("description", ep => ep.Is(description)) + 0, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(0.ToString())) + .WithProperty("filename", ep => ep.Is(fileName)) + .WithProperty("description", ep => ep.Is(description)) + ) ) - ) - )) + ) + ) ) ) .Respond("application/json", SampleRepository.Samples[typeof(IMessage)]) @@ -491,32 +495,36 @@ public async Task PerformsMultiFileUploadRequestCorrectly() j => j.IsObject ( o => o - .WithProperty("attachments", p => p.IsArray + .WithProperty ( - a => a - .WithElement - ( - 0, - e => e.IsObject + "attachments", + p => p.IsArray + ( + a => a + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(0.ToString())) - .WithProperty("filename", ep => ep.Is(fileName1)) - .WithProperty("description", ep => ep.Is(description1)) + 0, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(0.ToString())) + .WithProperty("filename", ep => ep.Is(fileName1)) + .WithProperty("description", ep => ep.Is(description1)) + ) ) - ) - .WithElement - ( - 1, - e => e.IsObject + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(1.ToString())) - .WithProperty("filename", ep => ep.Is(fileName2)) - .WithProperty("description", ep => ep.Is(description2)) + 1, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(1.ToString())) + .WithProperty("filename", ep => ep.Is(fileName2)) + .WithProperty("description", ep => ep.Is(description2)) + ) ) - ) - )) + ) + ) ) ) .Respond("application/json", SampleRepository.Samples[typeof(IMessage)]) @@ -566,30 +574,34 @@ public async Task PerformsRetainingFileUploadRequestCorrectly() j => j.IsObject ( o => o - .WithProperty("attachments", p => p.IsArray + .WithProperty ( - a => a - .WithElement - ( - 0, - e => e.IsObject + "attachments", + p => p.IsArray + ( + a => a + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(0.ToString())) - .WithProperty("filename", ep => ep.Is(fileName)) - .WithProperty("description", ep => ep.Is(description)) + 0, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(0.ToString())) + .WithProperty("filename", ep => ep.Is(fileName)) + .WithProperty("description", ep => ep.Is(description)) + ) ) - ) - .WithElement - ( - 1, - e => e.IsObject + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(999.ToString())) + 1, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(999.ToString())) + ) ) - ) - )) + ) + ) ) ) .Respond("application/json", SampleRepository.Samples[typeof(IMessage)]) @@ -786,21 +798,25 @@ public async Task PerformsFileUploadRequestCorrectly() j => j.IsObject ( o => o - .WithProperty("attachments", p => p.IsArray + .WithProperty ( - a => a - .WithElement - ( - 0, - e => e.IsObject + "attachments", + p => p.IsArray + ( + a => a + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(0.ToString())) - .WithProperty("filename", ep => ep.Is(fileName)) - .WithProperty("description", ep => ep.Is(description)) + 0, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(0.ToString())) + .WithProperty("filename", ep => ep.Is(fileName)) + .WithProperty("description", ep => ep.Is(description)) + ) ) - ) - )) + ) + ) ) ) .Respond("application/json", SampleRepository.Samples[typeof(IMessage)]) @@ -846,32 +862,36 @@ public async Task PerformsMultiFileUploadRequestCorrectly() j => j.IsObject ( o => o - .WithProperty("attachments", p => p.IsArray + .WithProperty ( - a => a - .WithElement - ( - 0, - e => e.IsObject + "attachments", + p => p.IsArray + ( + a => a + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(0.ToString())) - .WithProperty("filename", ep => ep.Is(fileName1)) - .WithProperty("description", ep => ep.Is(description1)) + 0, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(0.ToString())) + .WithProperty("filename", ep => ep.Is(fileName1)) + .WithProperty("description", ep => ep.Is(description1)) + ) ) - ) - .WithElement - ( - 1, - e => e.IsObject + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(1.ToString())) - .WithProperty("filename", ep => ep.Is(fileName2)) - .WithProperty("description", ep => ep.Is(description2)) + 1, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(1.ToString())) + .WithProperty("filename", ep => ep.Is(fileName2)) + .WithProperty("description", ep => ep.Is(description2)) + ) ) - ) - )) + ) + ) ) ) .Respond("application/json", SampleRepository.Samples[typeof(IMessage)]) @@ -917,30 +937,34 @@ public async Task PerformsRetainingFileUploadRequestCorrectly() j => j.IsObject ( o => o - .WithProperty("attachments", p => p.IsArray + .WithProperty ( - a => a - .WithElement - ( - 0, - e => e.IsObject + "attachments", + p => p.IsArray + ( + a => a + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(0.ToString())) - .WithProperty("filename", ep => ep.Is(fileName)) - .WithProperty("description", ep => ep.Is(description)) + 0, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(0.ToString())) + .WithProperty("filename", ep => ep.Is(fileName)) + .WithProperty("description", ep => ep.Is(description)) + ) ) - ) - .WithElement - ( - 1, - e => e.IsObject + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(999.ToString())) + 1, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(999.ToString())) + ) ) - ) - )) + ) + ) ) ) .Respond("application/json", SampleRepository.Samples[typeof(IMessage)]) @@ -1105,21 +1129,25 @@ public async Task PerformsFileUploadRequestCorrectly() j => j.IsObject ( o => o - .WithProperty("attachments", p => p.IsArray + .WithProperty ( - a => a - .WithElement - ( - 0, - e => e.IsObject + "attachments", + p => p.IsArray + ( + a => a + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(0.ToString())) - .WithProperty("filename", ep => ep.Is(fileName)) - .WithProperty("description", ep => ep.Is(description)) + 0, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(0.ToString())) + .WithProperty("filename", ep => ep.Is(fileName)) + .WithProperty("description", ep => ep.Is(description)) + ) ) - ) - )) + ) + ) ) ) .Respond("application/json", SampleRepository.Samples[typeof(IMessage)]) @@ -1171,32 +1199,36 @@ public async Task PerformsMultiFileUploadRequestCorrectly() j => j.IsObject ( o => o - .WithProperty("attachments", p => p.IsArray + .WithProperty ( - a => a - .WithElement - ( - 0, - e => e.IsObject + "attachments", + p => p.IsArray + ( + a => a + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(0.ToString())) - .WithProperty("filename", ep => ep.Is(fileName1)) - .WithProperty("description", ep => ep.Is(description1)) + 0, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(0.ToString())) + .WithProperty("filename", ep => ep.Is(fileName1)) + .WithProperty("description", ep => ep.Is(description1)) + ) ) - ) - .WithElement - ( - 1, - e => e.IsObject + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(1.ToString())) - .WithProperty("filename", ep => ep.Is(fileName2)) - .WithProperty("description", ep => ep.Is(description2)) + 1, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(1.ToString())) + .WithProperty("filename", ep => ep.Is(fileName2)) + .WithProperty("description", ep => ep.Is(description2)) + ) ) - ) - )) + ) + ) ) ) .Respond("application/json", SampleRepository.Samples[typeof(IMessage)]) @@ -1248,30 +1280,34 @@ public async Task PerformsRetainingFileUploadRequestCorrectly() j => j.IsObject ( o => o - .WithProperty("attachments", p => p.IsArray + .WithProperty ( - a => a - .WithElement - ( - 0, - e => e.IsObject + "attachments", + p => p.IsArray + ( + a => a + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(0.ToString())) - .WithProperty("filename", ep => ep.Is(fileName)) - .WithProperty("description", ep => ep.Is(description)) + 0, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(0.ToString())) + .WithProperty("filename", ep => ep.Is(fileName)) + .WithProperty("description", ep => ep.Is(description)) + ) ) - ) - .WithElement - ( - 1, - e => e.IsObject + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(999.ToString())) + 1, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(999.ToString())) + ) ) - ) - )) + ) + ) ) ) .Respond("application/json", SampleRepository.Samples[typeof(IMessage)]) diff --git a/Tests/Remora.Discord.Rest.Tests/API/Monetization/DiscordRestMonetizationAPITests.cs b/Tests/Remora.Discord.Rest.Tests/API/Monetization/DiscordRestMonetizationAPITests.cs new file mode 100644 index 0000000000..9bf890ac46 --- /dev/null +++ b/Tests/Remora.Discord.Rest.Tests/API/Monetization/DiscordRestMonetizationAPITests.cs @@ -0,0 +1,271 @@ +// +// DiscordRestMonetizationAPITests.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Remora.Discord.API; +using Remora.Discord.API.Abstractions.Objects; +using Remora.Discord.API.Abstractions.Rest; +using Remora.Discord.Rest.API; +using Remora.Discord.Rest.Tests.Extensions; +using Remora.Discord.Rest.Tests.TestBases; +using Remora.Discord.Tests; +using Remora.Rest.Xunit.Extensions; +using RichardSzalay.MockHttp; +using Xunit; + +namespace Remora.Discord.Rest.Tests.API.Monetization; + +/// +/// Tests the class. +/// +public class DiscordRestMonetizationAPITests +{ + /// + /// Tests the method. + /// + public class ListEntitlementsAsync : RestAPITestBase + { + /// + /// Initializes a new instance of the class. + /// + /// The test fixture. + public ListEntitlementsAsync(RestAPITestFixture fixture) + : base(fixture) + { + } + + /// + /// Tests whether the API method performs its request correctly. + /// + /// A representing the result of the asynchronous operation. + [Fact] + public async Task PerformsRequestCorrectly() + { + var applicationID = DiscordSnowflake.New(1); + var userID = DiscordSnowflake.New(2); + var skuIDs = new[] { DiscordSnowflake.New(3), DiscordSnowflake.New(4) }; + var before = DiscordSnowflake.New(5); + var after = DiscordSnowflake.New(6); + var limit = 1; + var guildID = DiscordSnowflake.New(7); + var excludeEnded = true; + + var api = CreateAPI + ( + b => b + .Expect(HttpMethod.Get, $"{Constants.BaseURL}applications/{applicationID}/entitlements") + .WithExactQueryString + ( + new KeyValuePair[] + { + new("user_id", userID.ToString()), + new("sku_ids", string.Join(',', skuIDs.Select(id => id.ToString()))), + new("before", before.ToString()), + new("after", after.ToString()), + new("limit", limit.ToString()), + new("guild_id", guildID.ToString()), + new("exclude_ended", excludeEnded.ToString()) + } + ) + .Respond("application/json", "[ ]") + ); + + var result = await api.ListEntitlementsAsync + ( + applicationID, + userID, + skuIDs, + before, + after, + limit, + guildID, + excludeEnded + ); + + ResultAssert.Successful(result); + } + } + + /// + /// Tests the method. + /// + public class ConsumeEntitlementAsync : RestAPITestBase + { + /// + /// Initializes a new instance of the class. + /// + /// The test fixture. + public ConsumeEntitlementAsync(RestAPITestFixture fixture) + : base(fixture) + { + } + + /// + /// Tests whether the API method performs its request correctly. + /// + /// A representing the result of the asynchronous operation. + [Fact] + public async Task PerformsRequestCorrectly() + { + var applicationID = DiscordSnowflake.New(1); + var entitlementID = DiscordSnowflake.New(2); + + var api = CreateAPI + ( + b => b + .Expect(HttpMethod.Post, $"{Constants.BaseURL}applications/{applicationID}/entitlements/{entitlementID}/consume") + .Respond("application/json", "[ ]") + ); + + var result = await api.ConsumeEntitlementAsync + ( + applicationID, + entitlementID + ); + + ResultAssert.Successful(result); + } + } + + /// + /// Tests the method. + /// + public class CreateTestEntitlementAsync : RestAPITestBase + { + /// + /// Initializes a new instance of the class. + /// + /// The test fixture. + public CreateTestEntitlementAsync(RestAPITestFixture fixture) + : base(fixture) + { + } + + /// + /// Tests whether the API method performs its request correctly. + /// + /// A representing the result of the asynchronous operation. + [Fact] + public async Task PerformsRequestCorrectly() + { + var applicationID = DiscordSnowflake.New(1); + var skuID = DiscordSnowflake.New(2); + var ownerID = DiscordSnowflake.New(3); + var ownerType = EntitlementOwnerType.Guild; + + var api = CreateAPI + ( + b => b + .Expect(HttpMethod.Post, $"{Constants.BaseURL}applications/{applicationID}/entitlements") + .WithJson + ( + j => j.IsObject + ( + o => o + .WithProperty("sku_id", p => p.Is(skuID)) + .WithProperty("owner_id", p => p.Is(ownerID)) + .WithProperty("owner_type", p => p.Is((int)ownerType)) + ) + ) + .Respond("application/json", SampleRepository.Samples[typeof(IEntitlement)]) + ); + + var result = await api.CreateTestEntitlementAsync(applicationID, skuID, ownerID, ownerType); + ResultAssert.Successful(result); + } + } + + /// + /// Tests the method. + /// + public class DeleteTestEntitlementAsync : RestAPITestBase + { + /// + /// Initializes a new instance of the class. + /// + /// The test fixture. + public DeleteTestEntitlementAsync(RestAPITestFixture fixture) + : base(fixture) + { + } + + /// + /// Tests whether the API method performs its request correctly. + /// + /// A representing the result of the asynchronous operation. + [Fact] + public async Task PerformsRequestCorrectly() + { + var applicationID = DiscordSnowflake.New(1); + var entitlementID = DiscordSnowflake.New(2); + + var api = CreateAPI + ( + b => b + .Expect(HttpMethod.Delete, $"{Constants.BaseURL}applications/{applicationID}/entitlements/{entitlementID}") + .Respond(HttpStatusCode.NoContent) + ); + + var result = await api.DeleteTestEntitlementAsync(applicationID, entitlementID); + ResultAssert.Successful(result); + } + } + + /// + /// Tests the method. + /// + public class ListSKUsAsync : RestAPITestBase + { + /// + /// Initializes a new instance of the class. + /// + /// The test fixture. + public ListSKUsAsync(RestAPITestFixture fixture) + : base(fixture) + { + } + + /// + /// Tests whether the API method performs its request correctly. + /// + /// A representing the result of the asynchronous operation. + [Fact] + public async Task PerformsRequestCorrectly() + { + var applicationID = DiscordSnowflake.New(1); + + var api = CreateAPI + ( + b => b + .Expect(HttpMethod.Get, $"{Constants.BaseURL}applications/{applicationID}/skus") + .Respond("application/json", "[ ]") + ); + + var result = await api.ListSKUsAsync(applicationID); + ResultAssert.Successful(result); + } + } +} diff --git a/Tests/Remora.Discord.Rest.Tests/API/StageInstances/DiscordRestStageInstanceAPITests.cs b/Tests/Remora.Discord.Rest.Tests/API/StageInstances/DiscordRestStageInstanceAPITests.cs index dff71ea36d..ad44a3f5dc 100644 --- a/Tests/Remora.Discord.Rest.Tests/API/StageInstances/DiscordRestStageInstanceAPITests.cs +++ b/Tests/Remora.Discord.Rest.Tests/API/StageInstances/DiscordRestStageInstanceAPITests.cs @@ -27,6 +27,7 @@ using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Rest; using Remora.Discord.Rest.API; +using Remora.Discord.Rest.Tests.Extensions; using Remora.Discord.Rest.Tests.TestBases; using Remora.Discord.Tests; using Remora.Rest.Xunit.Extensions; @@ -65,6 +66,7 @@ public async Task PerformsRequestCorrectly() var topic = "aa"; var privacyLevel = StagePrivacyLevel.GuildOnly; var sendNotification = true; + var guildScheduledEventID = DiscordSnowflake.New(2); var reason = "test"; var api = CreateAPI @@ -77,15 +79,26 @@ public async Task PerformsRequestCorrectly() json => json.IsObject ( o => o - .WithProperty("channel_id", p => p.Is(channelID.ToString())) + .WithProperty("channel_id", p => p.Is(channelID)) .WithProperty("topic", p => p.Is(topic)) .WithProperty("privacy_level", p => p.Is((int)privacyLevel)) + .WithProperty("send_start_notification", p => p.Is(sendNotification)) + .WithProperty("guild_scheduled_event_id", p => p.Is(guildScheduledEventID)) ) ) .Respond("application/json", SampleRepository.Samples[typeof(IStageInstance)]) ); - var result = await api.CreateStageInstanceAsync(channelID, topic, privacyLevel, sendNotification, reason); + var result = await api.CreateStageInstanceAsync + ( + channelID, + topic, + privacyLevel, + sendNotification, + guildScheduledEventID, + reason + ); + ResultAssert.Successful(result); } } diff --git a/Tests/Remora.Discord.Rest.Tests/API/Users/DiscordRestUserAPITests.cs b/Tests/Remora.Discord.Rest.Tests/API/Users/DiscordRestUserAPITests.cs index 1d50d93c0d..c5c2da370d 100644 --- a/Tests/Remora.Discord.Rest.Tests/API/Users/DiscordRestUserAPITests.cs +++ b/Tests/Remora.Discord.Rest.Tests/API/Users/DiscordRestUserAPITests.cs @@ -450,15 +450,15 @@ public async Task PerformsRequestCorrectly() } /// - /// Tests the method. + /// Tests the method. /// - public class GetUserConnectionsAsync : RestAPITestBase + public class GetCurrentUserConnectionsAsync : RestAPITestBase { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The test fixture. - public GetUserConnectionsAsync(RestAPITestFixture fixture) + public GetCurrentUserConnectionsAsync(RestAPITestFixture fixture) : base(fixture) { } @@ -477,21 +477,21 @@ public async Task PerformsRequestCorrectly() .Respond("application/json", "[]") ); - var result = await api.GetUserConnectionsAsync(); + var result = await api.GetCurrentUserConnectionsAsync(); ResultAssert.Successful(result); } } /// - /// Tests the method. + /// Tests the method. /// - public class GetUserApplicationRoleConnectionAsync : RestAPITestBase + public class GetCurrentUserApplicationRoleConnectionAsync : RestAPITestBase { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The test fixture. - public GetUserApplicationRoleConnectionAsync(RestAPITestFixture fixture) + public GetCurrentUserApplicationRoleConnectionAsync(RestAPITestFixture fixture) : base(fixture) { } @@ -516,21 +516,21 @@ public async Task PerformsRequestCorrectly() .Respond("application/json", SampleRepository.Samples[typeof(IApplicationRoleConnection)]) ); - var result = await api.GetUserApplicationRoleConnectionAsync(applicationID); + var result = await api.GetCurrentUserApplicationRoleConnectionAsync(applicationID); ResultAssert.Successful(result); } } /// - /// Tests the method. + /// Tests the method. /// - public class UpdateUserApplicationRoleConnectionAsync : RestAPITestBase + public class UpdateCurrentUserApplicationRoleConnectionAsync : RestAPITestBase { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The test fixture. - public UpdateUserApplicationRoleConnectionAsync(RestAPITestFixture fixture) + public UpdateCurrentUserApplicationRoleConnectionAsync(RestAPITestFixture fixture) : base(fixture) { } @@ -585,7 +585,7 @@ public async Task PerformsRequestCorrectly() .Respond("application/json", SampleRepository.Samples[typeof(IApplicationRoleConnection)]) ); - var result = await api.UpdateUserApplicationRoleConnectionAsync + var result = await api.UpdateCurrentUserApplicationRoleConnectionAsync ( applicationID, platformName, @@ -621,7 +621,7 @@ public async Task ReturnsUnsuccessfulIfPlatformNameIsTooLong() .Respond("application/json", SampleRepository.Samples[typeof(IApplicationRoleConnection)]) ); - var result = await api.UpdateUserApplicationRoleConnectionAsync + var result = await api.UpdateCurrentUserApplicationRoleConnectionAsync ( applicationID, platformName, @@ -657,7 +657,7 @@ public async Task ReturnsUnsuccessfulIfPlatformUsernameIsTooLong() .Respond("application/json", SampleRepository.Samples[typeof(IApplicationRoleConnection)]) ); - var result = await api.UpdateUserApplicationRoleConnectionAsync + var result = await api.UpdateCurrentUserApplicationRoleConnectionAsync ( applicationID, platformName, @@ -693,7 +693,7 @@ public async Task ReturnsUnsuccessfulIfMetadataValueIsTooLong() .Respond("application/json", SampleRepository.Samples[typeof(IApplicationRoleConnection)]) ); - var result = await api.UpdateUserApplicationRoleConnectionAsync + var result = await api.UpdateCurrentUserApplicationRoleConnectionAsync ( applicationID, platformName, diff --git a/Tests/Remora.Discord.Rest.Tests/API/Webhooks/DiscordRestWebhookAPITests.cs b/Tests/Remora.Discord.Rest.Tests/API/Webhooks/DiscordRestWebhookAPITests.cs index a0c1608e3b..b74083fd1b 100644 --- a/Tests/Remora.Discord.Rest.Tests/API/Webhooks/DiscordRestWebhookAPITests.cs +++ b/Tests/Remora.Discord.Rest.Tests/API/Webhooks/DiscordRestWebhookAPITests.cs @@ -35,6 +35,7 @@ using Remora.Discord.Rest.Tests.Extensions; using Remora.Discord.Rest.Tests.TestBases; using Remora.Discord.Tests; +using Remora.Rest.Core; using Remora.Rest.Xunit.Extensions; using RichardSzalay.MockHttp; using Xunit; @@ -753,6 +754,7 @@ public async Task PerformsNormalRequestCorrectly() var allowedMentions = new AllowedMentions(); var components = new List(); var flags = MessageFlags.SuppressEmbeds; + var tags = new List(); var api = CreateAPI ( @@ -771,6 +773,7 @@ public async Task PerformsNormalRequestCorrectly() .WithProperty("allowed_mentions", p => p.IsObject()) .WithProperty("components", p => p.IsArray()) .WithProperty("flags", p => p.Is((int)flags)) + .WithProperty("applied_tags", p => p.IsArray()) ) ) .Respond("application/json", SampleRepository.Samples[typeof(IMessage)]) @@ -787,7 +790,8 @@ public async Task PerformsNormalRequestCorrectly() tts, allowedMentions: allowedMentions, components: components, - flags: flags + flags: flags, + appliedTags: tags ); ResultAssert.Successful(result); @@ -995,21 +999,25 @@ public async Task PerformsFileUploadRequestCorrectly() j => j.IsObject ( o => o - .WithProperty("attachments", p => p.IsArray + .WithProperty ( - a => a - .WithElement - ( - 0, - e => e.IsObject + "attachments", + p => p.IsArray + ( + a => a + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(0.ToString())) - .WithProperty("filename", ep => ep.Is(fileName)) - .WithProperty("description", ep => ep.Is(description)) + 0, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(0.ToString())) + .WithProperty("filename", ep => ep.Is(fileName)) + .WithProperty("description", ep => ep.Is(description)) + ) ) - ) - )) + ) + ) ) ) .Respond("application/json", SampleRepository.Samples[typeof(IMessage)]) @@ -1055,32 +1063,36 @@ public async Task PerformsMultiFileUploadRequestCorrectly() j => j.IsObject ( o => o - .WithProperty("attachments", p => p.IsArray + .WithProperty ( - a => a - .WithElement - ( - 0, - e => e.IsObject + "attachments", + p => p.IsArray + ( + a => a + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(0.ToString())) - .WithProperty("filename", ep => ep.Is(fileName1)) - .WithProperty("description", ep => ep.Is(description1)) + 0, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(0.ToString())) + .WithProperty("filename", ep => ep.Is(fileName1)) + .WithProperty("description", ep => ep.Is(description1)) + ) ) - ) - .WithElement - ( - 1, - e => e.IsObject + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(1.ToString())) - .WithProperty("filename", ep => ep.Is(fileName2)) - .WithProperty("description", ep => ep.Is(description2)) + 1, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(1.ToString())) + .WithProperty("filename", ep => ep.Is(fileName2)) + .WithProperty("description", ep => ep.Is(description2)) + ) ) - ) - )) + ) + ) ) ) .Respond("application/json", SampleRepository.Samples[typeof(IMessage)]) @@ -1126,30 +1138,34 @@ public async Task PerformsRetainingFileUploadRequestCorrectly() j => j.IsObject ( o => o - .WithProperty("attachments", p => p.IsArray + .WithProperty ( - a => a - .WithElement - ( - 0, - e => e.IsObject + "attachments", + p => p.IsArray + ( + a => a + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(0.ToString())) - .WithProperty("filename", ep => ep.Is(fileName)) - .WithProperty("description", ep => ep.Is(description)) + 0, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(0.ToString())) + .WithProperty("filename", ep => ep.Is(fileName)) + .WithProperty("description", ep => ep.Is(description)) + ) ) - ) - .WithElement - ( - 1, - e => e.IsObject + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(999.ToString())) + 1, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(999.ToString())) + ) ) - ) - )) + ) + ) ) ) .Respond("application/json", SampleRepository.Samples[typeof(IMessage)]) @@ -1348,21 +1364,25 @@ public async Task PerformsFileUploadRequestCorrectly() j => j.IsObject ( o => o - .WithProperty("attachments", p => p.IsArray + .WithProperty ( - a => a - .WithElement - ( - 0, - e => e.IsObject + "attachments", + p => p.IsArray + ( + a => a + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(0.ToString())) - .WithProperty("filename", ep => ep.Is(fileName)) - .WithProperty("description", ep => ep.Is(description)) + 0, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(0.ToString())) + .WithProperty("filename", ep => ep.Is(fileName)) + .WithProperty("description", ep => ep.Is(description)) + ) ) - ) - )) + ) + ) ) ) .Respond("application/json", SampleRepository.Samples[typeof(IMessage)]) @@ -1414,32 +1434,36 @@ public async Task PerformsMultiFileUploadRequestCorrectly() j => j.IsObject ( o => o - .WithProperty("attachments", p => p.IsArray + .WithProperty ( - a => a - .WithElement - ( - 0, - e => e.IsObject + "attachments", + p => p.IsArray + ( + a => a + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(0.ToString())) - .WithProperty("filename", ep => ep.Is(fileName1)) - .WithProperty("description", ep => ep.Is(description1)) + 0, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(0.ToString())) + .WithProperty("filename", ep => ep.Is(fileName1)) + .WithProperty("description", ep => ep.Is(description1)) + ) ) - ) - .WithElement - ( - 1, - e => e.IsObject + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(1.ToString())) - .WithProperty("filename", ep => ep.Is(fileName2)) - .WithProperty("description", ep => ep.Is(description2)) + 1, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(1.ToString())) + .WithProperty("filename", ep => ep.Is(fileName2)) + .WithProperty("description", ep => ep.Is(description2)) + ) ) - ) - )) + ) + ) ) ) .Respond("application/json", SampleRepository.Samples[typeof(IMessage)]) @@ -1491,30 +1515,34 @@ public async Task PerformsRetainingFileUploadRequestCorrectly() j => j.IsObject ( o => o - .WithProperty("attachments", p => p.IsArray + .WithProperty ( - a => a - .WithElement - ( - 0, - e => e.IsObject + "attachments", + p => p.IsArray + ( + a => a + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(0.ToString())) - .WithProperty("filename", ep => ep.Is(fileName)) - .WithProperty("description", ep => ep.Is(description)) + 0, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(0.ToString())) + .WithProperty("filename", ep => ep.Is(fileName)) + .WithProperty("description", ep => ep.Is(description)) + ) ) - ) - .WithElement - ( - 1, - e => e.IsObject + .WithElement ( - eo => eo - .WithProperty("id", ep => ep.Is(999.ToString())) + 1, + e => e.IsObject + ( + eo => eo + .WithProperty("id", ep => ep.Is(999.ToString())) + ) ) - ) - )) + ) + ) ) ) .Respond("application/json", SampleRepository.Samples[typeof(IMessage)]) diff --git a/Tests/Remora.Discord.Rest.Tests/Extensions/JsonElementMatcherBuilderExtensions.cs b/Tests/Remora.Discord.Rest.Tests/Extensions/JsonElementMatcherBuilderExtensions.cs new file mode 100644 index 0000000000..847fa941f2 --- /dev/null +++ b/Tests/Remora.Discord.Rest.Tests/Extensions/JsonElementMatcherBuilderExtensions.cs @@ -0,0 +1,41 @@ +// +// JsonElementMatcherBuilderExtensions.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Remora.Rest.Core; +using Remora.Rest.Xunit.Json; + +namespace Remora.Discord.Rest.Tests.Extensions; + +/// +/// Defines extension methods for the class. +/// +public static class JsonElementMatcherBuilderExtensions +{ + /// + /// Adds a requirement that the element should be a snowflake with the given value. + /// + /// The builder. + /// The value. + /// The builder, with the added requirement. + public static JsonElementMatcherBuilder Is(this JsonElementMatcherBuilder builder, Snowflake value) + => builder.Is(value.ToString()); +} diff --git a/Tests/Remora.Discord.Rest.Tests/TestBases/RestAPITestBase.cs b/Tests/Remora.Discord.Rest.Tests/TestBases/RestAPITestBase.cs index 814e200018..7dfd406f83 100644 --- a/Tests/Remora.Discord.Rest.Tests/TestBases/RestAPITestBase.cs +++ b/Tests/Remora.Discord.Rest.Tests/TestBases/RestAPITestBase.cs @@ -143,6 +143,4 @@ public RestAPITestFixture() /// Defines a test collection for JSON-backed type tests. /// [CollectionDefinition("REST API tests")] -public class RestAPITestCollection : ICollectionFixture -{ -} +public class RestAPITestCollection : ICollectionFixture; diff --git a/Tests/Remora.Discord.Tests/Remora.Discord.Tests.csproj b/Tests/Remora.Discord.Tests/Remora.Discord.Tests.csproj index 9bffd781c5..c4c85fee21 100644 --- a/Tests/Remora.Discord.Tests/Remora.Discord.Tests.csproj +++ b/Tests/Remora.Discord.Tests/Remora.Discord.Tests.csproj @@ -10,5 +10,4 @@ PreserveNewest - diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/CHANNEL_CREATE/CHANNEL_CREATE.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/CHANNEL_CREATE/CHANNEL_CREATE.json index a58d166fc5..f59972f523 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/CHANNEL_CREATE/CHANNEL_CREATE.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/CHANNEL_CREATE/CHANNEL_CREATE.json @@ -26,7 +26,6 @@ { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" } diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/CHANNEL_CREATE/CHANNEL_CREATE.nulls.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/CHANNEL_CREATE/CHANNEL_CREATE.nulls.json index ea1d75dc46..44f85374d8 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/CHANNEL_CREATE/CHANNEL_CREATE.nulls.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/CHANNEL_CREATE/CHANNEL_CREATE.nulls.json @@ -26,7 +26,6 @@ { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" } diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/CHANNEL_DELETE/CHANNEL_DELETE.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/CHANNEL_DELETE/CHANNEL_DELETE.json index 94834b7688..d1ab00208f 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/CHANNEL_DELETE/CHANNEL_DELETE.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/CHANNEL_DELETE/CHANNEL_DELETE.json @@ -26,7 +26,6 @@ { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" } diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/CHANNEL_DELETE/CHANNEL_DELETE.nulls.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/CHANNEL_DELETE/CHANNEL_DELETE.nulls.json index 50cabc1026..62f953c103 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/CHANNEL_DELETE/CHANNEL_DELETE.nulls.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/CHANNEL_DELETE/CHANNEL_DELETE.nulls.json @@ -26,7 +26,6 @@ { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" } diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/CHANNEL_UPDATE/CHANNEL_UPDATE.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/CHANNEL_UPDATE/CHANNEL_UPDATE.json index 8070f73d75..a0acb989dd 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/CHANNEL_UPDATE/CHANNEL_UPDATE.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/CHANNEL_UPDATE/CHANNEL_UPDATE.json @@ -26,7 +26,6 @@ { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" } diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/CHANNEL_UPDATE/CHANNEL_UPDATE.nulls.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/CHANNEL_UPDATE/CHANNEL_UPDATE.nulls.json index 336a379d4a..6ac8aa2900 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/CHANNEL_UPDATE/CHANNEL_UPDATE.nulls.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/CHANNEL_UPDATE/CHANNEL_UPDATE.nulls.json @@ -26,7 +26,6 @@ { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" } diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/ENTITLEMENT_CREATE/ENTITLEMENT_CREATE.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/ENTITLEMENT_CREATE/ENTITLEMENT_CREATE.json new file mode 100644 index 0000000000..dcd21fa6e5 --- /dev/null +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/ENTITLEMENT_CREATE/ENTITLEMENT_CREATE.json @@ -0,0 +1,12 @@ +{ + "t": "ENTITLEMENT_CREATE", + "s": 2, + "op": 0, + "d": { + "id": "999999999999999999", + "sku_id": "999999999999999999", + "application_id": "999999999999999999", + "type": 8, + "deleted": false + } +} diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/ENTITLEMENT_DELETE/ENTITLEMENT_DELETE.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/ENTITLEMENT_DELETE/ENTITLEMENT_DELETE.json new file mode 100644 index 0000000000..c9e398639d --- /dev/null +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/ENTITLEMENT_DELETE/ENTITLEMENT_DELETE.json @@ -0,0 +1,12 @@ +{ + "t": "ENTITLEMENT_DELETE", + "s": 2, + "op": 0, + "d": { + "id": "999999999999999999", + "sku_id": "999999999999999999", + "application_id": "999999999999999999", + "type": 8, + "deleted": false + } +} diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/ENTITLEMENT_UPDATE/ENTITLEMENT_UPDATE.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/ENTITLEMENT_UPDATE/ENTITLEMENT_UPDATE.json new file mode 100644 index 0000000000..e1ed5bcd49 --- /dev/null +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/ENTITLEMENT_UPDATE/ENTITLEMENT_UPDATE.json @@ -0,0 +1,12 @@ +{ + "t": "ENTITLEMENT_UPDATE", + "s": 2, + "op": 0, + "d": { + "id": "999999999999999999", + "sku_id": "999999999999999999", + "application_id": "999999999999999999", + "type": 8, + "deleted": false + } +} diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_BAN_ADD/GUILD_BAN_ADD.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_BAN_ADD/GUILD_BAN_ADD.json index 1a6e55a2f4..08e072ca1d 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_BAN_ADD/GUILD_BAN_ADD.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_BAN_ADD/GUILD_BAN_ADD.json @@ -8,7 +8,6 @@ "id": "999999999999999999", "username": "none", "discriminator": "9999", - "global_name": "none", "avatar": "68b329da9893e34099c7d8ad5cb9c940" } } diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_BAN_REMOVE/GUILD_BAN_REMOVE.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_BAN_REMOVE/GUILD_BAN_REMOVE.json index 36e5c610b4..fa60a045ff 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_BAN_REMOVE/GUILD_BAN_REMOVE.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_BAN_REMOVE/GUILD_BAN_REMOVE.json @@ -8,7 +8,6 @@ "id": "999999999999999999", "username": "none", "discriminator": "9999", - "global_name": "none", "avatar": "68b329da9893e34099c7d8ad5cb9c940" } } diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_MEMBER_REMOVE/GUILD_MEMBER_REMOVE.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_MEMBER_REMOVE/GUILD_MEMBER_REMOVE.json index c11aa40f43..b3b5aeca70 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_MEMBER_REMOVE/GUILD_MEMBER_REMOVE.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_MEMBER_REMOVE/GUILD_MEMBER_REMOVE.json @@ -7,7 +7,6 @@ "id": "999999999999999999", "username": "none", "discriminator": "9999", - "global_name": "none", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, "guild_id": "999999999999999999" diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_MEMBER_UPDATE/GUILD_MEMBER_UPDATE.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_MEMBER_UPDATE/GUILD_MEMBER_UPDATE.json index 6d68b9dd14..2953e64378 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_MEMBER_UPDATE/GUILD_MEMBER_UPDATE.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_MEMBER_UPDATE/GUILD_MEMBER_UPDATE.json @@ -11,7 +11,6 @@ "id": "999999999999999999", "username": "none", "discriminator": "9999", - "global_name": "none", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, "nick": "none", diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_MEMBER_UPDATE/GUILD_MEMBER_UPDATE.nulls.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_MEMBER_UPDATE/GUILD_MEMBER_UPDATE.nulls.json index 8e4edfccda..5c2a5c085d 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_MEMBER_UPDATE/GUILD_MEMBER_UPDATE.nulls.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_MEMBER_UPDATE/GUILD_MEMBER_UPDATE.nulls.json @@ -11,7 +11,6 @@ "id": "999999999999999999", "username": "none", "discriminator": "9999", - "global_name": "none", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, "nick": null, diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_MEMBER_UPDATE/GUILD_MEMBER_UPDATE.optionals.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_MEMBER_UPDATE/GUILD_MEMBER_UPDATE.optionals.json index c6af229bd0..da7cc8e757 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_MEMBER_UPDATE/GUILD_MEMBER_UPDATE.optionals.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_MEMBER_UPDATE/GUILD_MEMBER_UPDATE.optionals.json @@ -11,7 +11,6 @@ "id": "999999999999999999", "username": "none", "discriminator": "9999", - "global_name": "none", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, "avatar": "68b329da9893e34099c7d8ad5cb9c940", diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_SCHEDULED_EVENT_CREATE/GUILD_SCHEDULED_EVENT_CREATE.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_SCHEDULED_EVENT_CREATE/GUILD_SCHEDULED_EVENT_CREATE.json index fc9da487e3..d460319ac0 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_SCHEDULED_EVENT_CREATE/GUILD_SCHEDULED_EVENT_CREATE.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_SCHEDULED_EVENT_CREATE/GUILD_SCHEDULED_EVENT_CREATE.json @@ -19,7 +19,6 @@ "creator": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_SCHEDULED_EVENT_CREATE/GUILD_SCHEDULED_EVENT_CREATE.nulls.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_SCHEDULED_EVENT_CREATE/GUILD_SCHEDULED_EVENT_CREATE.nulls.json index 6b2ff4c9cc..ee82557491 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_SCHEDULED_EVENT_CREATE/GUILD_SCHEDULED_EVENT_CREATE.nulls.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_SCHEDULED_EVENT_CREATE/GUILD_SCHEDULED_EVENT_CREATE.nulls.json @@ -19,7 +19,6 @@ "creator": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_SCHEDULED_EVENT_DELETE/GUILD_SCHEDULED_EVENT_DELETE.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_SCHEDULED_EVENT_DELETE/GUILD_SCHEDULED_EVENT_DELETE.json index 2dfac12122..49bfc9580a 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_SCHEDULED_EVENT_DELETE/GUILD_SCHEDULED_EVENT_DELETE.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_SCHEDULED_EVENT_DELETE/GUILD_SCHEDULED_EVENT_DELETE.json @@ -19,7 +19,6 @@ "creator": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_SCHEDULED_EVENT_DELETE/GUILD_SCHEDULED_EVENT_DELETE.nulls.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_SCHEDULED_EVENT_DELETE/GUILD_SCHEDULED_EVENT_DELETE.nulls.json index d8afc5929c..8d2d4d3c2a 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_SCHEDULED_EVENT_DELETE/GUILD_SCHEDULED_EVENT_DELETE.nulls.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_SCHEDULED_EVENT_DELETE/GUILD_SCHEDULED_EVENT_DELETE.nulls.json @@ -19,7 +19,6 @@ "creator": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_SCHEDULED_EVENT_UPDATE/GUILD_SCHEDULED_EVENT_UPDATE.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_SCHEDULED_EVENT_UPDATE/GUILD_SCHEDULED_EVENT_UPDATE.json index ee37463172..42c6c56268 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_SCHEDULED_EVENT_UPDATE/GUILD_SCHEDULED_EVENT_UPDATE.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_SCHEDULED_EVENT_UPDATE/GUILD_SCHEDULED_EVENT_UPDATE.json @@ -19,7 +19,6 @@ "creator": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_SCHEDULED_EVENT_UPDATE/GUILD_SCHEDULED_EVENT_UPDATE.nulls.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_SCHEDULED_EVENT_UPDATE/GUILD_SCHEDULED_EVENT_UPDATE.nulls.json index 1489b166bf..1677801bf0 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_SCHEDULED_EVENT_UPDATE/GUILD_SCHEDULED_EVENT_UPDATE.nulls.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/GUILD_SCHEDULED_EVENT_UPDATE/GUILD_SCHEDULED_EVENT_UPDATE.nulls.json @@ -19,7 +19,6 @@ "creator": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/INTEGRATION_CREATE/INTEGRATION_CREATE.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/INTEGRATION_CREATE/INTEGRATION_CREATE.json index 177483e2bf..234f55de62 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/INTEGRATION_CREATE/INTEGRATION_CREATE.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/INTEGRATION_CREATE/INTEGRATION_CREATE.json @@ -15,7 +15,6 @@ "user": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/INTEGRATION_UPDATE/INTEGRATION_UPDATE.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/INTEGRATION_UPDATE/INTEGRATION_UPDATE.json index 81613a7e59..77f5cf4516 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/INTEGRATION_UPDATE/INTEGRATION_UPDATE.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/INTEGRATION_UPDATE/INTEGRATION_UPDATE.json @@ -15,7 +15,6 @@ "user": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/INTERACTION_CREATE/INTERACTION_CREATE.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/INTERACTION_CREATE/INTERACTION_CREATE.json index 2f1df3e181..56afca4710 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/INTERACTION_CREATE/INTERACTION_CREATE.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/INTERACTION_CREATE/INTERACTION_CREATE.json @@ -25,7 +25,6 @@ "user": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, @@ -37,7 +36,6 @@ "author": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, @@ -55,6 +53,15 @@ }, "app_permissions": "0", "locale": "en-GB", - "guild_locale": "en-GB" + "guild_locale": "en-GB", + "entitlements": [ + { + "id": "999999999999999999", + "sku_id": "999999999999999999", + "application_id": "999999999999999999", + "type": 8, + "deleted": false + } + ] } } diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/INTERACTION_CREATE/INTERACTION_CREATE.optionals.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/INTERACTION_CREATE/INTERACTION_CREATE.optionals.json index 3e6d5c0f46..a58a9b9a4c 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/INTERACTION_CREATE/INTERACTION_CREATE.optionals.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/INTERACTION_CREATE/INTERACTION_CREATE.optionals.json @@ -7,6 +7,16 @@ "application_id": "999999999999999999", "type": 2, "token": "none", - "version": 1 + "version": 1, + "app_permissions": "180224", + "entitlements": [ + { + "id": "999999999999999999", + "sku_id": "999999999999999999", + "application_id": "999999999999999999", + "type": 8, + "deleted": false + } + ] } } diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/INVITE_CREATE/INVITE_CREATE.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/INVITE_CREATE/INVITE_CREATE.json index 709e58ad7f..b5cdaab5d3 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/INVITE_CREATE/INVITE_CREATE.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/INVITE_CREATE/INVITE_CREATE.json @@ -11,7 +11,6 @@ "id": "999999999999999999", "username": "none", "discriminator": "9999", - "global_name": "none", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, "max_age": 1, diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/MESSAGE_CREATE/MESSAGE_CREATE.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/MESSAGE_CREATE/MESSAGE_CREATE.json index 51f97e4247..ead02d07ee 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/MESSAGE_CREATE/MESSAGE_CREATE.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/MESSAGE_CREATE/MESSAGE_CREATE.json @@ -8,7 +8,6 @@ "author": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, @@ -43,8 +42,17 @@ "reactions": [ { "count": 1, + "count_details": { + "burst": 1, + "normal": 2 + }, "me": true, - "emoji": {} + "me_burst": true, + "emoji": { }, + "burst_colors": [ + "#ffffff", + "#e30f8a" + ] } ], "nonce": "none", @@ -64,7 +72,6 @@ "author": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, @@ -87,7 +94,6 @@ "user": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" } @@ -117,10 +123,10 @@ "id": "999999999999999999", "username": "none", "discriminator": "9999", - "global_name": "none", "avatar": "68b329da9893e34099c7d8ad5cb9c940", "member": {} } - ] + ], + "resolved": {} } } diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/MESSAGE_CREATE/MESSAGE_CREATE.nulls.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/MESSAGE_CREATE/MESSAGE_CREATE.nulls.json index 752a04b0d7..bfc29fc47c 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/MESSAGE_CREATE/MESSAGE_CREATE.nulls.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/MESSAGE_CREATE/MESSAGE_CREATE.nulls.json @@ -8,7 +8,6 @@ "author": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, @@ -43,8 +42,17 @@ "reactions": [ { "count": 1, + "count_details": { + "burst": 1, + "normal": 2 + }, "me": true, - "emoji": {} + "me_burst": true, + "emoji": { }, + "burst_colors": [ + "#ffffff", + "#e30f8a" + ] } ], "nonce": "none", @@ -66,7 +74,6 @@ "user": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" } @@ -95,7 +102,6 @@ "id": "999999999999999999", "username": "none", "discriminator": "9999", - "global_name": "none", "avatar": "68b329da9893e34099c7d8ad5cb9c940", "member": {} } diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/MESSAGE_CREATE/MESSAGE_CREATE.optionals.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/MESSAGE_CREATE/MESSAGE_CREATE.optionals.json index 8c827799cd..03ed240fa2 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/MESSAGE_CREATE/MESSAGE_CREATE.optionals.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/MESSAGE_CREATE/MESSAGE_CREATE.optionals.json @@ -8,7 +8,6 @@ "author": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, @@ -40,7 +39,6 @@ "id": "999999999999999999", "username": "none", "discriminator": "9999", - "global_name": "none", "avatar": "68b329da9893e34099c7d8ad5cb9c940", "member": {} } diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/MESSAGE_UPDATE/MESSAGE_UPDATE.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/MESSAGE_UPDATE/MESSAGE_UPDATE.json index 315972fe85..bddaede191 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/MESSAGE_UPDATE/MESSAGE_UPDATE.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/MESSAGE_UPDATE/MESSAGE_UPDATE.json @@ -8,7 +8,6 @@ "author": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, @@ -43,8 +42,17 @@ "reactions": [ { "count": 1, + "count_details": { + "burst": 1, + "normal": 2 + }, "me": true, - "emoji": {} + "me_burst": true, + "emoji": { }, + "burst_colors": [ + "#ffffff", + "#e30f8a" + ] } ], "nonce": "none", @@ -64,7 +72,6 @@ "author": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, @@ -87,7 +94,6 @@ "user": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" } @@ -117,10 +123,10 @@ "id": "999999999999999999", "username": "none", "discriminator": "9999", - "global_name": "none", "avatar": "68b329da9893e34099c7d8ad5cb9c940", "member": {} } - ] + ], + "resolved": {} } } diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/MESSAGE_UPDATE/MESSAGE_UPDATE.nulls.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/MESSAGE_UPDATE/MESSAGE_UPDATE.nulls.json index 1352fce305..83a2fff706 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/MESSAGE_UPDATE/MESSAGE_UPDATE.nulls.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/MESSAGE_UPDATE/MESSAGE_UPDATE.nulls.json @@ -8,7 +8,6 @@ "author": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, @@ -43,8 +42,17 @@ "reactions": [ { "count": 1, + "count_details": { + "burst": 1, + "normal": 2 + }, "me": true, - "emoji": {} + "me_burst": true, + "emoji": { }, + "burst_colors": [ + "#ffffff", + "#e30f8a" + ] } ], "nonce": "none", @@ -66,7 +74,6 @@ "user": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" } @@ -95,7 +102,6 @@ "id": "999999999999999999", "username": "none", "discriminator": "9999", - "global_name": "none", "avatar": "68b329da9893e34099c7d8ad5cb9c940", "member": {} } diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/MESSAGE_UPDATE/MESSAGE_UPDATE.optionals.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/MESSAGE_UPDATE/MESSAGE_UPDATE.optionals.json index be635e6706..4beb30d839 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/MESSAGE_UPDATE/MESSAGE_UPDATE.optionals.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/MESSAGE_UPDATE/MESSAGE_UPDATE.optionals.json @@ -11,7 +11,6 @@ "id": "999999999999999999", "username": "none", "discriminator": "9999", - "global_name": "none", "avatar": "68b329da9893e34099c7d8ad5cb9c940", "member": { } } diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/READY/READY.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/READY/READY.json index 5cf3358e0a..6fd89750ef 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/READY/READY.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/READY/READY.json @@ -6,7 +6,6 @@ "id": "999999999999999999", "username": "none", "discriminator": "9999", - "global_name": "none", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, "guilds": [ diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/READY/READY.optionals.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/READY/READY.optionals.json index e41832f2ed..0e72f52a62 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/READY/READY.optionals.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/READY/READY.optionals.json @@ -6,7 +6,6 @@ "id": "999999999999999999", "username": "none", "discriminator": "9999", - "global_name": "none", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, "guilds": [ diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/THREAD_CREATE/THREAD_CREATE.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/THREAD_CREATE/THREAD_CREATE.json index e1b7152578..261e1b0a97 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/THREAD_CREATE/THREAD_CREATE.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/THREAD_CREATE/THREAD_CREATE.json @@ -26,7 +26,6 @@ { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" } diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/THREAD_CREATE/THREAD_CREATE.nulls.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/THREAD_CREATE/THREAD_CREATE.nulls.json index aef30bb874..b9a23d1c76 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/THREAD_CREATE/THREAD_CREATE.nulls.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/THREAD_CREATE/THREAD_CREATE.nulls.json @@ -26,7 +26,6 @@ { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" } diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/THREAD_UPDATE/THREAD_UPDATE.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/THREAD_UPDATE/THREAD_UPDATE.json index 51ecf6dc8c..f3215872b4 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/THREAD_UPDATE/THREAD_UPDATE.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/THREAD_UPDATE/THREAD_UPDATE.json @@ -26,7 +26,6 @@ { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" } diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/THREAD_UPDATE/THREAD_UPDATE.nulls.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/THREAD_UPDATE/THREAD_UPDATE.nulls.json index c143de82df..2f87321c9a 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/THREAD_UPDATE/THREAD_UPDATE.nulls.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/THREAD_UPDATE/THREAD_UPDATE.nulls.json @@ -26,7 +26,6 @@ { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" } diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/USER_UPDATE/USER_UPDATE.nulls.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/USER_UPDATE/USER_UPDATE.nulls.json index 42a5729bf7..ea901e1c62 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/USER_UPDATE/USER_UPDATE.nulls.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/USER_UPDATE/USER_UPDATE.nulls.json @@ -6,7 +6,7 @@ "id": "999999999999999999", "username": "none", "discriminator": "9999", - "global_name": "none", + "global_name": null, "avatar": null, "bot": true, "system": true, diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/USER_UPDATE/USER_UPDATE.optionals.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/USER_UPDATE/USER_UPDATE.optionals.json index 022229938e..b7cac8fb8e 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/USER_UPDATE/USER_UPDATE.optionals.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/USER_UPDATE/USER_UPDATE.optionals.json @@ -6,7 +6,6 @@ "id": "999999999999999999", "username": "none", "discriminator": "9999", - "global_name": "none", "avatar": "68b329da9893e34099c7d8ad5cb9c940" } } diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/APPLICATION/APPLICATION.json b/Tests/Remora.Discord.Tests/Samples/Objects/APPLICATION/APPLICATION.json index 1770afc53a..6eeaf68a58 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/APPLICATION/APPLICATION.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/APPLICATION/APPLICATION.json @@ -8,6 +8,7 @@ ], "bot_public": true, "bot_require_code_grant": true, + "bot": {}, "terms_of_service_url": "https://www.example.org", "privacy_policy_url": "https://www.example.org", "owner": {}, @@ -27,19 +28,25 @@ "discriminator": "9999", "id": "999999999999999999", "username": "none" - } + }, + "role": "read_only" } ], "name": "none", "owner_user_id": "999999999999999999" }, "guild_id": "999999999999999999", - "guild": { }, + "guild": {}, "primary_sku_id": "999999999999999999", - "slug": "test", + "slug": "none", "cover_image": "68b329da9893e34099c7d8ad5cb9c940", "flags": 0, "approximate_guild_count": 0, + "redirect_uris": [ + "https://www.example.org" + ], + "interactions_endpoint_url": "https://www.example.org", + "role_connections_verification_url": "https://www.example.org", "tags": [ "none" ], @@ -50,5 +57,8 @@ "permissions": "0" }, "custom_install_url": "https://www.example.org", - "role_connections_verification_url": "https://www.example.org" + "integration_types_config": { + "0": { "oauth2_install_params": { "scopes": [ "bot", "application.commands" ], "permissions": "0"} }, + "1": { "oauth2_install_params": { "scopes": [ "application.commands" ], "permissions": "0" } } + } } diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/APPLICATION/APPLICATION.nulls.json b/Tests/Remora.Discord.Tests/Samples/Objects/APPLICATION/APPLICATION.nulls.json index 2107232de7..344248eed6 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/APPLICATION/APPLICATION.nulls.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/APPLICATION/APPLICATION.nulls.json @@ -14,9 +14,9 @@ "verify_key": "none", "team": null, "guild_id": "999999999999999999", - "guild": { }, + "guild": {}, "primary_sku_id": "999999999999999999", - "slug": "test", + "slug": "none", "cover_image": "68b329da9893e34099c7d8ad5cb9c940", "flags": 0, "approximate_guild_count": 0, @@ -30,5 +30,9 @@ "permissions": "0" }, "custom_install_url": "https://www.example.org", - "role_connections_verification_url": "https://www.example.org" -} + "role_connections_verification_url": "https://www.example.org", + "integration_types_config": { + "0": { "oauth2_install_params": { "scopes": [ "bot", "application.commands" ], "permissions": "0"} }, + "1": { "oauth2_install_params": { "scopes": [ "application.commands" ], "permissions": "0" } } + } +} \ No newline at end of file diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/APPLICATION/APPLICATION.optionals.json b/Tests/Remora.Discord.Tests/Samples/Objects/APPLICATION/APPLICATION.optionals.json index 58e0f0808c..926d3d2922 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/APPLICATION/APPLICATION.optionals.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/APPLICATION/APPLICATION.optionals.json @@ -21,10 +21,15 @@ "discriminator": "9999", "id": "999999999999999999", "username": "none" - } + }, + "role": "read_only" } ], "name": "none", "owner_user_id": "999999999999999999" + }, + "integration_types_config": { + "0": { "oauth2_install_params": { "scopes": [ "bot", "application.commands" ], "permissions": "0" } }, + "1": { "oauth2_install_params": { "scopes": [ "application.commands" ], "permissions": "0" } } } } diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/APPLICATION_COMMAND_INTERACTION_DATA_RESOLVED/APPLICATION_COMMAND_INTERACTION_DATA_RESOLVED.json b/Tests/Remora.Discord.Tests/Samples/Objects/APPLICATION_COMMAND_INTERACTION_DATA_RESOLVED/APPLICATION_COMMAND_INTERACTION_DATA_RESOLVED.json index 4893c7e97c..a08217b55d 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/APPLICATION_COMMAND_INTERACTION_DATA_RESOLVED/APPLICATION_COMMAND_INTERACTION_DATA_RESOLVED.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/APPLICATION_COMMAND_INTERACTION_DATA_RESOLVED/APPLICATION_COMMAND_INTERACTION_DATA_RESOLVED.json @@ -4,7 +4,6 @@ "id": "999999999999999999", "username": "none", "discriminator": "9999", - "global_name": "none", "avatar": null } }, diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/AUDIT_LOG/AUDIT_LOG.json b/Tests/Remora.Discord.Tests/Samples/Objects/AUDIT_LOG/AUDIT_LOG.json index a4ee00afd9..3a18a25b00 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/AUDIT_LOG/AUDIT_LOG.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/AUDIT_LOG/AUDIT_LOG.json @@ -68,7 +68,6 @@ { "avatar": "68b329da9893e34099c7d8ad5cb9c940", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "username": "none" } diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/AUTHORIZATION_INFORMATION/AUTHORIZATION_INFORMATION.json b/Tests/Remora.Discord.Tests/Samples/Objects/AUTHORIZATION_INFORMATION/AUTHORIZATION_INFORMATION.json index ff8f0918fb..60f534b536 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/AUTHORIZATION_INFORMATION/AUTHORIZATION_INFORMATION.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/AUTHORIZATION_INFORMATION/AUTHORIZATION_INFORMATION.json @@ -7,7 +7,6 @@ "user": { "avatar": "68b329da9893e34099c7d8ad5cb9c940", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "username": "none" } diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/BAN/BAN.json b/Tests/Remora.Discord.Tests/Samples/Objects/BAN/BAN.json index b62638e6cb..c3ac281498 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/BAN/BAN.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/BAN/BAN.json @@ -3,7 +3,6 @@ "user": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" } diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/BAN/BAN.nulls.json b/Tests/Remora.Discord.Tests/Samples/Objects/BAN/BAN.nulls.json index ee1a9e5f44..92e18b32ae 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/BAN/BAN.nulls.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/BAN/BAN.nulls.json @@ -3,7 +3,6 @@ "user": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" } diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/BULK_BAN_RESPONSE/BULK_BAN_RESPONSE.json b/Tests/Remora.Discord.Tests/Samples/Objects/BULK_BAN_RESPONSE/BULK_BAN_RESPONSE.json new file mode 100644 index 0000000000..1951b0184a --- /dev/null +++ b/Tests/Remora.Discord.Tests/Samples/Objects/BULK_BAN_RESPONSE/BULK_BAN_RESPONSE.json @@ -0,0 +1,8 @@ +{ + "banned_users": [ + "999999999999999999" + ], + "failed_users": [ + "999999999999999999" + ] +} diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/CHANNEL/CHANNEL.json b/Tests/Remora.Discord.Tests/Samples/Objects/CHANNEL/CHANNEL.json index 8b6b5e5b61..97e3cb46be 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/CHANNEL/CHANNEL.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/CHANNEL/CHANNEL.json @@ -22,7 +22,6 @@ { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" } diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/CHANNEL/CHANNEL.nulls.json b/Tests/Remora.Discord.Tests/Samples/Objects/CHANNEL/CHANNEL.nulls.json index 735719f2ee..8d1956d060 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/CHANNEL/CHANNEL.nulls.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/CHANNEL/CHANNEL.nulls.json @@ -22,7 +22,6 @@ { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" } diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/EMOJI/EMOJI.json b/Tests/Remora.Discord.Tests/Samples/Objects/EMOJI/EMOJI.json index e8cb96b83d..471ed99992 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/EMOJI/EMOJI.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/EMOJI/EMOJI.json @@ -7,7 +7,6 @@ "user": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/EMOJI/EMOJI.nulls.json b/Tests/Remora.Discord.Tests/Samples/Objects/EMOJI/EMOJI.nulls.json index c0a04d92db..47cdef3e83 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/EMOJI/EMOJI.nulls.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/EMOJI/EMOJI.nulls.json @@ -7,7 +7,6 @@ "user": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/ENTITLEMENT/ENTITLEMENT.json b/Tests/Remora.Discord.Tests/Samples/Objects/ENTITLEMENT/ENTITLEMENT.json new file mode 100644 index 0000000000..8f0ba6a472 --- /dev/null +++ b/Tests/Remora.Discord.Tests/Samples/Objects/ENTITLEMENT/ENTITLEMENT.json @@ -0,0 +1,12 @@ +{ + "id": "999999999999999999", + "sku_id": "999999999999999999", + "application_id": "999999999999999999", + "user_id": "999999999999999999", + "type": 8, + "deleted": false, + "starts_at": "1970-01-01T00:00:00.000000+00:00", + "ends_at": "1970-01-01T00:00:00.000000+00:00", + "guild_id": "999999999999999999", + "consumed": false +} \ No newline at end of file diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/ENTITLEMENT/ENTITLEMENT.optionals.json b/Tests/Remora.Discord.Tests/Samples/Objects/ENTITLEMENT/ENTITLEMENT.optionals.json new file mode 100644 index 0000000000..3c5dcaa1de --- /dev/null +++ b/Tests/Remora.Discord.Tests/Samples/Objects/ENTITLEMENT/ENTITLEMENT.optionals.json @@ -0,0 +1,7 @@ +{ + "id": "999999999999999999", + "sku_id": "999999999999999999", + "application_id": "999999999999999999", + "type": 8, + "deleted": false +} diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/GUILD_MEMBER/GUILD_MEMBER.json b/Tests/Remora.Discord.Tests/Samples/Objects/GUILD_MEMBER/GUILD_MEMBER.json index 04d49a0699..62372272a7 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/GUILD_MEMBER/GUILD_MEMBER.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/GUILD_MEMBER/GUILD_MEMBER.json @@ -2,7 +2,6 @@ "user": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/GUILD_MEMBER/GUILD_MEMBER.nulls.json b/Tests/Remora.Discord.Tests/Samples/Objects/GUILD_MEMBER/GUILD_MEMBER.nulls.json index f5e1aed939..932295a844 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/GUILD_MEMBER/GUILD_MEMBER.nulls.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/GUILD_MEMBER/GUILD_MEMBER.nulls.json @@ -2,7 +2,6 @@ "user": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/GUILD_ONBOARDING/GUILD_ONBOARDING.json b/Tests/Remora.Discord.Tests/Samples/Objects/GUILD_ONBOARDING/GUILD_ONBOARDING.json index 9214b957c8..1efd5eafa3 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/GUILD_ONBOARDING/GUILD_ONBOARDING.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/GUILD_ONBOARDING/GUILD_ONBOARDING.json @@ -25,7 +25,7 @@ "description": "none", "emoji": { "id": null, - "name": "😀", + "name": "none", "animated": false }, "role_ids": [ @@ -45,4 +45,4 @@ ], "enabled": true, "mode": 0 -} +} \ No newline at end of file diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/GUILD_SCHEDULED_EVENT/GUILD_SCHEDULED_EVENT.json b/Tests/Remora.Discord.Tests/Samples/Objects/GUILD_SCHEDULED_EVENT/GUILD_SCHEDULED_EVENT.json index a1177da79c..2ad56bce0d 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/GUILD_SCHEDULED_EVENT/GUILD_SCHEDULED_EVENT.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/GUILD_SCHEDULED_EVENT/GUILD_SCHEDULED_EVENT.json @@ -15,7 +15,6 @@ "creator": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/GUILD_SCHEDULED_EVENT/GUILD_SCHEDULED_EVENT.nulls.json b/Tests/Remora.Discord.Tests/Samples/Objects/GUILD_SCHEDULED_EVENT/GUILD_SCHEDULED_EVENT.nulls.json index 82b4fd8659..8711bb0c52 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/GUILD_SCHEDULED_EVENT/GUILD_SCHEDULED_EVENT.nulls.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/GUILD_SCHEDULED_EVENT/GUILD_SCHEDULED_EVENT.nulls.json @@ -15,7 +15,6 @@ "creator": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/GUILD_SCHEDULED_EVENT_USER/GUILD_SCHEDULED_EVENT_USER.json b/Tests/Remora.Discord.Tests/Samples/Objects/GUILD_SCHEDULED_EVENT_USER/GUILD_SCHEDULED_EVENT_USER.json index 3be32ec6af..32d255104e 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/GUILD_SCHEDULED_EVENT_USER/GUILD_SCHEDULED_EVENT_USER.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/GUILD_SCHEDULED_EVENT_USER/GUILD_SCHEDULED_EVENT_USER.json @@ -3,7 +3,6 @@ "user": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/GUILD_SCHEDULED_EVENT_USER/GUILD_SCHEDULED_EVENT_USER.optionals.json b/Tests/Remora.Discord.Tests/Samples/Objects/GUILD_SCHEDULED_EVENT_USER/GUILD_SCHEDULED_EVENT_USER.optionals.json index a90ec412e1..6a8a9cf490 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/GUILD_SCHEDULED_EVENT_USER/GUILD_SCHEDULED_EVENT_USER.optionals.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/GUILD_SCHEDULED_EVENT_USER/GUILD_SCHEDULED_EVENT_USER.optionals.json @@ -3,7 +3,6 @@ "user": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" } diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/INTEGRATION/INTEGRATION.json b/Tests/Remora.Discord.Tests/Samples/Objects/INTEGRATION/INTEGRATION.json index 911ae79b19..bddbabd39c 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/INTEGRATION/INTEGRATION.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/INTEGRATION/INTEGRATION.json @@ -11,7 +11,6 @@ "user": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/INTERACTION/INTERACTION.json b/Tests/Remora.Discord.Tests/Samples/Objects/INTERACTION/INTERACTION.json index cc1c992b07..15d8ef5e71 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/INTERACTION/INTERACTION.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/INTERACTION/INTERACTION.json @@ -21,7 +21,6 @@ "user": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, @@ -33,7 +32,6 @@ "author": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, @@ -51,5 +49,14 @@ }, "app_permissions": "0", "locale": "en-GB", - "guild_locale": "en-GB" + "guild_locale": "en-GB", + "entitlements": [ + { + "id": "999999999999999999", + "sku_id": "999999999999999999", + "application_id": "999999999999999999", + "type": 8, + "deleted": false + } + ] } diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/INTERACTION/INTERACTION.optionals.json b/Tests/Remora.Discord.Tests/Samples/Objects/INTERACTION/INTERACTION.optionals.json index 7f3fb36ace..d5c9ba6b03 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/INTERACTION/INTERACTION.optionals.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/INTERACTION/INTERACTION.optionals.json @@ -3,5 +3,15 @@ "application_id": "999999999999999999", "type": 2, "token": "none", - "version": 1 + "version": 1, + "app_permissions": "180224", + "entitlements": [ + { + "id": "999999999999999999", + "sku_id": "999999999999999999", + "application_id": "999999999999999999", + "type": 8, + "deleted": false + } + ] } diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/INVITE/INVITE.json b/Tests/Remora.Discord.Tests/Samples/Objects/INVITE/INVITE.json index eba9c17494..183d3e14f3 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/INVITE/INVITE.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/INVITE/INVITE.json @@ -5,7 +5,6 @@ "inviter": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, @@ -13,7 +12,6 @@ "target_user": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, @@ -38,10 +36,10 @@ "user": { "avatar": "68b329da9893e34099c7d8ad5cb9c940", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "username": "none" - } + }, + "role": "read_only" } ], "name": "none", diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/INVITE/INVITE.nulls.json b/Tests/Remora.Discord.Tests/Samples/Objects/INVITE/INVITE.nulls.json index 776a891c96..a232901b33 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/INVITE/INVITE.nulls.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/INVITE/INVITE.nulls.json @@ -5,7 +5,6 @@ "inviter": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, @@ -13,7 +12,6 @@ "target_user": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, @@ -38,10 +36,10 @@ "user": { "avatar": "68b329da9893e34099c7d8ad5cb9c940", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "username": "none" - } + }, + "role": "read_only" } ], "name": "none", diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE/MESSAGE.json b/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE/MESSAGE.json index e0578e5d62..cd4644aabc 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE/MESSAGE.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE/MESSAGE.json @@ -4,7 +4,6 @@ "author": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, @@ -17,7 +16,6 @@ { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" } @@ -48,8 +46,17 @@ "reactions": [ { "count": 1, + "count_details": { + "burst": 1, + "normal": 2 + }, "me": true, - "emoji": {} + "me_burst": true, + "emoji": { }, + "burst_colors": [ + "#ffffff", + "#e30f8a" + ] } ], "nonce": "none", @@ -69,7 +76,6 @@ "author": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, @@ -92,7 +98,6 @@ "user": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" } @@ -114,5 +119,6 @@ "format_type": 1 } ], - "position": 1 + "position": 1, + "resolved": {} } diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE/MESSAGE.nulls.json b/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE/MESSAGE.nulls.json index 1ed45fe21d..6216198287 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE/MESSAGE.nulls.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE/MESSAGE.nulls.json @@ -4,7 +4,6 @@ "author": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, @@ -17,7 +16,6 @@ { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" } @@ -48,8 +46,17 @@ "reactions": [ { "count": 1, + "count_details": { + "burst": 1, + "normal": 2 + }, "me": true, - "emoji": {} + "me_burst": true, + "emoji": { }, + "burst_colors": [ + "#ffffff", + "#e30f8a" + ] } ], "nonce": "none", @@ -71,7 +78,6 @@ "user": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" } diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE/MESSAGE.optionals.json b/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE/MESSAGE.optionals.json index 8d074ba756..59390cce70 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE/MESSAGE.optionals.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE/MESSAGE.optionals.json @@ -4,7 +4,6 @@ "author": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, @@ -17,7 +16,6 @@ { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" } diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE_COMPONENT/CHANNEL_SELECT_COMPONENT.json b/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE_COMPONENT/CHANNEL_SELECT_COMPONENT.json index 08c464e105..53dedf7ab4 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE_COMPONENT/CHANNEL_SELECT_COMPONENT.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE_COMPONENT/CHANNEL_SELECT_COMPONENT.json @@ -8,5 +8,11 @@ "placeholder": "none", "min_values": 1, "max_values": 3, - "disabled": true + "disabled": true, + "default_values": [ + { + "id": "999999999999999999", + "type": "channel" + } + ] } diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE_COMPONENT/MENTIONABLE_SELECT_COMPONENT.json b/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE_COMPONENT/MENTIONABLE_SELECT_COMPONENT.json index 0d2c8d2265..68a815d72c 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE_COMPONENT/MENTIONABLE_SELECT_COMPONENT.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE_COMPONENT/MENTIONABLE_SELECT_COMPONENT.json @@ -4,5 +4,11 @@ "placeholder": "none", "min_values": 1, "max_values": 3, - "disabled": true + "disabled": true, + "default_values": [ + { + "id": "999999999999999999", + "type": "channel" + } + ] } diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE_COMPONENT/ROLE_SELECT_COMPONENT.json b/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE_COMPONENT/ROLE_SELECT_COMPONENT.json index 6640eb2aaa..fdc2d88e9c 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE_COMPONENT/ROLE_SELECT_COMPONENT.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE_COMPONENT/ROLE_SELECT_COMPONENT.json @@ -4,5 +4,11 @@ "placeholder": "none", "min_values": 1, "max_values": 3, - "disabled": true + "disabled": true, + "default_values": [ + { + "id": "999999999999999999", + "type": "role" + } + ] } diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE_COMPONENT/USER_SELECT_COMPONENT.json b/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE_COMPONENT/USER_SELECT_COMPONENT.json index b88a58c02a..14d29f24f5 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE_COMPONENT/USER_SELECT_COMPONENT.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE_COMPONENT/USER_SELECT_COMPONENT.json @@ -4,5 +4,11 @@ "placeholder": "none", "min_values": 1, "max_values": 3, - "disabled": true + "disabled": true, + "default_values": [ + { + "id": "999999999999999999", + "type": "user" + } + ] } diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE_INTERACTION/MESSAGE_INTERACTION.json b/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE_INTERACTION/MESSAGE_INTERACTION.json index 73b811fdb2..ffc4cd1d1b 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE_INTERACTION/MESSAGE_INTERACTION.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE_INTERACTION/MESSAGE_INTERACTION.json @@ -5,7 +5,6 @@ "user": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE_INTERACTION/MESSAGE_INTERACTION.optionals.json b/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE_INTERACTION/MESSAGE_INTERACTION.optionals.json index 11eceb1d3d..629d5e32c7 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE_INTERACTION/MESSAGE_INTERACTION.optionals.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/MESSAGE_INTERACTION/MESSAGE_INTERACTION.optionals.json @@ -5,7 +5,6 @@ "user": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" } diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/ONBOARDING_PROMPT/ONBOARDING_PROMPT.json b/Tests/Remora.Discord.Tests/Samples/Objects/ONBOARDING_PROMPT/ONBOARDING_PROMPT.json index de868be7bc..8dddc9b29c 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/ONBOARDING_PROMPT/ONBOARDING_PROMPT.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/ONBOARDING_PROMPT/ONBOARDING_PROMPT.json @@ -22,7 +22,7 @@ "description": "none", "emoji": { "id": null, - "name": "😀", + "name": "none", "animated": false }, "role_ids": [ diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/OPTIONAL_AUDIT_ENTRY_INFO/OPTIONAL_AUDIT_ENTRY_INFO.json b/Tests/Remora.Discord.Tests/Samples/Objects/OPTIONAL_AUDIT_ENTRY_INFO/OPTIONAL_AUDIT_ENTRY_INFO.json index 3a49e651c0..2a28295360 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/OPTIONAL_AUDIT_ENTRY_INFO/OPTIONAL_AUDIT_ENTRY_INFO.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/OPTIONAL_AUDIT_ENTRY_INFO/OPTIONAL_AUDIT_ENTRY_INFO.json @@ -9,5 +9,6 @@ "members_removed": "1", "message_id": "999999999999999999", "role_name": "none", - "type": "1" + "type": "1", + "integration_type": "twitch" } diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/REACTION/REACTION.json b/Tests/Remora.Discord.Tests/Samples/Objects/REACTION/REACTION.json index b5b8b33f75..a11bc05efc 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/REACTION/REACTION.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/REACTION/REACTION.json @@ -1,5 +1,14 @@ { "count": 1, + "count_details": { + "burst": 1, + "normal": 2 + }, "me": true, - "emoji": { } + "me_burst": true, + "emoji": { }, + "burst_colors": [ + "#ffffff", + "#e30f8a" + ] } diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/REACTION_COUNT_DETAILS/REACTION_COUNT_DETAILS.json b/Tests/Remora.Discord.Tests/Samples/Objects/REACTION_COUNT_DETAILS/REACTION_COUNT_DETAILS.json new file mode 100644 index 0000000000..ad8e0048fd --- /dev/null +++ b/Tests/Remora.Discord.Tests/Samples/Objects/REACTION_COUNT_DETAILS/REACTION_COUNT_DETAILS.json @@ -0,0 +1,4 @@ +{ + "burst": 1, + "normal": 2 +} diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/SELECT_DEFAULT_VALUE/SELECT_DEFAULT_VALUE.json b/Tests/Remora.Discord.Tests/Samples/Objects/SELECT_DEFAULT_VALUE/SELECT_DEFAULT_VALUE.json new file mode 100644 index 0000000000..6a3339cb5d --- /dev/null +++ b/Tests/Remora.Discord.Tests/Samples/Objects/SELECT_DEFAULT_VALUE/SELECT_DEFAULT_VALUE.json @@ -0,0 +1,4 @@ +{ + "id": "999999999999999999", + "type": "channel" +} diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/SKU/SKU.json b/Tests/Remora.Discord.Tests/Samples/Objects/SKU/SKU.json new file mode 100644 index 0000000000..2cf9f8581f --- /dev/null +++ b/Tests/Remora.Discord.Tests/Samples/Objects/SKU/SKU.json @@ -0,0 +1,8 @@ +{ + "id": "999999999999999999", + "type": 5, + "application_id": "999999999999999999", + "name": "none", + "slug": "none", + "flags": 128 +} \ No newline at end of file diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/STICKER/STICKER.json b/Tests/Remora.Discord.Tests/Samples/Objects/STICKER/STICKER.json index 10995a8872..24166642df 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/STICKER/STICKER.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/STICKER/STICKER.json @@ -11,7 +11,6 @@ "user": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/STICKER/STICKER.nulls.json b/Tests/Remora.Discord.Tests/Samples/Objects/STICKER/STICKER.nulls.json index 002ad21ebf..03c273846b 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/STICKER/STICKER.nulls.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/STICKER/STICKER.nulls.json @@ -11,7 +11,6 @@ "user": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/TEAM/TEAM.json b/Tests/Remora.Discord.Tests/Samples/Objects/TEAM/TEAM.json index 62f181dc00..95b5d0b7b6 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/TEAM/TEAM.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/TEAM/TEAM.json @@ -13,7 +13,8 @@ "discriminator": "9999", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" - } + }, + "role": "read_only" } ], "name": "none", diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/TEAM/TEAM.nulls.json b/Tests/Remora.Discord.Tests/Samples/Objects/TEAM/TEAM.nulls.json index 51139709e6..a60eb7d9dd 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/TEAM/TEAM.nulls.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/TEAM/TEAM.nulls.json @@ -13,7 +13,8 @@ "discriminator": "9999", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" - } + }, + "role": "read_only" } ], "name": "none", diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/TEAM_MEMBER/TEAM_MEMBER.json b/Tests/Remora.Discord.Tests/Samples/Objects/TEAM_MEMBER/TEAM_MEMBER.json index 1276507d1d..2470252fab 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/TEAM_MEMBER/TEAM_MEMBER.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/TEAM_MEMBER/TEAM_MEMBER.json @@ -9,5 +9,6 @@ "discriminator": "9999", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" - } + }, + "role": "read_only" } diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/TEMPLATE/TEMPLATE.json b/Tests/Remora.Discord.Tests/Samples/Objects/TEMPLATE/TEMPLATE.json index 39462bb5cf..f014aca114 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/TEMPLATE/TEMPLATE.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/TEMPLATE/TEMPLATE.json @@ -9,7 +9,6 @@ "username": "none", "avatar": null, "discriminator": "9999", - "global_name": "none", "public_flags": 0, "bot": true }, diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/USER/USER.optionals.json b/Tests/Remora.Discord.Tests/Samples/Objects/USER/USER.optionals.json index ad45cdcf20..11ec4a3c3e 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/USER/USER.optionals.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/USER/USER.optionals.json @@ -2,6 +2,5 @@ "id": "999999999999999999", "username": "none", "discriminator": "9999", - "global_name": "none", "avatar": "68b329da9893e34099c7d8ad5cb9c940" } diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/USER_MENTION/USER_MENTION.nulls.json b/Tests/Remora.Discord.Tests/Samples/Objects/USER_MENTION/USER_MENTION.nulls.json index 0f2fdf33ef..289cfdc2b2 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/USER_MENTION/USER_MENTION.nulls.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/USER_MENTION/USER_MENTION.nulls.json @@ -2,7 +2,7 @@ "id": "999999999999999999", "username": "none", "discriminator": "9999", - "global_name": "none", + "global_name": null, "avatar": null, "bot": true, "system": true, diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/USER_MENTION/USER_MENTION.optionals.json b/Tests/Remora.Discord.Tests/Samples/Objects/USER_MENTION/USER_MENTION.optionals.json index f7ebe5a562..76159c32c9 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/USER_MENTION/USER_MENTION.optionals.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/USER_MENTION/USER_MENTION.optionals.json @@ -2,7 +2,6 @@ "id": "999999999999999999", "username": "none", "discriminator": "9999", - "global_name": "none", "avatar": "68b329da9893e34099c7d8ad5cb9c940", "member": { } } diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/WEBHOOK/WEBHOOK.json b/Tests/Remora.Discord.Tests/Samples/Objects/WEBHOOK/WEBHOOK.json index 8a2267f846..d71187815d 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/WEBHOOK/WEBHOOK.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/WEBHOOK/WEBHOOK.json @@ -6,7 +6,6 @@ "user": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/WEBHOOK/WEBHOOK.nulls.json b/Tests/Remora.Discord.Tests/Samples/Objects/WEBHOOK/WEBHOOK.nulls.json index bb861d833e..19585b554d 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/WEBHOOK/WEBHOOK.nulls.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/WEBHOOK/WEBHOOK.nulls.json @@ -6,7 +6,6 @@ "user": { "username": "none", "discriminator": "9999", - "global_name": "none", "id": "999999999999999999", "avatar": "68b329da9893e34099c7d8ad5cb9c940" }, diff --git a/Tools/Remora.Discord.SensitiveDataScrubber/Program.cs b/Tools/Remora.Discord.SensitiveDataScrubber/Program.cs index 8c5858019f..ee845f56d0 100644 --- a/Tools/Remora.Discord.SensitiveDataScrubber/Program.cs +++ b/Tools/Remora.Discord.SensitiveDataScrubber/Program.cs @@ -137,7 +137,7 @@ public static async Task Main(string[] args) try { await using var fileStream = File.OpenRead(actualFile); - json = JsonNode.Parse(fileStream) ?? throw new InvalidOperationException(); + json = await JsonNode.ParseAsync(fileStream) ?? throw new InvalidOperationException(); } catch (Exception e) { diff --git a/Tools/Remora.Discord.SensitiveDataScrubber/patterns.json b/Tools/Remora.Discord.SensitiveDataScrubber/patterns.json index dfee87c03d..eb8822148c 100644 --- a/Tools/Remora.Discord.SensitiveDataScrubber/patterns.json +++ b/Tools/Remora.Discord.SensitiveDataScrubber/patterns.json @@ -29,7 +29,7 @@ "value_pattern": "^\"[a-zA-Z0-9]+\"$", "replacement": "\"123456\"" }, - "^(((user)?name)|nick|description|content|label|summary|topic|small_text|large_text|state|details|buttons|tags|custom_id|placeholder|title)$": { + "^(((user)?name)|nick|description|content|label|summary|topic|small_text|large_text|state|details|buttons|tags|custom_id|placeholder|title|slug)$": { "priority": 0, "value_pattern": "^\".+\"$", "replacement": "\"none\"" diff --git a/docfx/docfx_project/templates/discordfx b/docfx/docfx_project/templates/discordfx index f209cd757d..f8e8b41a27 160000 --- a/docfx/docfx_project/templates/discordfx +++ b/docfx/docfx_project/templates/discordfx @@ -1 +1 @@ -Subproject commit f209cd757dd8e553bd18d571357cc5d17f6197f0 +Subproject commit f8e8b41a27f974dde6b930c4fee6e1624e43abcc diff --git a/global.json b/global.json index 0c90e7a9b2..c5ac4f09fa 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "msbuild-sdks": { - "Remora.Sdk": "2.1.1", - "Remora.Tests.Sdk": "2.0.5" + "Remora.Sdk": "3.0.1", + "Remora.Tests.Sdk": "3.0.1" } }