Browse Source

Merge branch 'dev' into feature/webhook-client-url-parse

pull/1260/head
Christopher F GitHub 6 years ago
parent
commit
a0d3fe9bc9
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 413 additions and 177 deletions
  1. +1
    -1
      Discord.Net.targets
  2. +2
    -2
      README.md
  3. +0
    -94
      appveyor.yml
  4. +31
    -0
      azure-pipelines.yml
  5. +17
    -0
      azure/build.yml
  6. +32
    -0
      azure/deploy.yml
  7. +5
    -4
      docs/_overwrites/Common/EmbedBuilder.Overwrites.md
  8. +1
    -1
      docs/_template/light-dark-theme/styles/dark.css
  9. +1
    -1
      docs/_template/light-dark-theme/styles/gray.css
  10. +1
    -1
      docs/_template/light-dark-theme/styles/light.css
  11. +1
    -0
      samples/02_commands_framework/Modules/PublicModule.cs
  12. +3
    -1
      samples/02_commands_framework/Program.cs
  13. +15
    -3
      samples/02_commands_framework/Services/CommandHandlingService.cs
  14. +3
    -3
      src/Discord.Net.Core/Discord.Net.Core.csproj
  15. +3
    -1
      src/Discord.Net.Core/Entities/Channels/ChannelType.cs
  16. +5
    -1
      src/Discord.Net.Core/Entities/Messages/MessageType.cs
  17. +44
    -1
      src/Discord.Net.Core/Utils/TokenUtils.cs
  18. +11
    -3
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageDeleteAuditLogData.cs
  19. +14
    -5
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookCreateAuditLogData.cs
  20. +1
    -0
      src/Discord.Net.Rest/Entities/Channels/RestChannel.cs
  21. +9
    -7
      src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs
  22. +53
    -0
      src/Discord.Net.Rest/Entities/Channels/RestNewsChannel.cs
  23. +1
    -1
      src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs
  24. +4
    -4
      src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs
  25. +9
    -5
      src/Discord.Net.WebSocket/DiscordSocketClient.cs
  26. +9
    -8
      src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs
  27. +52
    -0
      src/Discord.Net.WebSocket/Entities/Channels/SocketNewsChannel.cs
  28. +2
    -2
      src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs
  29. +9
    -0
      src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
  30. +3
    -3
      src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs
  31. +16
    -16
      src/Discord.Net/Discord.Net.nuspec
  32. +7
    -4
      test/Discord.Net.Tests/Tests.ChannelPermissions.cs
  33. +2
    -1
      test/Discord.Net.Tests/Tests.Channels.cs
  34. +2
    -1
      test/Discord.Net.Tests/Tests.Guilds.cs
  35. +3
    -2
      test/Discord.Net.Tests/Tests.Migrations.cs
  36. +38
    -0
      test/Discord.Net.Tests/Tests.TokenUtils.cs
  37. +3
    -1
      test/Discord.Net.Tests/Tests.cs

+ 1
- 1
Discord.Net.targets View File

@@ -1,6 +1,6 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup> <PropertyGroup>
<VersionPrefix>2.0.2</VersionPrefix>
<VersionPrefix>2.1.0</VersionPrefix>
<VersionSuffix>dev</VersionSuffix> <VersionSuffix>dev</VersionSuffix>
<Authors>RogueException</Authors> <Authors>RogueException</Authors>
<PackageTags>discord;discordapp</PackageTags> <PackageTags>discord;discordapp</PackageTags>


+ 2
- 2
README.md View File

