Browse Source

Merge branch 'dev' into Public-Client

pull/1283/head
Casino Boyale GitHub 6 years ago
parent
commit
90da4115e6
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 524 additions and 178 deletions
  1. +1
    -1
      Discord.Net.targets
  2. +1
    -1
      LICENSE
  3. +2
    -2
      README.md
  4. +0
    -94
      appveyor.yml
  5. +31
    -0
      azure-pipelines.yml
  6. +17
    -0
      azure/build.yml
  7. +32
    -0
      azure/deploy.yml
  8. +5
    -4
      docs/_overwrites/Common/EmbedBuilder.Overwrites.md
  9. +1
    -1
      docs/_template/light-dark-theme/styles/dark.css
  10. +1
    -1
      docs/_template/light-dark-theme/styles/gray.css
  11. +1
    -1
      docs/_template/light-dark-theme/styles/light.css
  12. +1
    -1
      docs/docfx.json
  13. +1
    -0
      samples/02_commands_framework/Modules/PublicModule.cs
  14. +3
    -1
      samples/02_commands_framework/Program.cs
  15. +15
    -3
      samples/02_commands_framework/Services/CommandHandlingService.cs
  16. +3
    -3
      src/Discord.Net.Core/Discord.Net.Core.csproj
  17. +3
    -1
      src/Discord.Net.Core/Entities/Channels/ChannelType.cs
  18. +5
    -1
      src/Discord.Net.Core/Entities/Messages/MessageType.cs
  19. +44
    -1
      src/Discord.Net.Core/Utils/TokenUtils.cs
  20. +11
    -3
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageDeleteAuditLogData.cs
  21. +14
    -5
      src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookCreateAuditLogData.cs
  22. +1
    -0
      src/Discord.Net.Rest/Entities/Channels/RestChannel.cs
  23. +9
    -7
      src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs
  24. +53
    -0
      src/Discord.Net.Rest/Entities/Channels/RestNewsChannel.cs
  25. +1
    -1
      src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs
  26. +4
    -4
      src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs
  27. +9
    -5
      src/Discord.Net.WebSocket/DiscordSocketClient.cs
  28. +9
    -7
      src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs
  29. +52
    -0
      src/Discord.Net.WebSocket/Entities/Channels/SocketNewsChannel.cs
  30. +1
    -1
      src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs
  31. +9
    -0
      src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
  32. +3
    -3
      src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs
  33. +48
    -0
      src/Discord.Net.Webhook/DiscordWebhookClient.cs
  34. +16
    -16
      src/Discord.Net/Discord.Net.nuspec
  35. +1
    -0
      test/Discord.Net.Tests/Discord.Net.Tests.csproj
  36. +7
    -4
      test/Discord.Net.Tests/Tests.ChannelPermissions.cs
  37. +2
    -1
      test/Discord.Net.Tests/Tests.Channels.cs
  38. +60
    -0
      test/Discord.Net.Tests/Tests.DiscordWebhookClient.cs
  39. +2
    -1
      test/Discord.Net.Tests/Tests.Guilds.cs
  40. +3
    -2
      test/Discord.Net.Tests/Tests.Migrations.cs
  41. +38
    -0
      test/Discord.Net.Tests/Tests.TokenUtils.cs
  42. +4
    -2
      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">
<PropertyGroup>
<VersionPrefix>2.0.2</VersionPrefix>
<VersionPrefix>2.1.0</VersionPrefix>
<VersionSuffix>dev</VersionSuffix>
<Authors>RogueException</Authors>
<PackageTags>discord;discordapp</PackageTags>


+ 1
- 1
LICENSE View File

@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2015-2017 Discord.Net Contributors
Copyright (c) 2015-2019 Discord.Net Contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal


+ 2
- 2
README.md View File