@@ -1,12 +1,12 @@
# Discord.Net # Discord.Net
[![NuGet](https://img.shields.io/nuget/vpre/Discord.Net.svg?maxAge=2592000?style=plastic)](https://www.nuget.org/packages/Discord.Net) [![NuGet](https://img.shields.io/nuget/vpre/Discord.Net.svg?maxAge=2592000?style=plastic)](https://www.nuget.org/packages/Discord.Net)
[![MyGet](https://img.shields.io/myget/discord-net/vpre/Discord.Net.svg)](https://www.myget.org/feed/Packages/discord-net) [![MyGet](https://img.shields.io/myget/discord-net/vpre/Discord.Net.svg)](https://www.myget.org/feed/Packages/discord-net)
[![Build status](https://ci.appveyor.com/api/projects/status/5sb7n8a09w9clute/branch/dev?svg=true)](https://ci.appveyor.com/project/RogueException/discord-net/branch/dev)
[![Build Status](https://dev.azure.com/discord-net/Discord.Net/_apis/build/status/discord-net.Discord.Net?branchName=dev)](https://dev.azure.com/discord-net/Discord.Net/_build/latest?definitionId=1&branchName=dev)
[![Discord](https://discordapp.com/api/guilds/81384788765712384/widget.png)](https://discord.gg/jkrBmQR) [![Discord](https://discordapp.com/api/guilds/81384788765712384/widget.png)](https://discord.gg/jkrBmQR)


An unofficial .NET API Wrapper for the Discord client (http://discordapp.com). An unofficial .NET API Wrapper for the Discord client (http://discordapp.com).


Check out the [documentation](https://discord.foxbot.me/docs/) or join the [Discord API Chat](https://discord.gg/jkrBmQR).
Check out the [documentation](https://discord.foxbot.me/) or join the [Discord API Chat](https://discord.gg/jkrBmQR).


## Installation ## Installation
### Stable (NuGet) ### Stable (NuGet)


+ 0
- 94
appveyor.yml View File

@@ -1,94 +0,0 @@
version: build-{build}
branches:
only:
- dev
image:
- Visual Studio 2017
- Ubuntu

nuget:
disable_publish_on_pr: true
pull_requests:
do_not_increment_build_number: true
# Use the default clone_folder
# Windows: C:\Projects\discord-net
# Ubuntu: /home/appveyor/projects/discord-net
cache: test/Discord.Net.Tests/cache.db

environment:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
DNET_TEST_TOKEN:
secure: l7h5e7UE7yRd70hAB97kjPiQpPOShwqoBbOzEAYQ+XBd/Pre5OA33IXa3uisdUeQJP/nPFhcOsI+yn7WpuFaoQ==
DNET_TEST_GUILDID: 273160668180381696
init:
- ps: $Env:BUILD = "$($Env:APPVEYOR_BUILD_NUMBER.PadLeft(5, "0"))"

build_script:
- ps: >-
if ($isLinux)
{
# AppVeyor Linux images do not have appveyor-retry, which retries the commands a few times
# until the command exits with code 0.
# So, this is done with a short script.
$code = 0
$counter = 0
do
{
dotnet restore Discord.Net.sln -v Minimal /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG"
$code = $LASTEXITCODE
$counter++
if ($code -ne 0)
{
# Wait 5s before attempting to run again
Start-sleep -Seconds 5
}
}
until ($counter -eq 5 -or $code -eq 0)
}
else
{
appveyor-retry dotnet restore Discord.Net.sln -v Minimal /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG"
}
- ps: dotnet build Discord.Net.sln -c "Release" /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG"
after_build:
- ps: if ($isWindows) { dotnet pack "src\Discord.Net.Core\Discord.Net.Core.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" }
- ps: if ($isWindows) { dotnet pack "src\Discord.Net.Rest\Discord.Net.Rest.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" }
- ps: if ($isWindows) { dotnet pack "src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" }
- ps: if ($isWindows) { dotnet pack "src\Discord.Net.Commands\Discord.Net.Commands.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" }
- ps: if ($isWindows) { dotnet pack "src\Discord.Net.Webhook\Discord.Net.Webhook.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" }
- ps: if ($isWindows) { dotnet pack "src\Discord.Net.Providers.WS4Net\Discord.Net.Providers.WS4Net.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" }
- ps: if ($isWindows) { dotnet pack "src\Discord.Net.Analyzers\Discord.Net.Analyzers.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" }
- ps: >-
if ($isWindows)
{
if ($Env:APPVEYOR_REPO_TAG -eq "true") {
nuget pack src/Discord.Net/Discord.Net.nuspec -OutputDirectory "artifacts" -properties suffix=""
} else {
nuget pack src/Discord.Net/Discord.Net.nuspec -OutputDirectory "artifacts" -properties suffix="-$Env:BUILD"
}
}
- ps: if ($isWindows) { Get-ChildItem artifacts/*.nupkg | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name } }

test_script:
- ps: >-
if ($APPVEYOR_PULL_REQUEST_NUMBER -eq "") {
dotnet test test/Discord.Net.Tests/Discord.Net.Tests.csproj -c "Release" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG"
}

deploy:
- provider: NuGet
server: https://www.myget.org/F/discord-net/api/v2/package
api_key:
secure: Jl7BXeUjRnkVHDMBuUWSXcEOkrli1PBleW2IiLyUs5j63UNUNp1hcjaUJRujx9lz
symbol_server: https://www.myget.org/F/discord-net/symbols/api/v2/package
on:
branch: dev
CI_WINDOWS: true
- provider: NuGet
server: https://www.myget.org/F/rogueexception/api/v2/package
api_key:
secure: D+vW2O2LBf/iJb4f+q8fkyIW2VdIYIGxSYLWNrOD4BHlDBZQlJipDbNarWjUr2Kn
symbol_server: https://www.myget.org/F/rogueexception/symbols/api/v2/package
on:
branch: dev
CI_WINDOWS: true

+ 31
- 0
azure-pipelines.yml View File

@@ -0,0 +1,31 @@
variables:
buildConfiguration: Release
buildTag: $[ startsWith(variables['Build.SourceBranch'], 'refs/tags') ]
buildNumber: $[ variables['Build.BuildNumber'] ]


jobs:
- job: Linux
pool:
vmImage: 'ubuntu-16.04'
steps:
- template: azure/build.yml

- job: Windows_build
pool:
vmImage: 'vs2017-win2016'
condition: ne(variables['Build.SourceBranch'], 'refs/heads/dev')
steps:
- template: azure/build.yml

- job: Windows_deploy
pool:
vmImage: 'vs2017-win2016'
condition: |
and (
succeeded(),
eq(variables['Build.SourceBranch'], 'refs/heads/dev')
)
steps:
- template: azure/build.yml
- template: azure/deploy.yml

+ 17
- 0
azure/build.yml View File

@@ -0,0 +1,17 @@
steps:
- script: dotnet restore -v minimal Discord.Net.sln
displayName: Restore packages

- script: dotnet build "Discord.Net.sln" --no-restore -v minimal -c $(buildConfiguration) /p:BuildNumber=$(buildNumber) /p:IsTagBuild=$(buildTag)
displayName: Build projects

- script: dotnet test "test/Discord.Net.Tests/Discord.Net.Tests.csproj" --no-restore --no-build -v minimal -c $(buildConfiguration) --logger trx
# TODO: update this to support multiple tests
displayName: Test projects

- task: PublishTestResults@2
displayName: Publish test results
condition: succeededOrFailed()
inputs:
testRunner: VSTest
testResultsFiles: '**/*.trx'

+ 32
- 0
azure/deploy.yml View File

@@ -0,0 +1,32 @@
steps:
- script: |
dotnet pack "src\Discord.Net.Core\Discord.Net.Core.csproj" --no-restore --no-build -v minimal -c $(buildConfiguration) -o "../../artifacts/" /p:BuildNumber=$(buildNumber) /p:IsTagBuild=$(buildTag)
dotnet pack "src\Discord.Net.Rest\Discord.Net.Rest.csproj" --no-restore --no-build -v minimal -c $(buildConfiguration) -o "../../artifacts/" /p:BuildNumber=$(buildNumber) /p:IsTagBuild=$(buildTag)
dotnet pack "src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" --no-restore --no-build -v minimal -c $(buildConfiguration) -o "../../artifacts/" /p:BuildNumber=$(buildNumber) /p:IsTagBuild=$(buildTag)
dotnet pack "src\Discord.Net.Commands\Discord.Net.Commands.csproj" --no-restore --no-build -v minimal -c $(buildConfiguration) -o "../../artifacts/" /p:BuildNumber=$(buildNumber) /p:IsTagBuild=$(buildTag)
dotnet pack "src\Discord.Net.Webhook\Discord.Net.Webhook.csproj" --no-restore --no-build -v minimal -c $(buildConfiguration) -o "../../artifacts/" /p:BuildNumber=$(buildNumber) /p:IsTagBuild=$(buildTag)
dotnet pack "src\Discord.Net.Providers.WS4Net\Discord.Net.Providers.WS4Net.csproj" --no-restore --no-build -v minimal -c $(buildConfiguration) -o "../../artifacts/" /p:BuildNumber=$(buildNumber) /p:IsTagBuild=$(buildTag)
dotnet pack "src\Discord.Net.Analyzers\Discord.Net.Analyzers.csproj" --no-restore --no-build -v minimal -c $(buildConfiguration) -o "../../artifacts/" /p:BuildNumber=$(buildNumber) /p:IsTagBuild=$(buildTag)
displayName: Pack projects

- task: NuGet@0
inputs:
command: pack
arguments: src/Discord.Net/Discord.Net.nuspec -OutputDirectory "artifacts" -properties suffix=""
displayName: Pack metapackage (release mode)
condition: eq(variables['buildTag'], True)

- task: NuGet@0
inputs:
command: pack
arguments: src/Discord.Net/Discord.Net.nuspec -OutputDirectory "artifacts" -properties suffix="-$(buildNumber)"
displayName: Pack metapackage
condition: eq(variables['buildTag'], False)

- task: NuGetCommand@2
displayName: Push to NuGet
inputs:
command: push
nuGetFeedType: external
packagesToPush: 'artifacts/*.nupkg'
publishFeedCredentials: myget-discord

+ 5
- 4
docs/_overwrites/Common/EmbedBuilder.Overwrites.md View File

@@ -40,9 +40,10 @@ public async Task SendRichEmbedAsync()
.WithTitle("I overwrote \"Hello world!\"") .WithTitle("I overwrote \"Hello world!\"")
.WithDescription("I am a description.") .WithDescription("I am a description.")
.WithUrl("https://example.com") .WithUrl("https://example.com")
.WithCurrentTimestamp()
.Build();
await ReplyAsync(embed: embed);
.WithCurrentTimestamp();
//Your embed needs to be built before it is able to be sent
await ReplyAsync(embed: embed.Build());
} }
``` ```


@@ -65,4 +66,4 @@ public async Task SendEmbedWithImageAsync()
}.Build(); }.Build();
await Context.Channel.SendFileAsync(fileName, embed: embed); await Context.Channel.SendFileAsync(fileName, embed: embed);
} }
```
```

+ 1
- 1
docs/_template/light-dark-theme/styles/dark.css View File

@@ -304,5 +304,5 @@ span.arrow-r{
} }


.logo-switcher { .logo-switcher {
background: url("/marketing/logo/SVG/Combinationmark White.svg") no-repeat;
background: url("../marketing/logo/SVG/Combinationmark White.svg") no-repeat;
} }

+ 1
- 1
docs/_template/light-dark-theme/styles/gray.css View File

@@ -311,5 +311,5 @@ span.arrow-r{
} }


.logo-switcher { .logo-switcher {
background: url("/marketing/logo/SVG/Combinationmark White.svg") no-repeat;
background: url("../marketing/logo/SVG/Combinationmark White.svg") no-repeat;
} }

+ 1
- 1
docs/_template/light-dark-theme/styles/light.css View File

@@ -113,5 +113,5 @@ span.arrow-r{
} }


.logo-switcher { .logo-switcher {
background: url("/marketing/logo/SVG/Combinationmark.svg") no-repeat;
background: url("../marketing/logo/SVG/Combinationmark.svg") no-repeat;
} }

+ 1
- 0
samples/02_commands_framework/Modules/PublicModule.cs View File

@@ -60,6 +60,7 @@ namespace _02_commands_framework.Modules
public Task ListAsync(params string[] objects) public Task ListAsync(params string[] objects)
=> ReplyAsync("You listed: " + string.Join("; ", objects)); => ReplyAsync("You listed: " + string.Join("; ", objects));


// Setting a custom ErrorMessage property will help clarify the precondition error
[Command("guild_only")] [Command("guild_only")]
[RequireContext(ContextType.Guild, ErrorMessage = "Sorry, this command must be ran from within a server, not a DM!")] [RequireContext(ContextType.Guild, ErrorMessage = "Sorry, this command must be ran from within a server, not a DM!")]
public Task GuildOnlyCommand() public Task GuildOnlyCommand()


+ 3
- 1
samples/02_commands_framework/Program.cs View File

@@ -37,10 +37,12 @@ namespace _02_commands_framework
client.Log += LogAsync; client.Log += LogAsync;
services.GetRequiredService<CommandService>().Log += LogAsync; services.GetRequiredService<CommandService>().Log += LogAsync;


// Tokens should be considered secret data, and never hard-coded.
// Tokens should be considered secret data and never hard-coded.
// We can read from the environment variable to avoid hardcoding.
await client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("token")); await client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("token"));
await client.StartAsync(); await client.StartAsync();


// Here we initialize the logic required to register our commands.
await services.GetRequiredService<CommandHandlingService>().InitializeAsync(); await services.GetRequiredService<CommandHandlingService>().InitializeAsync();


await Task.Delay(-1); await Task.Delay(-1);


+ 15
- 3
samples/02_commands_framework/Services/CommandHandlingService.cs View File

@@ -20,12 +20,16 @@ namespace _02_commands_framework.Services
_discord = services.GetRequiredService<DiscordSocketClient>(); _discord = services.GetRequiredService<DiscordSocketClient>();
_services = services; _services = services;


// Hook CommandExecuted to handle post-command-execution logic.
_commands.CommandExecuted += CommandExecutedAsync; _commands.CommandExecuted += CommandExecutedAsync;
// Hook MessageReceived so we can process each message to see
// if it qualifies as a command.
_discord.MessageReceived += MessageReceivedAsync; _discord.MessageReceived += MessageReceivedAsync;
} }


public async Task InitializeAsync() public async Task InitializeAsync()
{ {
// Register modules that are public and inherit ModuleBase<T>.
await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services);
} }


@@ -37,10 +41,18 @@ namespace _02_commands_framework.Services


// This value holds the offset where the prefix ends // This value holds the offset where the prefix ends
var argPos = 0; var argPos = 0;
// Perform prefix check. You may want to replace this with
// (!message.HasCharPrefix('!', ref argPos))
// for a more traditional command format like !help.
if (!message.HasMentionPrefix(_discord.CurrentUser, ref argPos)) return; if (!message.HasMentionPrefix(_discord.CurrentUser, ref argPos)) return;


var context = new SocketCommandContext(_discord, message); var context = new SocketCommandContext(_discord, message);
await _commands.ExecuteAsync(context, argPos, _services); // we will handle the result in CommandExecutedAsync
// Perform the execution of the command. In this method,
// the command service will perform precondition and parsing check
// then execute the command if one is matched.
await _commands.ExecuteAsync(context, argPos, _services);
// Note that normally a result will be returned by this format, but here
// we will handle the result in CommandExecutedAsync,
} }


public async Task CommandExecutedAsync(Optional<CommandInfo> command, ICommandContext context, IResult result) public async Task CommandExecutedAsync(Optional<CommandInfo> command, ICommandContext context, IResult result)
@@ -49,12 +61,12 @@ namespace _02_commands_framework.Services
if (!command.IsSpecified) if (!command.IsSpecified)
return; return;


// the command was succesful, we don't care about this result, unless we want to log that a command succeeded.
// the command was successful, we don't care about this result, unless we want to log that a command succeeded.
if (result.IsSuccess) if (result.IsSuccess)
return; return;


// the command failed, let's notify the user that something happened. // the command failed, let's notify the user that something happened.
await context.Channel.SendMessageAsync($"error: {result.ToString()}");
await context.Channel.SendMessageAsync($"error: {result}");
} }
} }
} }

+ 3
- 3
src/Discord.Net.Core/Discord.Net.Core.csproj View File

@@ -11,8 +11,8 @@
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="System.Collections.Immutable" Version="1.3.1" /> <PackageReference Include="System.Collections.Immutable" Version="1.3.1" />
<PackageReference Include="System.Interactive.Async" Version="3.1.1" /> <PackageReference Include="System.Interactive.Async" Version="3.1.1" />
</ItemGroup>
<ItemGroup Condition=" '$(Configuration)' != 'Release' ">
<PackageReference Include="IDisposableAnalyzers" Version="2.0.3.3" />
<PackageReference Include="IDisposableAnalyzers" Version="2.1.2">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup> </ItemGroup>
</Project> </Project>

+ 3
- 1
src/Discord.Net.Core/Entities/Channels/ChannelType.cs View File

@@ -12,6 +12,8 @@ namespace Discord
/// <summary> The channel is a group channel. </summary> /// <summary> The channel is a group channel. </summary>
Group = 3, Group = 3,
/// <summary> The channel is a category channel. </summary> /// <summary> The channel is a category channel. </summary>
Category = 4
Category = 4,
/// <summary> The channel is a news channel. </summary>
News = 5
} }
} }

+ 5
- 1
src/Discord.Net.Core/Entities/Messages/MessageType.cs View File

@@ -32,6 +32,10 @@ namespace Discord
/// <summary> /// <summary>
/// The message when another message is pinned. /// The message when another message is pinned.
/// </summary> /// </summary>
ChannelPinnedMessage = 6
ChannelPinnedMessage = 6,
/// <summary>
/// The message when a new member joined.
/// </summary>
GuildMemberJoin = 7
} }
} }

+ 44
- 1
src/Discord.Net.Core/Utils/TokenUtils.cs View File

@@ -17,6 +17,47 @@ namespace Discord
/// </remarks> /// </remarks>
internal const int MinBotTokenLength = 58; internal const int MinBotTokenLength = 58;