@@ -1,12 +1,12 @@
# 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)
[![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)

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
### 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!\"")
.WithDescription("I am a description.")
.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();
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 {
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 {
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 {
background: url("/marketing/logo/SVG/Combinationmark.svg") no-repeat;
background: url("../marketing/logo/SVG/Combinationmark.svg") no-repeat;
}

+ 1
- 1
docs/docfx.json View File

@@ -50,7 +50,7 @@
"overwrite": "_overwrites/**/**.md",
"globalMetadata": {
"_appTitle": "Discord.Net Documentation",
"_appFooter": "Discord.Net (c) 2015-2018 2.0.0-beta",
"_appFooter": "Discord.Net (c) 2015-2019 2.0.1",
"_enableSearch": true,
"_appLogoPath": "marketing/logo/SVG/Logomark Purple.svg",
"_appFaviconPath": "favicon.ico"


+ 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)
=> ReplyAsync("You listed: " + string.Join("; ", objects));

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


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

@@ -37,10 +37,12 @@ namespace _02_commands_framework
client.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.StartAsync();

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

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>();
_services = services;

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

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

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

// This value holds the offset where the prefix ends
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;

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)
@@ -49,12 +61,12 @@ namespace _02_commands_framework.Services
if (!command.IsSpecified)
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)
return;

// 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="System.Collections.Immutable" Version="1.3.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>
</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>
Group = 3,
/// <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>
/// The message when another message is pinned.
/// </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>
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>
/// Decodes a base 64 encoded string into a ulong value.
/// </summary>
@@ -29,6 +70,8 @@ namespace Discord

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

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

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>
@@ -34,5 +35,12 @@ namespace Discord.Rest
/// deleted from.
/// </returns>
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>
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;
WebhookId = webhookId;
Name = name;
Type = type;
ChannelId = channelId;
@@ -31,23 +32,31 @@ namespace Discord.Rest
var name = nameModel.NewValue.ToObject<string>(discord.ApiClient.Serializer);

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

/// <summary>
/// Gets the webhook that was created.
/// Gets the webhook that was created if it still exists.
/// </summary>
/// <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>
public IWebhook Webhook { get; }

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

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

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


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

@@ -23,6 +23,7 @@ namespace Discord.Rest
{
switch (model.Type)
{
case ChannelType.News:
case ChannelType.Text:
case ChannelType.Voice:
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;

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

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

@@ -164,7 +166,7 @@ namespace Discord.Rest
/// <returns>
/// A task representing the asynchronous operation for removing the specified permissions from the channel.
/// </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);



+ 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 />
public string Topic { get; private set; }
/// <inheritdoc />
public int SlowModeInterval { get; private set; }
public virtual int SlowModeInterval { get; private set; }
/// <inheritdoc />
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 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 />
public override bool IsTTS => _isTTS;


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

@@ -83,7 +83,7 @@ namespace Discord.WebSocket
/// </note>
/// </remarks>
/// <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>
public IReadOnlyCollection<SocketDMChannel> DMChannels
=> State.PrivateChannels.OfType<SocketDMChannel>().ToImmutableArray();
@@ -98,7 +98,7 @@ namespace Discord.WebSocket
/// </note>
/// </remarks>
/// <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>
public IReadOnlyCollection<SocketGroupChannel> GroupChannels
=> State.PrivateChannels.OfType<SocketGroupChannel>().ToImmutableArray();
@@ -1173,9 +1173,13 @@ namespace Discord.WebSocket
{
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)
author = (channel as SocketGroupChannel).GetOrAddUser(data.Author.Value);


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

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

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

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

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

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


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

@@ -520,6 +520,15 @@ namespace Discord.WebSocket
/// </returns>
public SocketVoiceChannel GetVoiceChannel(ulong id)
=> 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>
/// 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 bool _isMentioningEveryone, _isTTS, _isPinned;
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 />
public override bool IsTTS => _isTTS;


+ 48
- 0
src/Discord.Net.Webhook/DiscordWebhookClient.cs View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Discord.Logging;
using Discord.Rest;
@@ -26,6 +27,12 @@ namespace Discord.Webhook
/// <summary> Creates a new Webhook Discord client. </summary>
public DiscordWebhookClient(ulong webhookId, string webhookToken)
: this(webhookId, webhookToken, new DiscordRestConfig()) { }
/// <summary> Creates a new Webhook Discord client. </summary>
public DiscordWebhookClient(string webhookUrl)
: this(webhookUrl, new DiscordRestConfig()) { }

// regex pattern to match webhook urls
private static Regex WebhookUrlRegex = new Regex(@"^.*discordapp\.com\/api\/webhooks\/([\d]+)\/([a-z0-9_-]+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);

/// <summary> Creates a new Webhook Discord client. </summary>
public DiscordWebhookClient(ulong webhookId, string webhookToken, DiscordRestConfig config)
@@ -43,6 +50,21 @@ namespace Discord.Webhook
_webhookId = Webhook.Id;
}

/// <summary>
/// Creates a new Webhook Discord client.
/// </summary>
/// <param name="webhookUrl">The url of the webhook.</param>
/// <param name="config">The configuration options to use for this client.</param>
/// <exception cref="ArgumentException">Thrown if the <paramref name="webhookUrl"/> is an invalid format.</exception>
/// <exception cref="ArgumentNullException">Thrown if the <paramref name="webhookUrl"/> is null or whitespace.</exception>
public DiscordWebhookClient(string webhookUrl, DiscordRestConfig config) : this(config)
{
string token;
ParseWebhookUrl(webhookUrl, out _webhookId, out token);
ApiClient.LoginAsync(TokenType.Webhook, token).GetAwaiter().GetResult();
Webhook = WebhookClientHelper.GetWebhookAsync(this, _webhookId).GetAwaiter().GetResult();
}

private DiscordWebhookClient(DiscordRestConfig config)
{
ApiClient = CreateApiClient(config);
@@ -94,5 +116,31 @@ namespace Discord.Webhook
{
ApiClient?.Dispose();
}

internal static void ParseWebhookUrl(string webhookUrl, out ulong webhookId, out string webhookToken)
{
if (string.IsNullOrWhiteSpace(webhookUrl))
throw new ArgumentNullException(paramName: nameof(webhookUrl), message:
"The given webhook Url cannot be null or whitespace.");

// thrown when groups are not populated/valid, or when there is no match
ArgumentException ex(string reason = null)
=> new ArgumentException(paramName: nameof(webhookUrl), message:
$"The given webhook Url was not in a valid format. {reason}");
var match = WebhookUrlRegex.Match(webhookUrl);
if (match != null)
{
// ensure that the first group is a ulong, set the _webhookId
// 0th group is always the entire match, so start at index 1
if (!(match.Groups[1].Success && ulong.TryParse(match.Groups[1].Value, out webhookId)))
throw ex("The webhook Id could not be parsed.");

if (!match.Groups[2].Success)
throw ex("The webhook token could not be parsed.");
webhookToken = match.Groups[2].Value;
}
else
throw ex();
}
}
}

+ 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">
<metadata>
<id>Discord.Net</id>
<version>2.0.2-dev$suffix$</version>
<version>2.1.0-dev$suffix$</version>
<title>Discord.Net</title>
<authors>Discord.Net Contributors</authors>
<owners>RogueException</owners>
@@ -14,25 +14,25 @@
<iconUrl>https://github.com/RogueException/Discord.Net/raw/dev/docs/marketing/logo/PackageLogo.png</iconUrl>
<dependencies>
<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 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 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>
</dependencies>
</metadata>


+ 1
- 0
test/Discord.Net.Tests/Discord.Net.Tests.csproj View File

@@ -19,6 +19,7 @@
<ProjectReference Include="../../src/Discord.Net.Core/Discord.Net.Core.csproj" />
<ProjectReference Include="../../src/Discord.Net.Rest/Discord.Net.Rest.csproj" />
<ProjectReference Include="../../src/Discord.Net.Analyzers/Discord.Net.Analyzers.csproj" />
<ProjectReference Include="..\..\src\Discord.Net.Webhook\Discord.Net.Webhook.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Akavache" Version="6.0.31" />


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

@@ -6,7 +6,8 @@ namespace Discord
{
public class ChannelPermissionsTests
{
[Fact]
// seems like all these tests are broken
/*[Fact]
public Task TestChannelPermission()
{
var perm = new ChannelPermissions();
@@ -91,7 +92,8 @@ namespace Discord
| ChannelPermission.Speak
| 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
ulong groupChannel = (ulong)(
@@ -103,9 +105,10 @@ namespace Discord
| ChannelPermission.Speak
| ChannelPermission.UseVAD
);
Assert.Equal(groupChannel, ChannelPermissions.Group.RawValue);
// TODO: this test is also broken
//Assert.Equal(groupChannel, ChannelPermissions.Group.RawValue);
return Task.CompletedTask;
}
}*/
[Fact]
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.Threading.Tasks;
using Xunit;
#if IXTEST
namespace Discord
{
public partial class Tests
@@ -215,3 +215,4 @@ namespace Discord
}
}
}
#endif

+ 60
- 0
test/Discord.Net.Tests/Tests.DiscordWebhookClient.cs View File

@@ -0,0 +1,60 @@
using Discord.Webhook;
using System;
using System.Collections.Generic;
using System.Text;
using Xunit;

namespace Discord
{
/// <summary>
/// Tests the <see cref="DiscordWebhookClient.ParseWebhookUrl(string, out ulong, out string)"/> function.
/// </summary>
public class DiscordWebhookClientTests
{
[Theory]
[InlineData("https://discordapp.com/api/webhooks/123412347732897802/_abcde123456789-ABCDEFGHIJKLMNOP12345678-abcdefghijklmnopABCDEFGHIJK",
123412347732897802, "_abcde123456789-ABCDEFGHIJKLMNOP12345678-abcdefghijklmnopABCDEFGHIJK")]
// ptb, canary, etc will have slightly different urls
[InlineData("https://ptb.discordapp.com/api/webhooks/123412347732897802/_abcde123456789-ABCDEFGHIJKLMNOP12345678-abcdefghijklmnopABCDEFGHIJK",
123412347732897802, "_abcde123456789-ABCDEFGHIJKLMNOP12345678-abcdefghijklmnopABCDEFGHIJK")]
[InlineData("https://canary.discordapp.com/api/webhooks/123412347732897802/_abcde123456789-ABCDEFGHIJKLMNOP12345678-abcdefghijklmnopABCDEFGHIJK",
123412347732897802, "_abcde123456789-ABCDEFGHIJKLMNOP12345678-abcdefghijklmnopABCDEFGHIJK")]
// don't care about https
[InlineData("http://canary.discordapp.com/api/webhooks/123412347732897802/_abcde123456789-ABCDEFGHIJKLMNOP12345678-abcdefghijklmnopABCDEFGHIJK",
123412347732897802, "_abcde123456789-ABCDEFGHIJKLMNOP12345678-abcdefghijklmnopABCDEFGHIJK")]
// this is the minimum that the regex cares about
[InlineData("discordapp.com/api/webhooks/123412347732897802/_abcde123456789-ABCDEFGHIJKLMNOP12345678-abcdefghijklmnopABCDEFGHIJK",
123412347732897802, "_abcde123456789-ABCDEFGHIJKLMNOP12345678-abcdefghijklmnopABCDEFGHIJK")]
public void TestWebhook_Valid(string webhookurl, ulong expectedId, string expectedToken)
{
DiscordWebhookClient.ParseWebhookUrl(webhookurl, out ulong id, out string token);

Assert.Equal(expectedId, id);
Assert.Equal(expectedToken, token);
}

[Theory]
[InlineData("")]
[InlineData(" ")]
[InlineData(null)]
public void TestWebhook_Null(string webhookurl)
{
Assert.Throws<ArgumentNullException>(() =>
{
DiscordWebhookClient.ParseWebhookUrl(webhookurl, out ulong id, out string token);
});
}

[Theory]
[InlineData("123412347732897802/_abcde123456789-ABCDEFGHIJKLMNOP12345678-abcdefghijklmnopABCDEFGHIJK")]
// trailing slash
[InlineData("https://discordapp.com/api/webhooks/123412347732897802/_abcde123456789-ABCDEFGHIJKLMNOP12345678-abcdefghijklmnopABCDEFGHIJK/")]
public void TestWebhook_Invalid(string webhookurl)
{
Assert.Throws<ArgumentException>(() =>
{
DiscordWebhookClient.ParseWebhookUrl(webhookurl, out ulong id, out string token);
});
}
}
}

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

@@ -2,7 +2,7 @@ using System;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
#if IXTEST
namespace Discord
{
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.Threading.Tasks;
using Discord.Rest;
#if IXTEST
namespace Discord
{
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
[InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs")]
[InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWss")]
// simulated token with a very old user id
[InlineData("ODIzNjQ4MDEzNTAxMDcxMzY=.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKW")]
public void TestBotTokenDoesNotThrowExceptions(string token)
{
// 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
// indicating if a value is null
[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
[InlineData("", true, 0)]
[InlineData(" ", true, 0)]
@@ -164,5 +170,37 @@ namespace Discord
else
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);
});
}
}
}

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

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

// TODO: re-enable ix testing at a later date
#if IXTEST
namespace Discord
{
public partial class TestsFixture : IDisposable
@@ -50,4 +51,5 @@ namespace Discord
_guild = fixture._guild;
}
}
}
}
#endif

Loading…
Cancel
Save