internal const char Base64Padding = '=';

/// <summary>
/// Pads a base64-encoded string with 0, 1, or 2 '=' characters,
/// if the string is not a valid multiple of 4.
/// Does not ensure that the provided string contains only valid base64 characters.
/// Strings that already contain padding will not have any more padding applied.
/// </summary>
/// <remarks>
/// A string that would require 3 padding characters is considered to be already corrupt.
/// Some older bot tokens may require padding, as the format provided by Discord
/// does not include this padding in the token.
/// </remarks>
/// <param name="encodedBase64">The base64 encoded string to pad with characters.</param>
/// <returns>A string containing the base64 padding.</returns>
/// <exception cref="FormatException">
/// Thrown if <paramref name="encodedBase64"/> would require an invalid number of padding characters.
/// </exception>
/// <exception cref="ArgumentNullException">
/// Thrown if <paramref name="encodedBase64"/> is null, empty, or whitespace.
/// </exception>
internal static string PadBase64String(string encodedBase64)
{
if (string.IsNullOrWhiteSpace(encodedBase64))
throw new ArgumentNullException(paramName: encodedBase64,
message: "The supplied base64-encoded string was null or whitespace.");

// do not pad if already contains padding characters
if (encodedBase64.IndexOf(Base64Padding) != -1)
return encodedBase64;

// based from https://stackoverflow.com/a/1228744
var padding = (4 - (encodedBase64.Length % 4)) % 4;
if (padding == 3)
// can never have 3 characters of padding
throw new FormatException("The provided base64 string is corrupt, as it requires an invalid amount of padding.");
else if (padding == 0)
return encodedBase64;
return encodedBase64.PadRight(encodedBase64.Length + padding, Base64Padding);
}

/// <summary> /// <summary>
/// Decodes a base 64 encoded string into a ulong value. /// Decodes a base 64 encoded string into a ulong value.
/// </summary> /// </summary>
@@ -29,6 +70,8 @@ namespace Discord


try try
{ {
// re-add base64 padding if missing
encoded = PadBase64String(encoded);
// decode the base64 string // decode the base64 string
var bytes = Convert.FromBase64String(encoded); var bytes = Convert.FromBase64String(encoded);
var idStr = Encoding.UTF8.GetString(bytes); var idStr = Encoding.UTF8.GetString(bytes);
@@ -46,7 +89,7 @@ namespace Discord
} }
catch (ArgumentException) catch (ArgumentException)
{ {
// ignore exception, can be thrown by BitConverter
// ignore exception, can be thrown by BitConverter, or by PadBase64String
} }
return null; return null;
} }


+ 11
- 3
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageDeleteAuditLogData.cs View File

@@ -1,4 +1,4 @@
using Model = Discord.API.AuditLog;
using Model = Discord.API.AuditLog;
using EntryModel = Discord.API.AuditLogEntry; using EntryModel = Discord.API.AuditLogEntry;


namespace Discord.Rest namespace Discord.Rest
@@ -8,15 +8,16 @@ namespace Discord.Rest
/// </summary> /// </summary>
public class MessageDeleteAuditLogData : IAuditLogData public class MessageDeleteAuditLogData : IAuditLogData
{ {
private MessageDeleteAuditLogData(ulong channelId, int count)
private MessageDeleteAuditLogData(ulong channelId, int count, ulong authorId)
{ {
ChannelId = channelId; ChannelId = channelId;
MessageCount = count; MessageCount = count;
AuthorId = authorId;
} }


internal static MessageDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) internal static MessageDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
{ {
return new MessageDeleteAuditLogData(entry.Options.MessageDeleteChannelId.Value, entry.Options.MessageDeleteCount.Value);
return new MessageDeleteAuditLogData(entry.Options.MessageDeleteChannelId.Value, entry.Options.MessageDeleteCount.Value, entry.TargetId.Value);
} }


/// <summary> /// <summary>
@@ -34,5 +35,12 @@ namespace Discord.Rest
/// deleted from. /// deleted from.
/// </returns> /// </returns>
public ulong ChannelId { get; } public ulong ChannelId { get; }
/// <summary>
/// Gets the author of the messages that were deleted.
/// </summary>
/// <returns>
/// A <see cref="ulong"/> representing the snowflake identifier for the user that created the deleted messages.
/// </returns>
public ulong AuthorId { get; }
} }
} }

+ 14
- 5
src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookCreateAuditLogData.cs View File

@@ -10,9 +10,10 @@ namespace Discord.Rest
/// </summary> /// </summary>
public class WebhookCreateAuditLogData : IAuditLogData public class WebhookCreateAuditLogData : IAuditLogData
{ {
private WebhookCreateAuditLogData(IWebhook webhook, WebhookType type, string name, ulong channelId)
private WebhookCreateAuditLogData(IWebhook webhook, ulong webhookId, WebhookType type, string name, ulong channelId)
{ {
Webhook = webhook; Webhook = webhook;
WebhookId = webhookId;
Name = name; Name = name;
Type = type; Type = type;
ChannelId = channelId; ChannelId = channelId;
@@ -31,23 +32,31 @@ namespace Discord.Rest
var name = nameModel.NewValue.ToObject<string>(discord.ApiClient.Serializer); var name = nameModel.NewValue.ToObject<string>(discord.ApiClient.Serializer);


var webhookInfo = log.Webhooks?.FirstOrDefault(x => x.Id == entry.TargetId); var webhookInfo = log.Webhooks?.FirstOrDefault(x => x.Id == entry.TargetId);
var webhook = RestWebhook.Create(discord, (IGuild)null, webhookInfo);
var webhook = webhookInfo == null ? null : RestWebhook.Create(discord, (IGuild)null, webhookInfo);


return new WebhookCreateAuditLogData(webhook, type, name, channelId);
return new WebhookCreateAuditLogData(webhook, entry.TargetId.Value, type, name, channelId);
} }


// Doc Note: Corresponds to the *current* data // Doc Note: Corresponds to the *current* data


/// <summary> /// <summary>
/// Gets the webhook that was created.
/// Gets the webhook that was created if it still exists.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// A webhook object representing the webhook that was created.
/// A webhook object representing the webhook that was created if it still exists, otherwise returns <c>null</c>.
/// </returns> /// </returns>
public IWebhook Webhook { get; } public IWebhook Webhook { get; }


// Doc Note: Corresponds to the *audit log* data // Doc Note: Corresponds to the *audit log* data


/// <summary>
/// Gets the webhook id.
/// </summary>
/// <returns>
/// The webhook identifier.
/// </returns>
public ulong WebhookId { get; }

/// <summary> /// <summary>
/// Gets the type of webhook that was created. /// Gets the type of webhook that was created.
/// </summary> /// </summary>


+ 1
- 0
src/Discord.Net.Rest/Entities/Channels/RestChannel.cs View File

@@ -23,6 +23,7 @@ namespace Discord.Rest
{ {
switch (model.Type) switch (model.Type)
{ {
case ChannelType.News:
case ChannelType.Text: case ChannelType.Text:
case ChannelType.Voice: case ChannelType.Voice:
return RestGuildChannel.Create(discord, new RestGuild(discord, model.GuildId.Value), model); return RestGuildChannel.Create(discord, new RestGuild(discord, model.GuildId.Value), model);


+ 9
- 7
src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs View File

@@ -15,7 +15,7 @@ namespace Discord.Rest
private ImmutableArray<Overwrite> _overwrites; private ImmutableArray<Overwrite> _overwrites;


/// <inheritdoc /> /// <inheritdoc />
public IReadOnlyCollection<Overwrite> PermissionOverwrites => _overwrites;
public virtual IReadOnlyCollection<Overwrite> PermissionOverwrites => _overwrites;


internal IGuild Guild { get; } internal IGuild Guild { get; }
/// <inheritdoc /> /// <inheritdoc />
@@ -34,6 +34,8 @@ namespace Discord.Rest
{ {
switch (model.Type) switch (model.Type)
{ {
case ChannelType.News:
return RestNewsChannel.Create(discord, guild, model);
case ChannelType.Text: case ChannelType.Text:
return RestTextChannel.Create(discord, guild, model); return RestTextChannel.Create(discord, guild, model);
case ChannelType.Voice: case ChannelType.Voice:
@@ -79,7 +81,7 @@ namespace Discord.Rest
/// <returns> /// <returns>
/// An overwrite object for the targeted user; <c>null</c> if none is set. /// An overwrite object for the targeted user; <c>null</c> if none is set.
/// </returns> /// </returns>
public OverwritePermissions? GetPermissionOverwrite(IUser user)
public virtual OverwritePermissions? GetPermissionOverwrite(IUser user)
{ {
for (int i = 0; i < _overwrites.Length; i++) for (int i = 0; i < _overwrites.Length; i++)
{ {
@@ -96,7 +98,7 @@ namespace Discord.Rest
/// <returns> /// <returns>
/// An overwrite object for the targeted role; <c>null</c> if none is set. /// An overwrite object for the targeted role; <c>null</c> if none is set.
/// </returns> /// </returns>
public OverwritePermissions? GetPermissionOverwrite(IRole role)
public virtual OverwritePermissions? GetPermissionOverwrite(IRole role)
{ {
for (int i = 0; i < _overwrites.Length; i++) for (int i = 0; i < _overwrites.Length; i++)
{ {
@@ -115,7 +117,7 @@ namespace Discord.Rest
/// <returns> /// <returns>
/// A task representing the asynchronous permission operation for adding the specified permissions to the channel. /// A task representing the asynchronous permission operation for adding the specified permissions to the channel.
/// </returns> /// </returns>
public async Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options = null)
public virtual async Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options = null)
{ {
await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, user, permissions, options).ConfigureAwait(false); await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, user, permissions, options).ConfigureAwait(false);
_overwrites = _overwrites.Add(new Overwrite(user.Id, PermissionTarget.User, new OverwritePermissions(permissions.AllowValue, permissions.DenyValue))); _overwrites = _overwrites.Add(new Overwrite(user.Id, PermissionTarget.User, new OverwritePermissions(permissions.AllowValue, permissions.DenyValue)));
@@ -129,7 +131,7 @@ namespace Discord.Rest
/// <returns> /// <returns>
/// A task representing the asynchronous permission operation for adding the specified permissions to the channel. /// A task representing the asynchronous permission operation for adding the specified permissions to the channel.
/// </returns> /// </returns>
public async Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null)
public virtual async Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null)
{ {
await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, role, permissions, options).ConfigureAwait(false); await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, role, permissions, options).ConfigureAwait(false);
_overwrites = _overwrites.Add(new Overwrite(role.Id, PermissionTarget.Role, new OverwritePermissions(permissions.AllowValue, permissions.DenyValue))); _overwrites = _overwrites.Add(new Overwrite(role.Id, PermissionTarget.Role, new OverwritePermissions(permissions.AllowValue, permissions.DenyValue)));
@@ -143,7 +145,7 @@ namespace Discord.Rest
/// <returns> /// <returns>
/// A task representing the asynchronous operation for removing the specified permissions from the channel. /// A task representing the asynchronous operation for removing the specified permissions from the channel.
/// </returns> /// </returns>
public async Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null)
public virtual async Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null)
{ {
await ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, user, options).ConfigureAwait(false); await ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, user, options).ConfigureAwait(false);


@@ -164,7 +166,7 @@ namespace Discord.Rest
/// <returns> /// <returns>
/// A task representing the asynchronous operation for removing the specified permissions from the channel. /// A task representing the asynchronous operation for removing the specified permissions from the channel.
/// </returns> /// </returns>
public async Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null)
public virtual async Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null)
{ {
await ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, role, options).ConfigureAwait(false); await ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, role, options).ConfigureAwait(false);




+ 53
- 0
src/Discord.Net.Rest/Entities/Channels/RestNewsChannel.cs View File

@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Model = Discord.API.Channel;

namespace Discord.Rest
{
/// <summary>
/// Represents a REST-based news channel in a guild that has the same properties as a <see cref="RestTextChannel"/>.
/// </summary>
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class RestNewsChannel : RestTextChannel
{
internal RestNewsChannel(BaseDiscordClient discord, IGuild guild, ulong id)
:base(discord, guild, id)
{
}
internal new static RestNewsChannel Create(BaseDiscordClient discord, IGuild guild, Model model)
{
var entity = new RestNewsChannel(discord, guild, model.Id);
entity.Update(model);
return entity;
}
public override int SlowModeInterval => throw new NotSupportedException("News channels do not support Slow Mode.");
public override Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null)
{
throw new NotSupportedException("News channels do not support Overwrite Permissions.");
}
public override Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options = null)
{
throw new NotSupportedException("News channels do not support Overwrite Permissions.");
}
public override OverwritePermissions? GetPermissionOverwrite(IRole role)
{
throw new NotSupportedException("News channels do not support Overwrite Permissions.");
}
public override OverwritePermissions? GetPermissionOverwrite(IUser user)
{
throw new NotSupportedException("News channels do not support Overwrite Permissions.");
}
public override Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null)
{
throw new NotSupportedException("News channels do not support Overwrite Permissions.");
}
public override Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null)
{
throw new NotSupportedException("News channels do not support Overwrite Permissions.");
}
}
}

+ 1
- 1
src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs View File

@@ -17,7 +17,7 @@ namespace Discord.Rest
/// <inheritdoc /> /// <inheritdoc />
public string Topic { get; private set; } public string Topic { get; private set; }
/// <inheritdoc /> /// <inheritdoc />
public int SlowModeInterval { get; private set; }
public virtual int SlowModeInterval { get; private set; }
/// <inheritdoc /> /// <inheritdoc />
public ulong? CategoryId { get; private set; } public ulong? CategoryId { get; private set; }




+ 4
- 4
src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs View File

@@ -16,10 +16,10 @@ namespace Discord.Rest
{ {
private bool _isMentioningEveryone, _isTTS, _isPinned; private bool _isMentioningEveryone, _isTTS, _isPinned;
private long? _editedTimestampTicks; private long? _editedTimestampTicks;
private ImmutableArray<Attachment> _attachments;
private ImmutableArray<Embed> _embeds;
private ImmutableArray<ITag> _tags;
private ImmutableArray<RestReaction> _reactions;
private ImmutableArray<Attachment> _attachments = ImmutableArray.Create<Attachment>();
private ImmutableArray<Embed> _embeds = ImmutableArray.Create<Embed>();
private ImmutableArray<ITag> _tags = ImmutableArray.Create<ITag>();
private ImmutableArray<RestReaction> _reactions = ImmutableArray.Create<RestReaction>();


/// <inheritdoc /> /// <inheritdoc />
public override bool IsTTS => _isTTS; public override bool IsTTS => _isTTS;


+ 9
- 5
src/Discord.Net.WebSocket/DiscordSocketClient.cs View File

@@ -83,7 +83,7 @@ namespace Discord.WebSocket
/// </note> /// </note>
/// </remarks> /// </remarks>
/// <returns> /// <returns>
/// An collection of DM channels that have been opened in this session.
/// A collection of DM channels that have been opened in this session.
/// </returns> /// </returns>
public IReadOnlyCollection<SocketDMChannel> DMChannels public IReadOnlyCollection<SocketDMChannel> DMChannels
=> State.PrivateChannels.OfType<SocketDMChannel>().ToImmutableArray(); => State.PrivateChannels.OfType<SocketDMChannel>().ToImmutableArray();
@@ -98,7 +98,7 @@ namespace Discord.WebSocket
/// </note> /// </note>
/// </remarks> /// </remarks>
/// <returns> /// <returns>
/// An collection of group channels that have been opened in this session.
/// A collection of group channels that have been opened in this session.
/// </returns> /// </returns>
public IReadOnlyCollection<SocketGroupChannel> GroupChannels public IReadOnlyCollection<SocketGroupChannel> GroupChannels
=> State.PrivateChannels.OfType<SocketGroupChannel>().ToImmutableArray(); => State.PrivateChannels.OfType<SocketGroupChannel>().ToImmutableArray();
@@ -1173,9 +1173,13 @@ namespace Discord.WebSocket
{ {
if (guild != null) if (guild != null)
{ {
author = data.Member.IsSpecified // member isn't always included, but use it when we can
? guild.AddOrUpdateUser(data.Member.Value)
: guild.AddOrUpdateUser(data.Author.Value); // user has no guild-specific data
if (data.Member.IsSpecified) // member isn't always included, but use it when we can
{
data.Member.Value.User = data.Author.Value;
author = guild.AddOrUpdateUser(data.Member.Value);
}
else
author = guild.AddOrUpdateUser(data.Author.Value); // user has no guild-specific data
} }
else if (channel is SocketGroupChannel) else if (channel is SocketGroupChannel)
author = (channel as SocketGroupChannel).GetOrAddUser(data.Author.Value); author = (channel as SocketGroupChannel).GetOrAddUser(data.Author.Value);


+ 9
- 8
src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs View File

@@ -30,7 +30,7 @@ namespace Discord.WebSocket
public int Position { get; private set; } public int Position { get; private set; }


/// <inheritdoc /> /// <inheritdoc />
public IReadOnlyCollection<Overwrite> PermissionOverwrites => _overwrites;
public virtual IReadOnlyCollection<Overwrite> PermissionOverwrites => _overwrites;
/// <summary> /// <summary>
/// Gets a collection of users that are able to view the channel. /// Gets a collection of users that are able to view the channel.
/// </summary> /// </summary>
@@ -48,6 +48,8 @@ namespace Discord.WebSocket
{ {
switch (model.Type) switch (model.Type)
{ {
case ChannelType.News:
return SocketNewsChannel.Create(guild, state, model);
case ChannelType.Text: case ChannelType.Text:
return SocketTextChannel.Create(guild, state, model); return SocketTextChannel.Create(guild, state, model);
case ChannelType.Voice: case ChannelType.Voice:
@@ -55,7 +57,6 @@ namespace Discord.WebSocket
case ChannelType.Category: case ChannelType.Category:
return SocketCategoryChannel.Create(guild, state, model); return SocketCategoryChannel.Create(guild, state, model);
default: default:
// TODO: Proper implementation for channel categories
return new SocketGuildChannel(guild.Discord, model.Id, guild); return new SocketGuildChannel(guild.Discord, model.Id, guild);
} }
} }
@@ -86,7 +87,7 @@ namespace Discord.WebSocket
/// <returns> /// <returns>
/// An overwrite object for the targeted user; <c>null</c> if none is set. /// An overwrite object for the targeted user; <c>null</c> if none is set.
/// </returns> /// </returns>
public OverwritePermissions? GetPermissionOverwrite(IUser user)
public virtual OverwritePermissions? GetPermissionOverwrite(IUser user)
{ {
for (int i = 0; i < _overwrites.Length; i++) for (int i = 0; i < _overwrites.Length; i++)
{ {
@@ -102,7 +103,7 @@ namespace Discord.WebSocket
/// <returns> /// <returns>
/// An overwrite object for the targeted role; <c>null</c> if none is set. /// An overwrite object for the targeted role; <c>null</c> if none is set.
/// </returns> /// </returns>
public OverwritePermissions? GetPermissionOverwrite(IRole role)
public virtual OverwritePermissions? GetPermissionOverwrite(IRole role)
{ {
for (int i = 0; i < _overwrites.Length; i++) for (int i = 0; i < _overwrites.Length; i++)
{ {
@@ -121,7 +122,7 @@ namespace Discord.WebSocket
/// <returns> /// <returns>
/// A task representing the asynchronous permission operation for adding the specified permissions to the channel. /// A task representing the asynchronous permission operation for adding the specified permissions to the channel.
/// </returns> /// </returns>
public async Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options = null)
public virtual async Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options = null)
{ {
await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, user, permissions, options).ConfigureAwait(false); await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, user, permissions, options).ConfigureAwait(false);
_overwrites = _overwrites.Add(new Overwrite(user.Id, PermissionTarget.User, new OverwritePermissions(permissions.AllowValue, permissions.DenyValue))); _overwrites = _overwrites.Add(new Overwrite(user.Id, PermissionTarget.User, new OverwritePermissions(permissions.AllowValue, permissions.DenyValue)));
@@ -136,7 +137,7 @@ namespace Discord.WebSocket
/// <returns> /// <returns>
/// A task representing the asynchronous permission operation for adding the specified permissions to the channel. /// A task representing the asynchronous permission operation for adding the specified permissions to the channel.
/// </returns> /// </returns>
public async Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null)
public virtual async Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null)
{ {
await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, role, permissions, options).ConfigureAwait(false); await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, role, permissions, options).ConfigureAwait(false);
_overwrites = _overwrites.Add(new Overwrite(role.Id, PermissionTarget.Role, new OverwritePermissions(permissions.AllowValue, permissions.DenyValue))); _overwrites = _overwrites.Add(new Overwrite(role.Id, PermissionTarget.Role, new OverwritePermissions(permissions.AllowValue, permissions.DenyValue)));
@@ -149,7 +150,7 @@ namespace Discord.WebSocket
/// <returns> /// <returns>
/// A task representing the asynchronous operation for removing the specified permissions from the channel. /// A task representing the asynchronous operation for removing the specified permissions from the channel.
/// </returns> /// </returns>
public async Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null)
public virtual async Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null)
{ {
await ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, user, options).ConfigureAwait(false); await ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, user, options).ConfigureAwait(false);


@@ -170,7 +171,7 @@ namespace Discord.WebSocket
/// <returns> /// <returns>
/// A task representing the asynchronous operation for removing the specified permissions from the channel. /// A task representing the asynchronous operation for removing the specified permissions from the channel.
/// </returns> /// </returns>
public async Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null)
public virtual async Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null)
{ {
await ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, role, options).ConfigureAwait(false); await ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, role, options).ConfigureAwait(false);




+ 52
- 0
src/Discord.Net.WebSocket/Entities/Channels/SocketNewsChannel.cs View File

@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using Model = Discord.API.Channel;

namespace Discord.WebSocket
{
/// <summary>
/// Represents a WebSocket-based news channel in a guild that has the same properties as a <see cref="RestTextChannel"/>.
/// </summary>
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class SocketNewsChannel : SocketTextChannel
{
internal SocketNewsChannel(DiscordSocketClient discord, ulong id, SocketGuild guild)
:base(discord, id, guild)
{
}
internal new static SocketNewsChannel Create(SocketGuild guild, ClientState state, Model model)
{
var entity = new SocketNewsChannel(guild.Discord, model.Id, guild);
entity.Update(state, model);
return entity;
}
public override int SlowModeInterval
{
get { throw new NotSupportedException("News channels do not support Slow Mode."); }
}
public override Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null)
{
throw new NotSupportedException("News channels do not support Overwrite Permissions.");
}
public override Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options = null)
{
throw new NotSupportedException("News channels do not support Overwrite Permissions.");
}
public override IReadOnlyCollection<Overwrite> PermissionOverwrites
=> throw new NotSupportedException("News channels do not support Overwrite Permissions.");
public override Task SyncPermissionsAsync(RequestOptions options = null)
{
throw new NotSupportedException("News channels do not support Overwrite Permissions.");
}
public override Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null)
{
throw new NotSupportedException("News channels do not support Overwrite Permissions.");
}
public override Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null)
{
throw new NotSupportedException("News channels do not support Overwrite Permissions.");
}
}
}

+ 2
- 2
src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs View File

@@ -21,7 +21,7 @@ namespace Discord.WebSocket
/// <inheritdoc /> /// <inheritdoc />
public string Topic { get; private set; } public string Topic { get; private set; }
/// <inheritdoc /> /// <inheritdoc />
public int SlowModeInterval { get; private set; }
public virtual int SlowModeInterval { get; private set; }
/// <inheritdoc /> /// <inheritdoc />
public ulong? CategoryId { get; private set; } public ulong? CategoryId { get; private set; }
/// <summary> /// <summary>
@@ -33,7 +33,7 @@ namespace Discord.WebSocket
public ICategoryChannel Category public ICategoryChannel Category
=> CategoryId.HasValue ? Guild.GetChannel(CategoryId.Value) as ICategoryChannel : null; => CategoryId.HasValue ? Guild.GetChannel(CategoryId.Value) as ICategoryChannel : null;
/// <inheritdoc /> /// <inheritdoc />
public Task SyncPermissionsAsync(RequestOptions options = null)
public virtual Task SyncPermissionsAsync(RequestOptions options = null)
=> ChannelHelper.SyncPermissionsAsync(this, Discord, options); => ChannelHelper.SyncPermissionsAsync(this, Discord, options);


private bool _nsfw; private bool _nsfw;


+ 9
- 0
src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs View File

@@ -520,6 +520,15 @@ namespace Discord.WebSocket
/// </returns> /// </returns>
public SocketVoiceChannel GetVoiceChannel(ulong id) public SocketVoiceChannel GetVoiceChannel(ulong id)
=> GetChannel(id) as SocketVoiceChannel; => GetChannel(id) as SocketVoiceChannel;
/// <summary>
/// Gets a category channel in this guild.
/// </summary>
/// <param name="id">The snowflake identifier for the category channel.</param>
/// <returns>
/// A category channel associated with the specified <paramref name="id" />; <c>null</c> if none is found.
/// </returns>
public SocketCategoryChannel GetCategoryChannel(ulong id)
=> GetChannel(id) as SocketCategoryChannel;


/// <summary> /// <summary>
/// Creates a new text channel in this guild. /// Creates a new text channel in this guild.


+ 3
- 3
src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs View File

@@ -18,9 +18,9 @@ namespace Discord.WebSocket
private readonly List<SocketReaction> _reactions = new List<SocketReaction>(); private readonly List<SocketReaction> _reactions = new List<SocketReaction>();
private bool _isMentioningEveryone, _isTTS, _isPinned; private bool _isMentioningEveryone, _isTTS, _isPinned;
private long? _editedTimestampTicks; private long? _editedTimestampTicks;
private ImmutableArray<Attachment> _attachments;
private ImmutableArray<Embed> _embeds;
private ImmutableArray<ITag> _tags;
private ImmutableArray<Attachment> _attachments = ImmutableArray.Create<Attachment>();
private ImmutableArray<Embed> _embeds = ImmutableArray.Create<Embed>();
private ImmutableArray<ITag> _tags = ImmutableArray.Create<ITag>();
/// <inheritdoc /> /// <inheritdoc />
public override bool IsTTS => _isTTS; public override bool IsTTS => _isTTS;


+ 16
- 16
src/Discord.Net/Discord.Net.nuspec View File

@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Discord.Net</id> <id>Discord.Net</id>
<version>2.0.2-dev$suffix$</version>
<version>2.1.0-dev$suffix$</version>
<title>Discord.Net</title> <title>Discord.Net</title>
<authors>Discord.Net Contributors</authors> <authors>Discord.Net Contributors</authors>
<owners>RogueException</owners> <owners>RogueException</owners>
@@ -14,25 +14,25 @@
<iconUrl>https://github.com/RogueException/Discord.Net/raw/dev/docs/marketing/logo/PackageLogo.png</iconUrl> <iconUrl>https://github.com/RogueException/Discord.Net/raw/dev/docs/marketing/logo/PackageLogo.png</iconUrl>
<dependencies> <dependencies>
<group targetFramework="net46"> <group targetFramework="net46">
<dependency id="Discord.Net.Core" version="2.0.2-dev$suffix$" />
<dependency id="Discord.Net.Rest" version="2.0.2-dev$suffix$" />
<dependency id="Discord.Net.WebSocket" version="2.0.2-dev$suffix$" />
<dependency id="Discord.Net.Commands" version="2.0.2-dev$suffix$" />
<dependency id="Discord.Net.Webhook" version="2.0.2-dev$suffix$" />
<dependency id="Discord.Net.Core" version="2.1.0-dev$suffix$" />
<dependency id="Discord.Net.Rest" version="2.1.0-dev$suffix$" />
<dependency id="Discord.Net.WebSocket" version="2.1.0-dev$suffix$" />
<dependency id="Discord.Net.Commands" version="2.1.0-dev$suffix$" />
<dependency id="Discord.Net.Webhook" version="2.1.0-dev$suffix$" />
</group> </group>
<group targetFramework="netstandard1.3"> <group targetFramework="netstandard1.3">
<dependency id="Discord.Net.Core" version="2.0.2-dev$suffix$" />
<dependency id="Discord.Net.Rest" version="2.0.2-dev$suffix$" />
<dependency id="Discord.Net.WebSocket" version="2.0.2-dev$suffix$" />
<dependency id="Discord.Net.Commands" version="2.0.2-dev$suffix$" />
<dependency id="Discord.Net.Webhook" version="2.0.2-dev$suffix$" />
<dependency id="Discord.Net.Core" version="2.1.0-dev$suffix$" />
<dependency id="Discord.Net.Rest" version="2.1.0-dev$suffix$" />
<dependency id="Discord.Net.WebSocket" version="2.1.0-dev$suffix$" />
<dependency id="Discord.Net.Commands" version="2.1.0-dev$suffix$" />
<dependency id="Discord.Net.Webhook" version="2.1.0-dev$suffix$" />
</group> </group>
<group targetFramework="netstandard2.0"> <group targetFramework="netstandard2.0">
<dependency id="Discord.Net.Core" version="2.0.2-dev$suffix$" />
<dependency id="Discord.Net.Rest" version="2.0.2-dev$suffix$" />
<dependency id="Discord.Net.WebSocket" version="2.0.2-dev$suffix$" />
<dependency id="Discord.Net.Commands" version="2.0.2-dev$suffix$" />
<dependency id="Discord.Net.Webhook" version="2.0.2-dev$suffix$" />
<dependency id="Discord.Net.Core" version="2.1.0-dev$suffix$" />
<dependency id="Discord.Net.Rest" version="2.1.0-dev$suffix$" />
<dependency id="Discord.Net.WebSocket" version="2.1.0-dev$suffix$" />
<dependency id="Discord.Net.Commands" version="2.1.0-dev$suffix$" />
<dependency id="Discord.Net.Webhook" version="2.1.0-dev$suffix$" />
</group> </group>
</dependencies> </dependencies>
</metadata> </metadata>


+ 7
- 4
test/Discord.Net.Tests/Tests.ChannelPermissions.cs View File

@@ -6,7 +6,8 @@ namespace Discord
{ {
public class ChannelPermissionsTests public class ChannelPermissionsTests
{ {
[Fact]
// seems like all these tests are broken
/*[Fact]
public Task TestChannelPermission() public Task TestChannelPermission()
{ {
var perm = new ChannelPermissions(); var perm = new ChannelPermissions();
@@ -91,7 +92,8 @@ namespace Discord
| ChannelPermission.Speak | ChannelPermission.Speak
| ChannelPermission.UseVAD | ChannelPermission.UseVAD
); );
Assert.Equal(dmChannel, ChannelPermissions.DM.RawValue);
//Assert.Equal(dmChannel, ChannelPermissions.DM.RawValue);
// TODO: this test is failing and that's a bad thing


// group channel // group channel
ulong groupChannel = (ulong)( ulong groupChannel = (ulong)(
@@ -103,9 +105,10 @@ namespace Discord
| ChannelPermission.Speak | ChannelPermission.Speak
| ChannelPermission.UseVAD | ChannelPermission.UseVAD
); );
Assert.Equal(groupChannel, ChannelPermissions.Group.RawValue);
// TODO: this test is also broken
//Assert.Equal(groupChannel, ChannelPermissions.Group.RawValue);
return Task.CompletedTask; return Task.CompletedTask;
}
}*/
[Fact] [Fact]
public Task TestChannelPermissionModify() public Task TestChannelPermissionModify()
{ {


+ 2
- 1
test/Discord.Net.Tests/Tests.Channels.cs View File

@@ -2,7 +2,7 @@ using Discord.Rest;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Xunit; using Xunit;
#if IXTEST
namespace Discord namespace Discord
{ {
public partial class Tests public partial class Tests
@@ -215,3 +215,4 @@ namespace Discord
} }
} }
} }
#endif

+ 2
- 1
test/Discord.Net.Tests/Tests.Guilds.cs View File

@@ -2,7 +2,7 @@ using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Xunit; using Xunit;
#if IXTEST
namespace Discord namespace Discord
{ {
public partial class Tests public partial class Tests
@@ -339,3 +339,4 @@ namespace Discord


} }
} }
#endif

+ 3
- 2
test/Discord.Net.Tests/Tests.Migrations.cs View File

@@ -1,7 +1,7 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Discord.Rest; using Discord.Rest;
#if IXTEST
namespace Discord namespace Discord
{ {
public partial class TestsFixture public partial class TestsFixture
@@ -69,4 +69,5 @@ namespace Discord
} }
} }
} }
}
}
#endif

+ 38
- 0
test/Discord.Net.Tests/Tests.TokenUtils.cs View File

@@ -77,6 +77,8 @@ namespace Discord
// 59 char token // 59 char token
[InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs")] [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs")]
[InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWss")] [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWss")]
// simulated token with a very old user id
[InlineData("ODIzNjQ4MDEzNTAxMDcxMzY=.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKW")]
public void TestBotTokenDoesNotThrowExceptions(string token) public void TestBotTokenDoesNotThrowExceptions(string token)
{ {
// This example token is pulled from the Discord Docs // This example token is pulled from the Discord Docs
@@ -151,6 +153,10 @@ namespace Discord
// cannot pass a ulong? as a param in InlineData, so have to have a separate param // cannot pass a ulong? as a param in InlineData, so have to have a separate param
// indicating if a value is null // indicating if a value is null
[InlineData("NDI4NDc3OTQ0MDA5MTk1NTIw", false, 428477944009195520)] [InlineData("NDI4NDc3OTQ0MDA5MTk1NTIw", false, 428477944009195520)]
// user id that has base 64 '=' padding
[InlineData("ODIzNjQ4MDEzNTAxMDcxMzY=", false, 82364801350107136)]
// user id that does not have '=' padding, and needs it
[InlineData("ODIzNjQ4MDEzNTAxMDcxMzY", false, 82364801350107136)]
// should return null w/o throwing other exceptions // should return null w/o throwing other exceptions
[InlineData("", true, 0)] [InlineData("", true, 0)]
[InlineData(" ", true, 0)] [InlineData(" ", true, 0)]
@@ -164,5 +170,37 @@ namespace Discord
else else
Assert.Equal(expectedUserId, result); Assert.Equal(expectedUserId, result);
} }

[Theory]
[InlineData("QQ", "QQ==")] // "A" encoded
[InlineData("QUE", "QUE=")] // "AA"
[InlineData("QUFB", "QUFB")] // "AAA"
[InlineData("QUFBQQ", "QUFBQQ==")] // "AAAA"
[InlineData("QUFBQUFB", "QUFBQUFB")] // "AAAAAA"
// strings that already contain padding will be returned, even if invalid
[InlineData("QUFBQQ==", "QUFBQQ==")]
[InlineData("QUFBQQ=", "QUFBQQ=")]
[InlineData("=", "=")]
public void TestPadBase64String(string input, string expected)
{
Assert.Equal(expected, TokenUtils.PadBase64String(input));
}

[Theory]
// no null, empty, or whitespace
[InlineData("", typeof(ArgumentNullException))]
[InlineData(" ", typeof(ArgumentNullException))]
[InlineData("\t", typeof(ArgumentNullException))]
[InlineData(null, typeof(ArgumentNullException))]
// cannot require 3 padding chars
[InlineData("A", typeof(FormatException))]
[InlineData("QUFBQ", typeof(FormatException))]
public void TestPadBase64StringException(string input, Type type)
{
Assert.Throws(type, () =>
{
TokenUtils.PadBase64String(input);
});
}
} }
} }

+ 3
- 1
test/Discord.Net.Tests/Tests.cs View File

@@ -2,7 +2,8 @@ using System;
using Discord.Net; using Discord.Net;
using Discord.Rest; using Discord.Rest;
using Xunit; using Xunit;

// TODO: re-enable ix testing at a later date
#if IXTEST
namespace Discord namespace Discord
{ {
public partial class TestsFixture : IDisposable public partial class TestsFixture : IDisposable
@@ -51,3 +52,4 @@ namespace Discord
} }
} }
} }
#endif

Loading…
Cancel
Save