@@ -34,6 +34,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "03_sharded_client", "sample | |||||
EndProject | EndProject | ||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "04_webhook_client", "samples\04_webhook_client\04_webhook_client.csproj", "{88B77A5B-0BC0-4E99-8FD9-D83F6999F562}" | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "04_webhook_client", "samples\04_webhook_client\04_webhook_client.csproj", "{88B77A5B-0BC0-4E99-8FD9-D83F6999F562}" | ||||
EndProject | EndProject | ||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{D1F0271E-0EE2-4B66-AC3D-9871B7E1C4CF}" | |||||
EndProject | |||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Examples", "src\Discord.Net.Examples\Discord.Net.Examples.csproj", "{7EA96B2B-4D71-458D-9423-839362DC38BE}" | |||||
EndProject | |||||
Global | Global | ||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
Debug|Any CPU = Debug|Any CPU | Debug|Any CPU = Debug|Any CPU | ||||
@@ -188,6 +192,18 @@ Global | |||||
{88B77A5B-0BC0-4E99-8FD9-D83F6999F562}.Release|x64.Build.0 = Release|Any CPU | {88B77A5B-0BC0-4E99-8FD9-D83F6999F562}.Release|x64.Build.0 = Release|Any CPU | ||||
{88B77A5B-0BC0-4E99-8FD9-D83F6999F562}.Release|x86.ActiveCfg = Release|Any CPU | {88B77A5B-0BC0-4E99-8FD9-D83F6999F562}.Release|x86.ActiveCfg = Release|Any CPU | ||||
{88B77A5B-0BC0-4E99-8FD9-D83F6999F562}.Release|x86.Build.0 = Release|Any CPU | {88B77A5B-0BC0-4E99-8FD9-D83F6999F562}.Release|x86.Build.0 = Release|Any CPU | ||||
{7EA96B2B-4D71-458D-9423-839362DC38BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||||
{7EA96B2B-4D71-458D-9423-839362DC38BE}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||||
{7EA96B2B-4D71-458D-9423-839362DC38BE}.Debug|x64.ActiveCfg = Debug|Any CPU | |||||
{7EA96B2B-4D71-458D-9423-839362DC38BE}.Debug|x64.Build.0 = Debug|Any CPU | |||||
{7EA96B2B-4D71-458D-9423-839362DC38BE}.Debug|x86.ActiveCfg = Debug|Any CPU | |||||
{7EA96B2B-4D71-458D-9423-839362DC38BE}.Debug|x86.Build.0 = Debug|Any CPU | |||||
{7EA96B2B-4D71-458D-9423-839362DC38BE}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||||
{7EA96B2B-4D71-458D-9423-839362DC38BE}.Release|Any CPU.Build.0 = Release|Any CPU | |||||
{7EA96B2B-4D71-458D-9423-839362DC38BE}.Release|x64.ActiveCfg = Release|Any CPU | |||||
{7EA96B2B-4D71-458D-9423-839362DC38BE}.Release|x64.Build.0 = Release|Any CPU | |||||
{7EA96B2B-4D71-458D-9423-839362DC38BE}.Release|x86.ActiveCfg = Release|Any CPU | |||||
{7EA96B2B-4D71-458D-9423-839362DC38BE}.Release|x86.Build.0 = Release|Any CPU | |||||
EndGlobalSection | EndGlobalSection | ||||
GlobalSection(SolutionProperties) = preSolution | GlobalSection(SolutionProperties) = preSolution | ||||
HideSolutionNode = FALSE | HideSolutionNode = FALSE | ||||
@@ -203,6 +219,7 @@ Global | |||||
{4E1F1F40-B1DD-40C9-A4B1-A2046A4C9C76} = {BB59D5B5-E7B0-4BF4-8F82-D14431B2799B} | {4E1F1F40-B1DD-40C9-A4B1-A2046A4C9C76} = {BB59D5B5-E7B0-4BF4-8F82-D14431B2799B} | ||||
{9B4C4AFB-3D15-44C6-9E36-12ED625AAA26} = {BB59D5B5-E7B0-4BF4-8F82-D14431B2799B} | {9B4C4AFB-3D15-44C6-9E36-12ED625AAA26} = {BB59D5B5-E7B0-4BF4-8F82-D14431B2799B} | ||||
{88B77A5B-0BC0-4E99-8FD9-D83F6999F562} = {BB59D5B5-E7B0-4BF4-8F82-D14431B2799B} | {88B77A5B-0BC0-4E99-8FD9-D83F6999F562} = {BB59D5B5-E7B0-4BF4-8F82-D14431B2799B} | ||||
{7EA96B2B-4D71-458D-9423-839362DC38BE} = {D1F0271E-0EE2-4B66-AC3D-9871B7E1C4CF} | |||||
EndGlobalSection | EndGlobalSection | ||||
GlobalSection(ExtensibilityGlobals) = postSolution | GlobalSection(ExtensibilityGlobals) = postSolution | ||||
SolutionGuid = {D2404771-EEC8-45F2-9D71-F3373F6C1495} | SolutionGuid = {D2404771-EEC8-45F2-9D71-F3373F6C1495} | ||||
@@ -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> | ||||
@@ -1,6 +1,6 @@ | |||||
The MIT License (MIT) | 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 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
of this software and associated documentation files (the "Software"), to deal | of this software and associated documentation files (the "Software"), to deal | ||||
@@ -1,12 +1,12 @@ | |||||
# Discord.Net | # Discord.Net | ||||
[](https://www.nuget.org/packages/Discord.Net) | [](https://www.nuget.org/packages/Discord.Net) | ||||
[](https://www.myget.org/feed/Packages/discord-net) | [](https://www.myget.org/feed/Packages/discord-net) | ||||
[](https://ci.appveyor.com/project/RogueException/discord-net/branch/dev) | |||||
[](https://dev.azure.com/discord-net/Discord.Net/_build/latest?definitionId=1&branchName=dev) | |||||
[](https://discord.gg/jkrBmQR) | [](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) | ||||
@@ -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 |
@@ -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 |
@@ -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' |
@@ -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 |
@@ -1,23 +1,46 @@ | |||||
# Contributing to Docs | # Contributing to Docs | ||||
## General Guidelines | |||||
First of all, thank you for your interest in contributing to our | |||||
documentation work. We really appreciate it! That being said, | |||||
there are several guidelines you should attempt to follow when adding | |||||
to/changing the documentation. | |||||
We do not have any strict conditions for writing documentation, | |||||
but keep these guidelines in mind: | |||||
## General Guidelines | |||||
* Keep code samples in the `guides/samples` folder | |||||
* Keep code samples in each section's `samples` folder | |||||
* When referencing an object in the API, link to its page in the | * When referencing an object in the API, link to its page in the | ||||
API documentation | API documentation | ||||
* Documentation should be written in an FAQ/Wiki-style format | * Documentation should be written in an FAQ/Wiki-style format | ||||
* Documentation should be written in clear and proper English* | * Documentation should be written in clear and proper English* | ||||
\* If anyone is interested in translating documentation into other | \* If anyone is interested in translating documentation into other | ||||
languages, please open an issue or contact me on | |||||
Discord (`foxbot#0282`). | |||||
languages, please open an issue or contact `foxbot#0282` on | |||||
Discord. | |||||
## XML Docstrings Guidelines | |||||
## Style Consistencies | |||||
* When using the `<summary>` tag, use concise verbs. For example: | |||||
```cs | |||||
/// <summary> Gets or sets the guild user in this object. </summary> | |||||
public IGuildUser GuildUser { get; set; } | |||||
``` | |||||
* Use a ruler set at 70 characters | |||||
* The `<summary>` tag should not be more than 3 lines long. Consider | |||||
simplifying the terms or using the `<remarks>` tag instead. | |||||
* When using the `<code>` tag, put the code sample within the | |||||
`src/Discord.Net.Examples` project under the corresponding path of | |||||
the object and surround the code with a `#region` tag. | |||||
* If the remarks you are looking to write are too long, consider | |||||
writing a shorter version in the XML docs while keeping the longer | |||||
version in the `overwrites` folder using the DocFX overwrites syntax. | |||||
* You may find an example of this in the samples provided within | |||||
the folder. | |||||
## Docs Guide Guidelines | |||||
* Use a ruler set at 70 characters (use the docs workspace provided | |||||
if you are using Visual Studio Code) | |||||
* Links should use long syntax | * Links should use long syntax | ||||
* Pages should be short and concise, not broad and long | * Pages should be short and concise, not broad and long | ||||
@@ -31,5 +54,7 @@ Please consult the [API Documentation] for more information. | |||||
## Recommended Reads | ## Recommended Reads | ||||
* http://docs.microsoft.com | |||||
* http://flask.pocoo.org/docs/0.12/ | |||||
* [Microsoft Docs](https://docs.microsoft.com) | |||||
* [Flask Docs](https://flask.pocoo.org/docs/1.0/) | |||||
* [DocFX Manual](https://dotnet.github.io/docfx/) | |||||
* [Sandcastle XML Guide](http://ewsoftware.github.io/XMLCommentsGuide) |
@@ -1,6 +1,6 @@ | |||||
A "precondidtion" in the command system is used to determine if a | |||||
A "precondition" in the command system is used to determine if a | |||||
condition is met before entering the command task. Using a | condition is met before entering the command task. Using a | ||||
precondidtion may aid in keeping a well-organized command logic. | |||||
precondition may aid in keeping a well-organized command logic. | |||||
The most common use case being whether a user has sufficient | The most common use case being whether a user has sufficient | ||||
permission to execute the command. | permission to execute the command. |
@@ -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,4 +1,4 @@ | |||||
<configuration> | <configuration> | ||||
<dllmap os="linux" cpu="x86-64" wordsize="64" dll="git2-8e0b172" target="lib/linux-x64/libgit2-8e0b172.so" /> | |||||
<dllmap os="osx" cpu="x86,x86-64" dll="git2-8e0b172" target="lib/osx/libgit2-8e0b172.dylib" /> | |||||
<dllmap os="linux" cpu="x86-64" wordsize="64" dll="git2-a904fc6" target="lib/linux-x64/libgit2-a904fc6.so" /> | |||||
<dllmap os="osx" cpu="x86,x86-64" dll="git2-a904fc6" target="lib/osx/libgit2-a904fc6.dylib" /> | |||||
</configuration> | </configuration> |
@@ -0,0 +1,21 @@ | |||||
MIT License | |||||
Copyright (c) 2019 Oscar Vásquez | |||||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
of this software and associated documentation files (the "Software"), to deal | |||||
in the Software without restriction, including without limitation the rights | |||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
copies of the Software, and to permit persons to whom the Software is | |||||
furnished to do so, subject to the following conditions: | |||||
The above copyright notice and this permission notice shall be included in all | |||||
copies or substantial portions of the Software. | |||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||||
SOFTWARE. |
@@ -15,6 +15,7 @@ | |||||
<link rel="stylesheet" href="{{_rel}}styles/docfx.css"> | <link rel="stylesheet" href="{{_rel}}styles/docfx.css"> | ||||
<link rel="stylesheet" href="{{_rel}}styles/master.css"> | <link rel="stylesheet" href="{{_rel}}styles/master.css"> | ||||
<link rel="stylesheet" href="{{_rel}}styles/main.css"> | <link rel="stylesheet" href="{{_rel}}styles/main.css"> | ||||
<link rel="stylesheet" href="{{_rel}}styles/material.css"> | |||||
<link rel="stylesheet" href="{{_rel}}styles/theme-switcher.css"> | <link rel="stylesheet" href="{{_rel}}styles/theme-switcher.css"> | ||||
<link href="https://cdn.rawgit.com/noelboss/featherlight/1.7.6/release/featherlight.min.css" type="text/css" rel="stylesheet" /> | <link href="https://cdn.rawgit.com/noelboss/featherlight/1.7.6/release/featherlight.min.css" type="text/css" rel="stylesheet" /> | ||||
<meta name="theme-color" content="#99AAB5"/> | <meta name="theme-color" content="#99AAB5"/> | ||||
@@ -7,6 +7,15 @@ body { | |||||
color: #C0C0C0; | color: #C0C0C0; | ||||
} | } | ||||
h1, | |||||
h2, | |||||
h3, | |||||
h4, | |||||
h5, | |||||
h6 { | |||||
color: #E0E0E0; | |||||
} | |||||
button, | button, | ||||
a { | a { | ||||
color: #64B5F6; | color: #64B5F6; | ||||
@@ -258,6 +267,11 @@ tbody>tr { | |||||
border-top: 2px solid rgb(173, 173, 173) | border-top: 2px solid rgb(173, 173, 173) | ||||
} | } | ||||
/* top navbar */ | |||||
.navbar-inverse[role="navigation"] { | |||||
background-color: #2C2F33; | |||||
} | |||||
/* select */ | /* select */ | ||||
select { | select { | ||||
@@ -304,5 +318,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; | |||||
} | } |
@@ -361,7 +361,6 @@ pre { | |||||
word-break: break-all; | word-break: break-all; | ||||
word-wrap: break-word; | word-wrap: break-word; | ||||
color: #333; | color: #333; | ||||
border: 1px solid #ccc; | |||||
border-radius: 4px; | border-radius: 4px; | ||||
} | } | ||||
@@ -7,6 +7,15 @@ body { | |||||
color: #dddddd; | color: #dddddd; | ||||
} | } | ||||
h1, | |||||
h2, | |||||
h3, | |||||
h4, | |||||
h5, | |||||
h6 { | |||||
color: #EEEEEE; | |||||
} | |||||
button, | button, | ||||
a { | a { | ||||
color: #64B5F6; | color: #64B5F6; | ||||
@@ -39,13 +48,13 @@ hr { | |||||
} | } | ||||
/* top navbar */ | /* top navbar */ | ||||
.navbar-inverse[role="navigation"] { | |||||
/*.navbar-inverse[role="navigation"] { | |||||
background-color: #2C2F33; | background-color: #2C2F33; | ||||
} | |||||
}*/ | |||||
/* sub navbar (below top) */ | /* sub navbar (below top) */ | ||||
.subnav { | .subnav { | ||||
background: #282B2F | |||||
background: rgb(69, 75, 82) | |||||
} | } | ||||
@@ -311,5 +320,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; | |||||
} | } |
@@ -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,14 +1,37 @@ | |||||
@import url('https://fonts.googleapis.com/css?family=Titillium+Web|Noto+Sans'); | |||||
@import url('https://fonts.googleapis.com/css?family=Roboto|Muli|Fira+Mono'); | |||||
html, | html, | ||||
body { | body { | ||||
font-family: 'Titillium Web', 'Segoe UI', Tahoma, Helvetica, sans-serif; | |||||
font-family: 'Roboto', 'Segoe UI', Tahoma, Helvetica, sans-serif; | |||||
font-display: optional; | font-display: optional; | ||||
height: 100%; | height: 100%; | ||||
font-size: 15px; | font-size: 15px; | ||||
scroll-behavior: smooth; | scroll-behavior: smooth; | ||||
} | } | ||||
code{ | |||||
font-family: 'Fira Mono', 'Courier New', Courier, monospace | |||||
} | |||||
h1, | |||||
h2, | |||||
h3, | |||||
h4, | |||||
h5, | |||||
h6 { | |||||
font-family: 'Muli', Verdana, Geneva, Tahoma, sans-serif; | |||||
line-height: 130%; | |||||
} | |||||
h1, | |||||
.h1, | |||||
h2, | |||||
.h2, | |||||
h3, | |||||
.h3 { | |||||
font-weight: 600; | |||||
} | |||||
#logo | #logo | ||||
{ | { | ||||
max-width: 100px; | max-width: 100px; | ||||
@@ -25,6 +48,14 @@ li, | |||||
line-height: 160%; | line-height: 160%; | ||||
} | } | ||||
.toc-filter{ | |||||
background: inherit !important; | |||||
} | |||||
.affix ul>li.active>ul, .affix ul>li.active>a:before, .affix ul>li>a:hover:before{ | |||||
white-space: normal; | |||||
} | |||||
img { | img { | ||||
box-shadow: 0px 0px 3px 0px rgb(66, 66, 66); | box-shadow: 0px 0px 3px 0px rgb(66, 66, 66); | ||||
max-width: 95% !important; | max-width: 95% !important; | ||||
@@ -57,16 +88,6 @@ article.content h6{ | |||||
transition: all .25s ease-in-out; | transition: all .25s ease-in-out; | ||||
} | } | ||||
h1, | |||||
h2, | |||||
h3, | |||||
h4, | |||||
h5, | |||||
h6 { | |||||
font-family: 'Noto Sans', Verdana, Geneva, Tahoma, sans-serif; | |||||
line-height: 130%; | |||||
} | |||||
.sideaffix { | .sideaffix { | ||||
line-height: 140%; | line-height: 140%; | ||||
} | } | ||||
@@ -173,3 +194,38 @@ span.arrow-d{ | |||||
span.arrow-r{ | span.arrow-r{ | ||||
top: 6px; position: relative; | top: 6px; position: relative; | ||||
} | } | ||||
/* widen viewport */ | |||||
@media (min-width: 1085px) { | |||||
.container { | |||||
width: calc(100% - 15vw); | |||||
max-width: calc(100% - 15vw); | |||||
} | |||||
} | |||||
/* fix level indentation */ | |||||
.level2 { | |||||
padding: 0 5px; | |||||
} | |||||
.level3 { | |||||
padding: 0 5px; | |||||
font-size: 90%; | |||||
} | |||||
.level4 { | |||||
padding: 0 5px; | |||||
font-size: 85%; | |||||
} | |||||
.level5 { | |||||
padding: 0 5px; | |||||
font-size: 80%; | |||||
} | |||||
.level6 { | |||||
padding: 0 5px; | |||||
font-size: 75%; | |||||
} |
@@ -0,0 +1,199 @@ | |||||
body { | |||||
color: #34393e; | |||||
line-height: 1.5; | |||||
/*font-size: 16px;*/ | |||||
-ms-text-size-adjust: 100%; | |||||
-webkit-text-size-adjust: 100%; | |||||
word-wrap: break-word | |||||
} | |||||
/* HEADINGS */ | |||||
h1 { | |||||
font-weight: 600; | |||||
font-size: 32px; | |||||
} | |||||
h2 { | |||||
font-weight: 600; | |||||
font-size: 24px; | |||||
line-height: 1.8; | |||||
} | |||||
h3 { | |||||
font-weight: 600; | |||||
font-size: 20px; | |||||
line-height: 1.8; | |||||
} | |||||
h5 { | |||||
font-size: 14px; | |||||
padding: 10px 0px; | |||||
} | |||||
article h1, | |||||
article h2, | |||||
article h3, | |||||
article h4 { | |||||
margin-top: 35px; | |||||
margin-bottom: 15px; | |||||
} | |||||
article h4 { | |||||
padding-bottom: 8px; | |||||
border-bottom: 2px solid #ddd; | |||||
} | |||||
/* NAVBAR */ | |||||
.navbar-brand>img { | |||||
color: #fff; | |||||
} | |||||
.navbar { | |||||
border: none; | |||||
/* Both navbars use box-shadow */ | |||||
-webkit-box-shadow: 0px 1px 3px 0px rgba(100, 100, 100, 0.5); | |||||
-moz-box-shadow: 0px 1px 3px 0px rgba(100, 100, 100, 0.5); | |||||
box-shadow: 0px 1px 3px 0px rgba(100, 100, 100, 0.5); | |||||
} | |||||
.subnav { | |||||
border-top: 1px solid #ddd; | |||||
background-color: #fff; | |||||
} | |||||
.navbar-inverse { | |||||
background-color: #0d47a1; | |||||
z-index: 100; | |||||
} | |||||
.navbar-inverse .navbar-nav>li>a, | |||||
.navbar-inverse .navbar-text { | |||||
color: #fff; | |||||
/*background-color: #0d47a1;*/ | |||||
border-bottom: 3px solid transparent; | |||||
padding-bottom: 12px; | |||||
} | |||||
.navbar-inverse .navbar-nav>li>a:focus, | |||||
.navbar-inverse .navbar-nav>li>a:hover { | |||||
color: #fff; | |||||
background-color: #1157c0; | |||||
border-bottom: 3px solid white; | |||||
} | |||||
.navbar-inverse .navbar-nav>.active>a, | |||||
.navbar-inverse .navbar-nav>.active>a:focus, | |||||
.navbar-inverse .navbar-nav>.active>a:hover { | |||||
color: #fff; | |||||
background-color: #1157c0; | |||||
border-bottom: 3px solid white; | |||||
} | |||||
.navbar-form .form-control { | |||||
border: none; | |||||
border-radius: 20px; | |||||
} | |||||
/* SIDEBAR */ | |||||
/*.toc .level1>li { | |||||
font-weight: 400; | |||||
}*/ | |||||
.toc .nav>li>a { | |||||
color: #34393e; | |||||
} | |||||
.sidefilter { | |||||
background-color: #fff; | |||||
border-left: none; | |||||
border-right: none; | |||||
} | |||||
.sidefilter { | |||||
background-color: #fff; | |||||
border-left: none; | |||||
border-right: none; | |||||
} | |||||
.toc-filter { | |||||
padding: 10px; | |||||
margin: 0; | |||||
} | |||||
.toc-filter>input { | |||||
border: 2px solid #ddd; | |||||
border-radius: 20px; | |||||
} | |||||
.toc-filter>.filter-icon { | |||||
display: none; | |||||
} | |||||
.sidetoc>.toc { | |||||
overflow-x: hidden; | |||||
} | |||||
.sidetoc { | |||||
border: none; | |||||
} | |||||
/* ALERTS */ | |||||
.alert { | |||||
padding: 0px 0px 5px 0px; | |||||
color: inherit; | |||||
background-color: inherit; | |||||
border: none; | |||||
box-shadow: 0px 2px 2px 0px rgba(100, 100, 100, 0.4); | |||||
} | |||||
.alert>p { | |||||
margin-bottom: 0; | |||||
padding: 5px 10px; | |||||
} | |||||
.alert>ul { | |||||
margin-bottom: 0; | |||||
padding: 5px 40px; | |||||
} | |||||
.alert>h5 { | |||||
padding: 10px 15px; | |||||
margin-top: 0; | |||||
text-transform: uppercase; | |||||
font-weight: bold; | |||||
border-radius: 4px 4px 0 0; | |||||
} | |||||
.alert-info>h5 { | |||||
color: #1976d2; | |||||
border-bottom: 4px solid #1976d2; | |||||
background-color: #e3f2fd; | |||||
} | |||||
.alert-warning>h5 { | |||||
color: #f57f17; | |||||
border-bottom: 4px solid #f57f17; | |||||
background-color: #fff3e0; | |||||
} | |||||
.alert-danger>h5 { | |||||
color: #d32f2f; | |||||
border-bottom: 4px solid #d32f2f; | |||||
background-color: #ffebee; | |||||
} | |||||
/* CODE HIGHLIGHT */ | |||||
pre { | |||||
padding: 9.5px; | |||||
margin: 10px 10px 10px; | |||||
font-size: 13px; | |||||
word-break: break-all; | |||||
word-wrap: break-word; | |||||
/*background-color: #fffaef;*/ | |||||
border-radius: 4px; | |||||
box-shadow: 0px 1px 4px 1px rgba(100, 100, 100, 0.4); | |||||
} |
@@ -50,7 +50,7 @@ | |||||
"overwrite": "_overwrites/**/**.md", | "overwrite": "_overwrites/**/**.md", | ||||
"globalMetadata": { | "globalMetadata": { | ||||
"_appTitle": "Discord.Net Documentation", | "_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, | "_enableSearch": true, | ||||
"_appLogoPath": "marketing/logo/SVG/Logomark Purple.svg", | "_appLogoPath": "marketing/logo/SVG/Logomark Purple.svg", | ||||
"_appFaviconPath": "favicon.ico" | "_appFaviconPath": "favicon.ico" | ||||
@@ -4,4 +4,7 @@ apiRules: | |||||
type: Namespace | type: Namespace | ||||
- exclude: | - exclude: | ||||
uidRegex: ^Discord\.Analyzers$ | uidRegex: ^Discord\.Analyzers$ | ||||
type: Namespace | |||||
- exclude: | |||||
uidRegex: ^Discord\.API$ | |||||
type: Namespace | type: Namespace |
@@ -3,6 +3,9 @@ public class Initialize | |||||
private readonly CommandService _commands; | private readonly CommandService _commands; | ||||
private readonly DiscordSocketClient _client; | private readonly DiscordSocketClient _client; | ||||
// Ask if there are existing CommandService and DiscordSocketClient | |||||
// instance. If there are, we retrieve them and add them to the | |||||
// DI container; if not, we create our own. | |||||
public Initialize(CommandService commands = null, DiscordSocketClient client = null) | public Initialize(CommandService commands = null, DiscordSocketClient client = null) | ||||
{ | { | ||||
_commands = commands ?? new CommandService(); | _commands = commands ?? new CommandService(); | ||||
@@ -32,6 +32,6 @@ public class DatabaseModule : ModuleBase<SocketCommandContext> | |||||
[Command("read")] | [Command("read")] | ||||
public async Task ReadFromDbAsync() | public async Task ReadFromDbAsync() | ||||
{ | { | ||||
await ReplyAsync(_database.GetData()); | |||||
await ReplyAsync(DbService.GetData()); | |||||
} | } | ||||
} | |||||
} |
@@ -3,6 +3,7 @@ public class CommandHandler | |||||
private readonly DiscordSocketClient _client; | private readonly DiscordSocketClient _client; | ||||
private readonly CommandService _commands; | private readonly CommandService _commands; | ||||
// Retrieve client and CommandService instance via ctor | |||||
public CommandHandler(DiscordSocketClient client, CommandService commands) | public CommandHandler(DiscordSocketClient client, CommandService commands) | ||||
{ | { | ||||
_commands = commands; | _commands = commands; | ||||
@@ -46,19 +47,9 @@ public class CommandHandler | |||||
// Execute the command with the command context we just | // Execute the command with the command context we just | ||||
// created, along with the service provider for precondition checks. | // created, along with the service provider for precondition checks. | ||||
// Keep in mind that result does not indicate a return value | |||||
// rather an object stating if the command executed successfully. | |||||
var result = await _commands.ExecuteAsync( | |||||
await _commands.ExecuteAsync( | |||||
context: context, | context: context, | ||||
argPos: argPos, | argPos: argPos, | ||||
services: null); | services: null); | ||||
// Optionally, we may inform the user if the command fails | |||||
// to be executed; however, this may not always be desired, | |||||
// as it may clog up the request queue should a user spam a | |||||
// command. | |||||
// if (!result.IsSuccess) | |||||
// await context.Channel.SendMessageAsync(result.ErrorReason); | |||||
} | } | ||||
} | } |
@@ -1,6 +1,5 @@ | |||||
public async Task LogAsync(LogMessage logMessage) | public async Task LogAsync(LogMessage logMessage) | ||||
{ | { | ||||
// This casting type requries C#7 | |||||
if (logMessage.Exception is CommandException cmdException) | if (logMessage.Exception is CommandException cmdException) | ||||
{ | { | ||||
// We can tell the user that something unexpected has happened | // We can tell the user that something unexpected has happened | ||||
@@ -1,5 +1,6 @@ | |||||
// Note: This example is obsolete, a boolean type reader is bundled | |||||
// with Discord.Commands | |||||
// Please note that the library already supports type reading | |||||
// primitive types such as bool. This example is merely used | |||||
// to demonstrate how one could write a simple TypeReader. | |||||
using Discord; | using Discord; | ||||
using Discord.Commands; | using Discord.Commands; | ||||
@@ -35,10 +35,6 @@ sync and has a completed guild cache. | |||||
[DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient | [DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient | ||||
### Samples | |||||
[!code-csharp[Connection Sample](samples/events.cs)] | |||||
## Reconnection | ## Reconnection | ||||
> [!TIP] | > [!TIP] | ||||
@@ -7,7 +7,7 @@ title: Entities | |||||
> [!NOTE] | > [!NOTE] | ||||
> This article is written with the Socket variants of entities in mind, | > This article is written with the Socket variants of entities in mind, | ||||
> not the general interfaces or Rest/Rpc entities. | |||||
> not the general interfaces or Rest entities. | |||||
Discord.Net provides a versatile entity system for navigating the | Discord.Net provides a versatile entity system for navigating the | ||||
Discord API. | Discord API. | ||||
@@ -1,23 +0,0 @@ | |||||
using Discord; | |||||
using Discord.WebSocket; | |||||
public class Program | |||||
{ | |||||
private DiscordSocketClient _client; | |||||
static void Main(string[] args) => new Program().MainAsync().GetAwaiter().GetResult(); | |||||
public async Task MainAsync() | |||||
{ | |||||
_client = new DiscordSocketClient(); | |||||
await _client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("DiscordToken")); | |||||
await _client.StartAsync(); | |||||
Console.WriteLine("Press any key to exit..."); | |||||
Console.ReadKey(); | |||||
await _client.StopAsync(); | |||||
// Wait a little for the client to finish disconnecting before allowing the program to return | |||||
await Task.Delay(500); | |||||
} | |||||
} |
@@ -1,29 +1,24 @@ | |||||
using Discord; | using Discord; | ||||
using Discord.WebSocket; | using Discord.WebSocket; | ||||
public class Program | |||||
public class LoggingService | |||||
{ | { | ||||
private DiscordSocketClient _client; | |||||
static void Main(string[] args) => new Program().MainAsync().GetAwaiter().GetResult(); | |||||
public async Task MainAsync() | |||||
public LoggingService(DiscordSocketClient client, CommandService command) | |||||
{ | { | ||||
_client = new DiscordSocketClient(new DiscordSocketConfig | |||||
{ | |||||
LogLevel = LogSeverity.Info | |||||
}); | |||||
_client.Log += Log; | |||||
await _client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("DiscordToken")); | |||||
await _client.StartAsync(); | |||||
await Task.Delay(-1); | |||||
client.Log += LogAsync; | |||||
command.Log += LogAsync; | |||||
} | } | ||||
private Task Log(LogMessage message) | |||||
private Task LogAsync(LogMessage message) | |||||
{ | { | ||||
Console.WriteLine(message.ToString()); | |||||
if (message.Exception is CommandException cmdException) | |||||
{ | |||||
Console.WriteLine($"[Command/{message.Severity}] {cmdException.Command.Aliases.First()}" | |||||
+ $" failed to execute in {cmdException.Context.Channel}."); | |||||
Console.WriteLine(cmdException); | |||||
} | |||||
else | |||||
Console.WriteLine($"[General/{message.Severity}] {message}"); | |||||
return Task.CompletedTask; | return Task.CompletedTask; | ||||
} | } | ||||
} | } |
@@ -128,12 +128,10 @@ Finally, we can create a new connection to Discord. | |||||
Since we are writing a bot, we will be using a [DiscordSocketClient] | Since we are writing a bot, we will be using a [DiscordSocketClient] | ||||
along with socket entities. See @Guides.GettingStarted.Terminology | along with socket entities. See @Guides.GettingStarted.Terminology | ||||
if you are unsure of the differences. | |||||
To establish a new connection, we will create an instance of | |||||
[DiscordSocketClient] in the new async main. You may pass in an | |||||
optional @Discord.WebSocket.DiscordSocketConfig if necessary. For most | |||||
users, the default will work fine. | |||||
if you are unsure of the differences. To establish a new connection, | |||||
we will create an instance of [DiscordSocketClient] in the new async | |||||
main. You may pass in an optional @Discord.WebSocket.DiscordSocketConfig | |||||
if necessary. For most users, the default will work fine. | |||||
Before connecting, we should hook the client's `Log` event to the | Before connecting, we should hook the client's `Log` event to the | ||||
log handler that we had just created. Events in Discord.Net work | log handler that we had just created. Events in Discord.Net work | ||||
@@ -142,22 +140,33 @@ similarly to any other events in C#. | |||||
Next, you will need to "log in to Discord" with the [LoginAsync] | Next, you will need to "log in to Discord" with the [LoginAsync] | ||||
method with the application's "token." | method with the application's "token." | ||||
 | |||||
> [!NOTE] | > [!NOTE] | ||||
> Pay attention to what you are copying from the developer portal! | > Pay attention to what you are copying from the developer portal! | ||||
> A token is not the same as the application's "client secret." | > A token is not the same as the application's "client secret." | ||||
 | |||||
> [!IMPORTANT] | > [!IMPORTANT] | ||||
> Your bot's token can be used to gain total access to your bot, so | > Your bot's token can be used to gain total access to your bot, so | ||||
> **do __NOT__ share this token with anyone else!** It may behoove you | |||||
> to store this token in an external source if you plan on distributing | |||||
> **do not** share this token with anyone else! You should store this | |||||
> token in an external source if you plan on distributing | |||||
> the source code for your bot. | > the source code for your bot. | ||||
> | |||||
> In the following example, we retrieve the token from the environment | |||||
> variable `DiscordToken`. Please note that this is *not* designed to | |||||
> be used in a production environment, as the secrets are stored in | |||||
> plain-text. | |||||
> | |||||
> For information on how to set an environment variable, please see | |||||
> instructions below, | |||||
> | |||||
> * Windows: [How to Create Environment Variables Shortcut in Windows](https://www.tenforums.com/tutorials/121742-create-environment-variables-shortcut-windows.html) | |||||
> * Linux: [How To Read and Set Environmental and Shell Variables on a Linux VPS](https://www.digitalocean.com/community/tutorials/how-to-read-and-set-environmental-and-shell-variables-on-a-linux-vps) | |||||
> * macOS: [How do I set environment variables on OS X?](https://apple.stackexchange.com/questions/106778/how-do-i-set-environment-variables-on-os-x) | |||||
We may now invoke the client's [StartAsync] method, which will | We may now invoke the client's [StartAsync] method, which will | ||||
start connection/reconnection logic. It is important to note that | start connection/reconnection logic. It is important to note that | ||||
**this method will return as soon as connection logic has been started!** | **this method will return as soon as connection logic has been started!** | ||||
Any methods that rely on the client's state should go in an event | Any methods that rely on the client's state should go in an event | ||||
handler. This means that you should **not** directly be interacting with | handler. This means that you should **not** directly be interacting with | ||||
the client before it is fully ready. | the client before it is fully ready. | ||||
@@ -173,81 +182,34 @@ The following lines can now be added: | |||||
At this point, feel free to start your program and see your bot come | At this point, feel free to start your program and see your bot come | ||||
online in Discord. | online in Discord. | ||||
> [!TIP] | |||||
> [!WARNING] | |||||
> Getting a warning about `A supplied token was invalid.` and/or | > Getting a warning about `A supplied token was invalid.` and/or | ||||
> having trouble logging in? Double-check whether you have put in | > having trouble logging in? Double-check whether you have put in | ||||
> the correct credentials and make sure that it is _not_ a client | > the correct credentials and make sure that it is _not_ a client | ||||
> secret, which is different from a token. | > secret, which is different from a token. | ||||
> [!TIP] | |||||
> [!WARNING] | |||||
> Encountering a `PlatformNotSupportedException` when starting your bot? | > Encountering a `PlatformNotSupportedException` when starting your bot? | ||||
> This means that you are targeting a platform where .NET's default | > This means that you are targeting a platform where .NET's default | ||||
> WebSocket client is not supported. Refer to the [installation guide] | > WebSocket client is not supported. Refer to the [installation guide] | ||||
> for how to fix this. | > for how to fix this. | ||||
> [!NOTE] | |||||
> For your reference, you may view the [completed program]. | |||||
[DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient | [DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient | ||||
[LoginAsync]: xref:Discord.Rest.BaseDiscordClient.LoginAsync* | [LoginAsync]: xref:Discord.Rest.BaseDiscordClient.LoginAsync* | ||||
[StartAsync]: xref:Discord.WebSocket.DiscordSocketClient.StartAsync* | [StartAsync]: xref:Discord.WebSocket.DiscordSocketClient.StartAsync* | ||||
[installation guide]: xref:Guides.GettingStarted.Installation | [installation guide]: xref:Guides.GettingStarted.Installation | ||||
### Handling a 'ping' | |||||
> [!WARNING] | |||||
> Please note that this is *not* a proper way to create a command. | |||||
> Use the `CommandService` provided by the library instead, as explained | |||||
> in the [Command Guide](xref:Guides.Commands.Intro) section. | |||||
Now that we have learned to open a connection to Discord, we can | |||||
begin handling messages that the users are sending. To start out, our | |||||
bot will listen for any message whose content is equal to `!ping` and | |||||
will respond back with "Pong!". | |||||
Since we want to listen for new messages, the event to hook into | |||||
is [MessageReceived]. | |||||
In your program, add a method that matches the signature of the | |||||
`MessageReceived` event - it must be a method (`Func`) that returns | |||||
the type `Task` and takes a single parameter, a [SocketMessage]. Also, | |||||
since we will be sending data to Discord in this method, we will flag | |||||
it as `async`. | |||||
In this method, we will add an `if` block to determine if the message | |||||
content fits the rules of our scenario - recall that it must be equal | |||||
to `!ping`. | |||||
Inside the branch of this condition, we will want to send a message, | |||||
`Pong!`, back to the channel from which the message comes from. To | |||||
find the channel, look for the `Channel` property on the message | |||||
parameter. | |||||
Next, we will want to send a message to this channel. Since the | |||||
channel object is of type [ISocketMessageChannel], we can invoke the | |||||
[SendMessageAsync] instance method. For the message content, send back | |||||
a string, "Pong!". | |||||
You should have now added the following lines, | |||||
[!code-csharp[Message](samples/first-bot/message.cs)] | |||||
Now that your first bot is complete. You may continue to add on to this | |||||
if you desire, but for any bots that will be carrying out multiple | |||||
commands, it is strongly recommended to use the command framework as | |||||
shown below. | |||||
> [!NOTE] | |||||
> For your reference, you may view the [completed program]. | |||||
[MessageReceived]: xref:Discord.WebSocket.BaseSocketClient.MessageReceived | |||||
[SocketMessage]: xref:Discord.WebSocket.SocketMessage | |||||
[ISocketMessageChannel]: xref:Discord.WebSocket.ISocketMessageChannel | |||||
[SendMessageAsync]: xref:Discord.WebSocket.ISocketMessageChannel.SendMessageAsync* | |||||
[completed program]: samples/first-bot/complete.cs | [completed program]: samples/first-bot/complete.cs | ||||
# Building a bot with commands | # Building a bot with commands | ||||
@Guides.Commands.Intro will guide you through how to setup a program | |||||
that is ready for [CommandService], a service that is ready for | |||||
advanced command usage. | |||||
To create commands for your bot, you may choose from a variety of | |||||
command processors available. Throughout the guides, we will be using | |||||
the one that Discord.Net ships with. @Guides.Commands.Intro will | |||||
guide you through how to setup a program that is ready for | |||||
[CommandService]. | |||||
For reference, view an [annotated example] of this structure. | For reference, view an [annotated example] of this structure. | ||||
@@ -42,37 +42,45 @@ published to our [MyGet feed]. See | |||||
### [Using Visual Studio](#tab/vs-install) | ### [Using Visual Studio](#tab/vs-install) | ||||
1. Create a new solution for your bot. | |||||
1. Create a new solution for your bot | |||||
2. In the Solution Explorer, find the "Dependencies" element under your | 2. In the Solution Explorer, find the "Dependencies" element under your | ||||
bot's project. | |||||
3. Right click on "Dependencies", and select "Manage NuGet packages." | |||||
 | |||||
4. In the "Browse" tab, search for `Discord.Net`. | |||||
5. Install the `Discord.Net` package. | |||||
 | |||||
bot's project | |||||
3. Right click on "Dependencies", and select "Manage NuGet packages" | |||||
 | |||||
4. In the "Browse" tab, search for `Discord.Net` | |||||
5. Install the `Discord.Net` package | |||||
 | |||||
### [Using JetBrains Rider](#tab/rider-install) | ### [Using JetBrains Rider](#tab/rider-install) | ||||
1. Create a new solution for your bot. | |||||
2. Open the NuGet window (Tools > NuGet > Manage NuGet packages for | |||||
Solution). | |||||
 | |||||
3. In the "Packages" tab, search for `Discord.Net`. | |||||
 | |||||
4. Install by adding the package to your project. | |||||
 | |||||
1. Create a new solution for your bot | |||||
2. Open the NuGet window (Tools > NuGet > Manage NuGet packages for Solution) | |||||
 | |||||
3. In the "Packages" tab, search for `Discord.Net` | |||||
 | |||||
4. Install by adding the package to your project | |||||
 | |||||
### [Using Visual Studio Code](#tab/vs-code) | ### [Using Visual Studio Code](#tab/vs-code) | ||||
1. Create a new project for your bot. | |||||
2. Add `Discord.Net` to your .csproj. | |||||
1. Create a new project for your bot | |||||
2. Add `Discord.Net` to your `*.csproj` | |||||
[!code[Sample .csproj](samples/project.xml)] | [!code[Sample .csproj](samples/project.xml)] | ||||
### [Using dotnet CLI](#tab/dotnet-cli) | ### [Using dotnet CLI](#tab/dotnet-cli) | ||||
1. Open command-line and navigate to where your .csproj is located. | |||||
2. Enter `dotnet add package Discord.Net`. | |||||
1. Launch your terminal | |||||
2. Navigate to where your `*.csproj` is located | |||||
3. Enter `dotnet add package Discord.Net` | |||||
*** | *** | ||||
@@ -115,16 +123,16 @@ by installing one or more custom packages as listed below. | |||||
1. Install or compile the following packages: | 1. Install or compile the following packages: | ||||
* `Discord.Net.Providers.WS4Net` | |||||
* `Discord.Net.Providers.UDPClient` (Optional) | |||||
* This is _only_ required if your bot will be utilizing voice chat. | |||||
* `Discord.Net.Providers.WS4Net` | |||||
* `Discord.Net.Providers.UDPClient` (Optional) | |||||
* This is _only_ required if your bot will be utilizing voice chat. | |||||
2. Configure your [DiscordSocketClient] to use these custom providers | 2. Configure your [DiscordSocketClient] to use these custom providers | ||||
over the default ones. | over the default ones. | ||||
* To do this, set the `WebSocketProvider` and the optional | |||||
`UdpSocketProvider` properties on the [DiscordSocketConfig] that you | |||||
are passing into your client. | |||||
* To do this, set the `WebSocketProvider` and the optional | |||||
`UdpSocketProvider` properties on the [DiscordSocketConfig] that you | |||||
are passing into your client. | |||||
[!code-csharp[Example](samples/netstd11.cs)] | [!code-csharp[Example](samples/netstd11.cs)] | ||||
@@ -31,22 +31,33 @@ The following is the feed link of Discord.Net, | |||||
Depending on which IDE you use, there are many different ways of | Depending on which IDE you use, there are many different ways of | ||||
adding the feed to your package source. | adding the feed to your package source. | ||||
### [Visual Studio](#tab/vs) | |||||
### [Using Visual Studio](#tab/vs) | |||||
1. Go to `Tools` > `NuGet Package Manager` > `Package Manager Settings` | 1. Go to `Tools` > `NuGet Package Manager` > `Package Manager Settings` | ||||
 |  | ||||
2. Go to `Package Sources` | 2. Go to `Package Sources` | ||||
 |  | ||||
3. Click on the add icon | 3. Click on the add icon | ||||
4. Fill in the desired name and source as shown below and hit `Update` | 4. Fill in the desired name and source as shown below and hit `Update` | ||||
 |  | ||||
> [!NOTE] | > [!NOTE] | ||||
> Remember to tick the `Include prerelease` checkbox to see the | |||||
> Remember to tick the `Include pre-release` checkbox to see the | |||||
> nightly builds! | > nightly builds! | ||||
>  | >  | ||||
### [Local NuGet.Config](#tab/local-nuget-config) | |||||
### [Using dotnet CLI](#tab/cli) | |||||
1. Launch your terminal | |||||
2. Navigate to where your `*.csproj` is located | |||||
3. Type `dotnet add package Discord.Net --source https://www.myget.org/F/discord-net/api/v3/index.json` | |||||
### [Using Local NuGet.Config](#tab/local-nuget-config) | |||||
If you plan on deploying your bot or developing outside of Visual | If you plan on deploying your bot or developing outside of Visual | ||||
Studio, you will need to create a local NuGet configuration file for | Studio, you will need to create a local NuGet configuration file for | ||||
@@ -9,7 +9,6 @@ public class Program | |||||
{ | { | ||||
_client = new DiscordSocketClient(); | _client = new DiscordSocketClient(); | ||||
_client.Log += Log; | _client.Log += Log; | ||||
_client.MessageReceived += MessageReceivedAsync; | |||||
await _client.LoginAsync(TokenType.Bot, | await _client.LoginAsync(TokenType.Bot, | ||||
Environment.GetEnvironmentVariable("DiscordToken")); | Environment.GetEnvironmentVariable("DiscordToken")); | ||||
await _client.StartAsync(); | await _client.StartAsync(); | ||||
@@ -17,15 +16,6 @@ public class Program | |||||
// Block this task until the program is closed. | // Block this task until the program is closed. | ||||
await Task.Delay(-1); | await Task.Delay(-1); | ||||
} | } | ||||
private async Task MessageReceivedAsync(SocketMessage message) | |||||
{ | |||||
if (message.Content == "!ping") | |||||
{ | |||||
await message.Channel.SendMessageAsync("Pong!"); | |||||
} | |||||
} | |||||
private Task Log(LogMessage msg) | private Task Log(LogMessage msg) | ||||
{ | { | ||||
Console.WriteLine(msg.ToString()); | Console.WriteLine(msg.ToString()); | ||||
@@ -16,17 +16,14 @@ understand these topics to some extent before proceeding. With all | |||||
that being said, feel free to visit us on Discord at the link below | that being said, feel free to visit us on Discord at the link below | ||||
if you have any questions! | if you have any questions! | ||||
Here are some examples: | |||||
1. [Official samples] | |||||
2. [Official template] | |||||
An official collection of samples can be found | |||||
in [our GitHub repository]. | |||||
> [!NOTE] | > [!NOTE] | ||||
> Please note that you should *not* try to blindly copy paste | > Please note that you should *not* try to blindly copy paste | ||||
> the code. The examples are meant to be a template or a guide. | > the code. The examples are meant to be a template or a guide. | ||||
[Official template]: https://github.com/foxbot/DiscordBotBase/tree/csharp/src/DiscordBot | |||||
[Official samples]: https://github.com/RogueException/Discord.Net/tree/dev/samples | |||||
[our GitHub repository]: https://github.com/RogueException/Discord.Net/tree/dev/samples | |||||
[Task-based Asynchronous Pattern]: https://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap | [Task-based Asynchronous Pattern]: https://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap | ||||
[polymorphism]: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/polymorphism | [polymorphism]: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/polymorphism | ||||
[interface]: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/interfaces/ | [interface]: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/interfaces/ | ||||
@@ -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() | ||||
@@ -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); | ||||
@@ -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}"); | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -17,7 +17,12 @@ namespace Discord.Commands | |||||
/// Gets the specified <see cref="Discord.ChannelPermission" /> of the precondition. | /// Gets the specified <see cref="Discord.ChannelPermission" /> of the precondition. | ||||
/// </summary> | /// </summary> | ||||
public ChannelPermission? ChannelPermission { get; } | public ChannelPermission? ChannelPermission { get; } | ||||
/// <inheritdoc /> | |||||
public override string ErrorMessage { get; set; } | public override string ErrorMessage { get; set; } | ||||
/// <summary> | |||||
/// Gets or sets the error message if the precondition | |||||
/// fails due to being run outside of a Guild channel. | |||||
/// </summary> | |||||
public string NotAGuildErrorMessage { get; set; } | public string NotAGuildErrorMessage { get; set; } | ||||
/// <summary> | /// <summary> | ||||
@@ -33,6 +33,7 @@ namespace Discord.Commands | |||||
/// Gets the context required to execute the command. | /// Gets the context required to execute the command. | ||||
/// </summary> | /// </summary> | ||||
public ContextType Contexts { get; } | public ContextType Contexts { get; } | ||||
/// <inheritdoc /> | |||||
public override string ErrorMessage { get; set; } | public override string ErrorMessage { get; set; } | ||||
/// <summary> Requires the command to be invoked in the specified context. </summary> | /// <summary> Requires the command to be invoked in the specified context. </summary> | ||||
@@ -30,6 +30,7 @@ namespace Discord.Commands | |||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] | ||||
public class RequireNsfwAttribute : PreconditionAttribute | public class RequireNsfwAttribute : PreconditionAttribute | ||||
{ | { | ||||
/// <inheritdoc /> | |||||
public override string ErrorMessage { get; set; } | public override string ErrorMessage { get; set; } | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
@@ -34,6 +34,7 @@ namespace Discord.Commands | |||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] | ||||
public class RequireOwnerAttribute : PreconditionAttribute | public class RequireOwnerAttribute : PreconditionAttribute | ||||
{ | { | ||||
/// <inheritdoc /> | |||||
public override string ErrorMessage { get; set; } | public override string ErrorMessage { get; set; } | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
@@ -17,7 +17,12 @@ namespace Discord.Commands | |||||
/// Gets the specified <see cref="Discord.ChannelPermission" /> of the precondition. | /// Gets the specified <see cref="Discord.ChannelPermission" /> of the precondition. | ||||
/// </summary> | /// </summary> | ||||
public ChannelPermission? ChannelPermission { get; } | public ChannelPermission? ChannelPermission { get; } | ||||
/// <inheritdoc /> | |||||
public override string ErrorMessage { get; set; } | public override string ErrorMessage { get; set; } | ||||
/// <summary> | |||||
/// Gets or sets the error message if the precondition | |||||
/// fails due to being run outside of a Guild channel. | |||||
/// </summary> | |||||
public string NotAGuildErrorMessage { get; set; } | public string NotAGuildErrorMessage { get; set; } | ||||
/// <summary> | /// <summary> | ||||
@@ -39,15 +39,8 @@ namespace Discord.Commands | |||||
/// Occurs when a command is successfully executed without any error. | /// Occurs when a command is successfully executed without any error. | ||||
/// </summary> | /// </summary> | ||||
/// <remarks> | /// <remarks> | ||||
/// <para> | |||||
/// This event is fired when a command has been successfully executed without any of the following errors: | |||||
/// </para> | |||||
/// <para>* Parsing error</para> | |||||
/// <para>* Precondition error</para> | |||||
/// <para>* Runtime exception</para> | |||||
/// <para> | |||||
/// Should the command encounter any of the aforementioned error, this event will not be raised. | |||||
/// </para> | |||||
/// This event is fired when a command has been executed, successfully or not. When a command fails to | |||||
/// execute during parsing or precondition stage, the CommandInfo may not be returned. | |||||
/// </remarks> | /// </remarks> | ||||
public event Func<Optional<CommandInfo>, ICommandContext, IResult, Task> CommandExecuted { add { _commandExecutedEvent.Add(value); } remove { _commandExecutedEvent.Remove(value); } } | public event Func<Optional<CommandInfo>, ICommandContext, IResult, Task> CommandExecuted { add { _commandExecutedEvent.Add(value); } remove { _commandExecutedEvent.Remove(value); } } | ||||
internal readonly AsyncEvent<Func<Optional<CommandInfo>, ICommandContext, IResult, Task>> _commandExecutedEvent = new AsyncEvent<Func<Optional<CommandInfo>, ICommandContext, IResult, Task>>(); | internal readonly AsyncEvent<Func<Optional<CommandInfo>, ICommandContext, IResult, Task>> _commandExecutedEvent = new AsyncEvent<Func<Optional<CommandInfo>, ICommandContext, IResult, Task>>(); | ||||
@@ -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> |
@@ -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 | |||||
} | } | ||||
} | } |
@@ -97,26 +97,12 @@ namespace Discord | |||||
/// Adds or updates the permission overwrite for the given role. | /// Adds or updates the permission overwrite for the given role. | ||||
/// </summary> | /// </summary> | ||||
/// <example> | /// <example> | ||||
/// The following example fetches a role via <see cref="IGuild.GetRole"/> and a channel via | |||||
/// <para>The following example fetches a role via <see cref="IGuild.GetRole"/> and a channel via | |||||
/// <see cref="IGuild.GetChannelAsync"/>. Next, it checks if an overwrite had already been set via | /// <see cref="IGuild.GetChannelAsync"/>. Next, it checks if an overwrite had already been set via | ||||
/// <see cref="GetPermissionOverwrite(Discord.IRole)"/>; if not, it denies the role from sending any | /// <see cref="GetPermissionOverwrite(Discord.IRole)"/>; if not, it denies the role from sending any | ||||
/// messages to the channel. | |||||
/// <code lang="cs"> | |||||
/// // Fetches the role and channels | |||||
/// var role = guild.GetRole(339805618376540160); | |||||
/// var channel = await guild.GetChannelAsync(233937283911385098); | |||||
/// | |||||
/// // If either the of the object does not exist, bail | |||||
/// if (role == null || channel == null) return; | |||||
/// | |||||
/// // Fetches the previous overwrite and bail if one is found | |||||
/// var previousOverwrite = channel.GetPermissionOverwrite(role); | |||||
/// if (previousOverwrite.HasValue) return; | |||||
/// | |||||
/// // Creates a new OverwritePermissions with send message set to deny and pass it into the method | |||||
/// await channel.AddPermissionOverwriteAsync(role, | |||||
/// new OverwritePermissions(sendMessage: PermValue.Deny)); | |||||
/// </code> | |||||
/// messages to the channel.</para> | |||||
/// <code language="cs" region="AddPermissionOverwriteAsyncRole" | |||||
/// source="..\..\..\Discord.Net.Examples\Core\Entities\Channels\IGuildChannel.Examples.cs"/> | |||||
/// </example> | /// </example> | ||||
/// <param name="role">The role to add the overwrite to.</param> | /// <param name="role">The role to add the overwrite to.</param> | ||||
/// <param name="permissions">The overwrite to add to the role.</param> | /// <param name="permissions">The overwrite to add to the role.</param> | ||||
@@ -130,26 +116,12 @@ namespace Discord | |||||
/// Adds or updates the permission overwrite for the given user. | /// Adds or updates the permission overwrite for the given user. | ||||
/// </summary> | /// </summary> | ||||
/// <example> | /// <example> | ||||
/// The following example fetches a user via <see cref="IGuild.GetUserAsync"/> and a channel via | |||||
/// <para>The following example fetches a user via <see cref="IGuild.GetUserAsync"/> and a channel via | |||||
/// <see cref="IGuild.GetChannelAsync"/>. Next, it checks if an overwrite had already been set via | /// <see cref="IGuild.GetChannelAsync"/>. Next, it checks if an overwrite had already been set via | ||||
/// <see cref="GetPermissionOverwrite(Discord.IUser)"/>; if not, it denies the user from sending any | /// <see cref="GetPermissionOverwrite(Discord.IUser)"/>; if not, it denies the user from sending any | ||||
/// messages to the channel. | |||||
/// <code lang="cs"> | |||||
/// // Fetches the role and channels | |||||
/// var user = await guild.GetUserAsync(168693960628371456); | |||||
/// var channel = await guild.GetChannelAsync(233937283911385098); | |||||
/// | |||||
/// // If either the of the object does not exist, bail | |||||
/// if (user == null || channel == null) return; | |||||
/// | |||||
/// // Fetches the previous overwrite and bail if one is found | |||||
/// var previousOverwrite = channel.GetPermissionOverwrite(user); | |||||
/// if (previousOverwrite.HasValue) return; | |||||
/// | |||||
/// // Creates a new OverwritePermissions with send message set to deny and pass it into the method | |||||
/// await channel.AddPermissionOverwriteAsync(role, | |||||
/// new OverwritePermissions(sendMessage: PermValue.Deny)); | |||||
/// </code> | |||||
/// messages to the channel.</para> | |||||
/// <code language="cs" region="AddPermissionOverwriteAsyncUser" | |||||
/// source="..\..\..\Discord.Net.Examples\Core\Entities\Channels\IGuildChannel.Examples.cs"/> | |||||
/// </example> | /// </example> | ||||
/// <param name="user">The user to add the overwrite to.</param> | /// <param name="user">The user to add the overwrite to.</param> | ||||
/// <param name="permissions">The overwrite to add to the user.</param> | /// <param name="permissions">The overwrite to add to the user.</param> | ||||
@@ -14,13 +14,10 @@ namespace Discord | |||||
/// Sends a message to this message channel. | /// Sends a message to this message channel. | ||||
/// </summary> | /// </summary> | ||||
/// <example> | /// <example> | ||||
/// The following example sends a message with the current system time in RFC 1123 format to the channel and | |||||
/// deletes itself after 5 seconds. | |||||
/// <code language="cs"> | |||||
/// var message = await channel.SendMessageAsync(DateTimeOffset.UtcNow.ToString("R")); | |||||
/// await Task.Delay(TimeSpan.FromSeconds(5)) | |||||
/// .ContinueWith(x => message.DeleteAsync()); | |||||
/// </code> | |||||
/// <para>The following example sends a message with the current system time in RFC 1123 format to the channel and | |||||
/// deletes itself after 5 seconds.</para> | |||||
/// <code language="cs" region="SendMessageAsync" | |||||
/// source="..\..\..\Discord.Net.Examples\Core\Entities\Channels\IMessageChannel.Examples.cs" /> | |||||
/// </example> | /// </example> | ||||
/// <param name="text">The message to be sent.</param> | /// <param name="text">The message to be sent.</param> | ||||
/// <param name="isTTS">Determines whether the message should be read aloud by Discord or not.</param> | /// <param name="isTTS">Determines whether the message should be read aloud by Discord or not.</param> | ||||
@@ -35,18 +32,14 @@ namespace Discord | |||||
/// Sends a file to this message channel with an optional caption. | /// Sends a file to this message channel with an optional caption. | ||||
/// </summary> | /// </summary> | ||||
/// <example> | /// <example> | ||||
/// The following example uploads a local file called <c>wumpus.txt</c> along with the text | |||||
/// <c>good discord boi</c> to the channel. | |||||
/// <code language="cs"> | |||||
/// await channel.SendFileAsync("wumpus.txt", "good discord boi"); | |||||
/// </code> | |||||
/// | |||||
/// The following example uploads a local image called <c>b1nzy.jpg</c> embedded inside a rich embed to the | |||||
/// channel. | |||||
/// <code language="cs"> | |||||
/// await channel.SendFileAsync("b1nzy.jpg", | |||||
/// embed: new EmbedBuilder {ImageUrl = "attachment://b1nzy.jpg"}.Build()); | |||||
/// </code> | |||||
/// <para>The following example uploads a local file called <c>wumpus.txt</c> along with the text | |||||
/// <c>good discord boi</c> to the channel.</para> | |||||
/// <code language="cs" region="SendFileAsync.FilePath" | |||||
/// source="..\..\..\Discord.Net.Examples\Core\Entities\Channels\IMessageChannel.Examples.cs" /> | |||||
/// <para>The following example uploads a local image called <c>b1nzy.jpg</c> embedded inside a rich embed to the | |||||
/// channel.</para> | |||||
/// <code language="cs" region="SendFileAsync.FilePath.EmbeddedImage" | |||||
/// source="..\..\..\Discord.Net.Examples\Core\Entities\Channels\IMessageChannel.Examples.cs" /> | |||||
/// </example> | /// </example> | ||||
/// <remarks> | /// <remarks> | ||||
/// This method sends a file as if you are uploading an attachment directly from your Discord client. | /// This method sends a file as if you are uploading an attachment directly from your Discord client. | ||||
@@ -61,21 +54,20 @@ namespace Discord | |||||
/// <param name="isTTS">Whether the message should be read aloud by Discord or not.</param> | /// <param name="isTTS">Whether the message should be read aloud by Discord or not.</param> | ||||
/// <param name="embed">The <see cref="Discord.EmbedType.Rich" /> <see cref="Embed" /> to be sent.</param> | /// <param name="embed">The <see cref="Discord.EmbedType.Rich" /> <see cref="Embed" /> to be sent.</param> | ||||
/// <param name="options">The options to be used when sending the request.</param> | /// <param name="options">The options to be used when sending the request.</param> | ||||
/// <param name="isSpoiler">Whether the message attachment should be hidden as a spoiler.</param> | |||||
/// <returns> | /// <returns> | ||||
/// A task that represents an asynchronous send operation for delivering the message. The task result | /// A task that represents an asynchronous send operation for delivering the message. The task result | ||||
/// contains the sent message. | /// contains the sent message. | ||||
/// </returns> | /// </returns> | ||||
Task<IUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); | |||||
Task<IUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false); | |||||
/// <summary> | /// <summary> | ||||
/// Sends a file to this message channel with an optional caption. | /// Sends a file to this message channel with an optional caption. | ||||
/// </summary> | /// </summary> | ||||
/// <example> | /// <example> | ||||
/// The following example uploads a streamed image that will be called <c>b1nzy.jpg</c> embedded inside a | |||||
/// rich embed to the channel. | |||||
/// <code language="cs"> | |||||
/// await channel.SendFileAsync(b1nzyStream, "b1nzy.jpg", | |||||
/// embed: new EmbedBuilder {ImageUrl = "attachment://b1nzy.jpg"}.Build()); | |||||
/// </code> | |||||
/// <para>The following example uploads a streamed image that will be called <c>b1nzy.jpg</c> embedded inside a | |||||
/// rich embed to the channel.</para> | |||||
/// <code language="cs" region="SendFileAsync.FileStream.EmbeddedImage" | |||||
/// source="..\..\..\Discord.Net.Examples\Core\Entities\Channels\IMessageChannel.Examples.cs" /> | |||||
/// </example> | /// </example> | ||||
/// <remarks> | /// <remarks> | ||||
/// This method sends a file as if you are uploading an attachment directly from your Discord client. | /// This method sends a file as if you are uploading an attachment directly from your Discord client. | ||||
@@ -91,11 +83,12 @@ namespace Discord | |||||
/// <param name="isTTS">Whether the message should be read aloud by Discord or not.</param> | /// <param name="isTTS">Whether the message should be read aloud by Discord or not.</param> | ||||
/// <param name="embed">The <see cref="Discord.EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param> | /// <param name="embed">The <see cref="Discord.EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param> | ||||
/// <param name="options">The options to be used when sending the request.</param> | /// <param name="options">The options to be used when sending the request.</param> | ||||
/// <param name="isSpoiler">Whether the message attachment should be hidden as a spoiler.</param> | |||||
/// <returns> | /// <returns> | ||||
/// A task that represents an asynchronous send operation for delivering the message. The task result | /// A task that represents an asynchronous send operation for delivering the message. The task result | ||||
/// contains the sent message. | /// contains the sent message. | ||||
/// </returns> | /// </returns> | ||||
Task<IUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); | |||||
Task<IUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false); | |||||
/// <summary> | /// <summary> | ||||
/// Gets a message from this message channel. | /// Gets a message from this message channel. | ||||
@@ -130,12 +123,10 @@ namespace Discord | |||||
/// of flattening. | /// of flattening. | ||||
/// </remarks> | /// </remarks> | ||||
/// <example> | /// <example> | ||||
/// The following example downloads 300 messages and gets messages that belong to the user | |||||
/// <c>53905483156684800</c>. | |||||
/// <code lang="cs"> | |||||
/// var messages = await messageChannel.GetMessagesAsync(300).FlattenAsync(); | |||||
/// var userMessages = messages.Where(x => x.Author.Id == 53905483156684800); | |||||
/// </code> | |||||
/// <para>The following example downloads 300 messages and gets messages that belong to the user | |||||
/// <c>53905483156684800</c>.</para> | |||||
/// <code language="cs" region="GetMessagesAsync.FromLimit.Standard" | |||||
/// source="..\..\..\Discord.Net.Examples\Core\Entities\Channels\IMessageChannel.Examples.cs" /> | |||||
/// </example> | /// </example> | ||||
/// <param name="limit">The numbers of message to be gotten from.</param> | /// <param name="limit">The numbers of message to be gotten from.</param> | ||||
/// <param name="mode">The <see cref="CacheMode" /> that determines whether the object should be fetched from | /// <param name="mode">The <see cref="CacheMode" /> that determines whether the object should be fetched from | ||||
@@ -168,10 +159,13 @@ namespace Discord | |||||
/// of flattening. | /// of flattening. | ||||
/// </remarks> | /// </remarks> | ||||
/// <example> | /// <example> | ||||
/// The following example gets 5 message prior to the message identifier <c>442012544660537354</c>. | |||||
/// <code lang="cs"> | |||||
/// var messages = await channel.GetMessagesAsync(442012544660537354, Direction.Before, 5).FlattenAsync(); | |||||
/// </code> | |||||
/// <para>The following example gets 5 message prior to the message identifier <c>442012544660537354</c>.</para> | |||||
/// <code language="cs" region="GetMessagesAsync.FromId.FromMessage" | |||||
/// source="..\..\..\Discord.Net.Examples\Core\Entities\Channels\IMessageChannel.Examples.cs" /> | |||||
/// <para>The following example attempts to retrieve <c>messageCount</c> number of messages from the | |||||
/// beginning of the channel and prints them to the console.</para> | |||||
/// <code language="cs" region="GetMessagesAsync.FromId.BeginningMessages" | |||||
/// source="..\..\..\Discord.Net.Examples\Core\Entities\Channels\IMessageChannel.Examples.cs" /> | |||||
/// </example> | /// </example> | ||||
/// <param name="fromMessageId">The ID of the starting message to get the messages from.</param> | /// <param name="fromMessageId">The ID of the starting message to get the messages from.</param> | ||||
/// <param name="dir">The direction of the messages to be gotten from.</param> | /// <param name="dir">The direction of the messages to be gotten from.</param> | ||||
@@ -206,10 +200,9 @@ namespace Discord | |||||
/// of flattening. | /// of flattening. | ||||
/// </remarks> | /// </remarks> | ||||
/// <example> | /// <example> | ||||
/// The following example gets 5 message prior to a specific message, <c>oldMessage</c>. | |||||
/// <code lang="cs"> | |||||
/// var messages = await channel.GetMessagesAsync(oldMessage, Direction.Before, 5).FlattenAsync(); | |||||
/// </code> | |||||
/// <para>The following example gets 5 message prior to a specific message, <c>oldMessage</c>.</para> | |||||
/// <code language="cs" region="GetMessagesAsync.FromMessage" | |||||
/// source="..\..\..\Discord.Net.Examples\Core\Entities\Channels\IMessageChannel.Examples.cs" /> | |||||
/// </example> | /// </example> | ||||
/// <param name="fromMessage">The starting message to get the messages from.</param> | /// <param name="fromMessage">The starting message to get the messages from.</param> | ||||
/// <param name="dir">The direction of the messages to be gotten from.</param> | /// <param name="dir">The direction of the messages to be gotten from.</param> | ||||
@@ -262,13 +255,9 @@ namespace Discord | |||||
/// object is disposed. | /// object is disposed. | ||||
/// </summary> | /// </summary> | ||||
/// <example> | /// <example> | ||||
/// The following example keeps the client in the typing state until <c>LongRunningAsync</c> has finished. | |||||
/// <code lang="cs"> | |||||
/// using (messageChannel.EnterTypingState()) | |||||
/// { | |||||
/// await LongRunningAsync(); | |||||
/// } | |||||
/// </code> | |||||
/// <para>The following example keeps the client in the typing state until <c>LongRunningAsync</c> has finished.</para> | |||||
/// <code language="cs" region="EnterTypingState" | |||||
/// source="..\..\..\Discord.Net.Examples\Core\Entities\Channels\IMessageChannel.Examples.cs" /> | |||||
/// </example> | /// </example> | ||||
/// <param name="options">The options to be used when sending the request.</param> | /// <param name="options">The options to be used when sending the request.</param> | ||||
/// <returns> | /// <returns> | ||||
@@ -40,7 +40,7 @@ namespace Discord | |||||
/// </summary> | /// </summary> | ||||
/// <example> | /// <example> | ||||
/// The following example gets 250 messages from the channel and deletes them. | /// The following example gets 250 messages from the channel and deletes them. | ||||
/// <code lang="cs"> | |||||
/// <code language="cs"> | |||||
/// var messages = await textChannel.GetMessagesAsync(250).FlattenAsync(); | /// var messages = await textChannel.GetMessagesAsync(250).FlattenAsync(); | ||||
/// await textChannel.DeleteMessagesAsync(messages); | /// await textChannel.DeleteMessagesAsync(messages); | ||||
/// </code> | /// </code> | ||||
@@ -440,16 +440,8 @@ namespace Discord | |||||
/// </summary> | /// </summary> | ||||
/// <example> | /// <example> | ||||
/// The following example creates a new text channel under an existing category named <c>Wumpus</c> with a set topic. | /// The following example creates a new text channel under an existing category named <c>Wumpus</c> with a set topic. | ||||
/// <code lang="cs"> | |||||
/// var categories = await guild.GetCategoriesAsync(); | |||||
/// var targetCategory = categories.FirstOrDefault(x => x.Name == "wumpus"); | |||||
/// if (targetCategory == null) return; | |||||
/// await Context.Guild.CreateTextChannelAsync(name, x => | |||||
/// { | |||||
/// x.CategoryId = targetCategory.Id; | |||||
/// x.Topic = $"This channel was created at {DateTimeOffset.UtcNow} by {user}."; | |||||
/// }); | |||||
/// </code> | |||||
/// <code language="cs" region="CreateTextChannelAsync" | |||||
/// source="..\..\..\Discord.Net.Examples\Core\Entities\Guilds\IGuild.Examples.cs"/> | |||||
/// </example> | /// </example> | ||||
/// <param name="name">The new name for the text channel.</param> | /// <param name="name">The new name for the text channel.</param> | ||||
/// <param name="func">The delegate containing the properties to be applied to the channel upon its creation.</param> | /// <param name="func">The delegate containing the properties to be applied to the channel upon its creation.</param> | ||||
@@ -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 | |||||
} | } | ||||
} | } |
@@ -23,10 +23,8 @@ namespace Discord | |||||
/// <example> | /// <example> | ||||
/// The following example attempts to retrieve the user's current avatar and send it to a channel; if one is | /// The following example attempts to retrieve the user's current avatar and send it to a channel; if one is | ||||
/// not set, a default avatar for this user will be returned instead. | /// not set, a default avatar for this user will be returned instead. | ||||
/// <code language="cs"> | |||||
/// var userAvatarUrl = user.GetAvatarUrl() ?? user.GetDefaultAvatarUrl(); | |||||
/// await textChannel.SendMessageAsync(userAvatarUrl); | |||||
/// </code> | |||||
/// <code language="cs" region="GetAvatarUrl" | |||||
/// source="..\..\..\Discord.Net.Examples\Core\Entities\Users\IUser.Examples.cs"/> | |||||
/// </example> | /// </example> | ||||
/// <param name="format">The format to return.</param> | /// <param name="format">The format to return.</param> | ||||
/// <param name="size">The size of the image to return in. This can be any power of two between 16 and 2048. | /// <param name="size">The size of the image to return in. This can be any power of two between 16 and 2048. | ||||
@@ -84,27 +82,18 @@ namespace Discord | |||||
/// <remarks> | /// <remarks> | ||||
/// This method is used to obtain or create a channel used to send a direct message. | /// This method is used to obtain or create a channel used to send a direct message. | ||||
/// <note type="warning"> | /// <note type="warning"> | ||||
/// In event that the current user cannot send a message to the target user, a channel can and will still be | |||||
/// created by Discord. However, attempting to send a message will yield a | |||||
/// <see cref="Discord.Net.HttpException"/> with a 403 as its | |||||
/// <see cref="Discord.Net.HttpException.HttpCode"/>. There are currently no official workarounds by | |||||
/// Discord. | |||||
/// In event that the current user cannot send a message to the target user, a channel can and will | |||||
/// still be created by Discord. However, attempting to send a message will yield a | |||||
/// <see cref="Discord.Net.HttpException"/> with a 403 as its | |||||
/// <see cref="Discord.Net.HttpException.HttpCode"/>. There are currently no official workarounds by | |||||
/// Discord. | |||||
/// </note> | /// </note> | ||||
/// </remarks> | /// </remarks> | ||||
/// <example> | /// <example> | ||||
/// The following example attempts to send a direct message to the target user and logs the incident should | /// The following example attempts to send a direct message to the target user and logs the incident should | ||||
/// it fail. | /// it fail. | ||||
/// <code language="cs"> | |||||
/// var channel = await user.GetOrCreateDMChannelAsync(); | |||||
/// try | |||||
/// { | |||||
/// await channel.SendMessageAsync("Awesome stuff!"); | |||||
/// } | |||||
/// catch (Discord.Net.HttpException ex) when (ex.HttpCode == 403) | |||||
/// { | |||||
/// Console.WriteLine($"Boo, I cannot message {user}"); | |||||
/// } | |||||
/// </code> | |||||
/// <code region="GetOrCreateDMChannelAsync" language="cs" | |||||
/// source="../../../Discord.Net.Examples/Core/Entities/Users/IUser.Examples.cs"/> | |||||
/// </example> | /// </example> | ||||
/// <param name="options">The options to be used when sending the request.</param> | /// <param name="options">The options to be used when sending the request.</param> | ||||
/// <returns> | /// <returns> | ||||
@@ -0,0 +1,15 @@ | |||||
namespace Discord | |||||
{ | |||||
public static class AttachmentExtensions | |||||
{ | |||||
/// <summary> | |||||
/// The prefix applied to files to indicate that it is a spoiler. | |||||
/// </summary> | |||||
public const string SpoilerPrefix = "SPOILER_"; | |||||
/// <summary> | |||||
/// Gets whether the message's attachments are spoilers or not. | |||||
/// </summary> | |||||
public static bool IsSpoiler(this IAttachment attachment) | |||||
=> attachment.Filename.StartsWith(SpoilerPrefix); | |||||
} | |||||
} |
@@ -29,10 +29,6 @@ namespace Discord | |||||
public static EmbedBuilder WithAuthor(this EmbedBuilder builder, IUser user) => | public static EmbedBuilder WithAuthor(this EmbedBuilder builder, IUser user) => | ||||
builder.WithAuthor($"{user.Username}#{user.Discriminator}", user.GetAvatarUrl()); | builder.WithAuthor($"{user.Username}#{user.Discriminator}", user.GetAvatarUrl()); | ||||
/// <summary> Fills the embed author field with the provided user's nickname and avatar URL; username is used if nickname is not set. </summary> | |||||
public static EmbedBuilder WithAuthor(this EmbedBuilder builder, IGuildUser user) => | |||||
builder.WithAuthor($"{user.Nickname ?? user.Username}#{user.Discriminator}", user.GetAvatarUrl()); | |||||
/// <summary> Converts a <see cref="EmbedType.Rich"/> <see cref="IEmbed"/> object to a <see cref="EmbedBuilder"/>. </summary> | /// <summary> Converts a <see cref="EmbedType.Rich"/> <see cref="IEmbed"/> object to a <see cref="EmbedBuilder"/>. </summary> | ||||
/// <exception cref="InvalidOperationException">The embed type is not <see cref="EmbedType.Rich"/>.</exception> | /// <exception cref="InvalidOperationException">The embed type is not <see cref="EmbedType.Rich"/>.</exception> | ||||
public static EmbedBuilder ToEmbedBuilder(this IEmbed embed) | public static EmbedBuilder ToEmbedBuilder(this IEmbed embed) | ||||
@@ -4,7 +4,7 @@ namespace Discord | |||||
public static class Format | public static class Format | ||||
{ | { | ||||
// Characters which need escaping | // Characters which need escaping | ||||
private static readonly string[] SensitiveCharacters = { "\\", "*", "_", "~", "`" }; | |||||
private static readonly string[] SensitiveCharacters = { "\\", "*", "_", "~", "`", "|" }; | |||||
/// <summary> Returns a markdown-formatted string with bold formatting. </summary> | /// <summary> Returns a markdown-formatted string with bold formatting. </summary> | ||||
public static string Bold(string text) => $"**{text}**"; | public static string Bold(string text) => $"**{text}**"; | ||||
@@ -14,6 +14,8 @@ namespace Discord | |||||
public static string Underline(string text) => $"__{text}__"; | public static string Underline(string text) => $"__{text}__"; | ||||
/// <summary> Returns a markdown-formatted string with strikethrough formatting. </summary> | /// <summary> Returns a markdown-formatted string with strikethrough formatting. </summary> | ||||
public static string Strikethrough(string text) => $"~~{text}~~"; | public static string Strikethrough(string text) => $"~~{text}~~"; | ||||
/// <summary> Returns a string with spoiler formatting. </summary> | |||||
public static string Spoiler(string text) => $"||{text}||"; | |||||
/// <summary> Returns a markdown-formatted URL. Only works in <see cref="EmbedBuilder"/> descriptions and fields. </summary> | /// <summary> Returns a markdown-formatted URL. Only works in <see cref="EmbedBuilder"/> descriptions and fields. </summary> | ||||
public static string Url(string text, string url) => $"[{text}]({url})"; | public static string Url(string text, string url) => $"[{text}]({url})"; | ||||
/// <summary> Escapes a URL so that a preview is not generated. </summary> | /// <summary> Escapes a URL so that a preview is not generated. </summary> | ||||
@@ -63,7 +63,7 @@ namespace Discord | |||||
/// Gets a generic channel. | /// Gets a generic channel. | ||||
/// </summary> | /// </summary> | ||||
/// <example> | /// <example> | ||||
/// <code lang="cs" title="Example method"> | |||||
/// <code language="cs" title="Example method"> | |||||
/// var channel = await _client.GetChannelAsync(381889909113225237); | /// var channel = await _client.GetChannelAsync(381889909113225237); | ||||
/// if (channel != null && channel is IMessageChannel msgChannel) | /// if (channel != null && channel is IMessageChannel msgChannel) | ||||
/// { | /// { | ||||
@@ -194,7 +194,7 @@ namespace Discord | |||||
/// Gets a user. | /// Gets a user. | ||||
/// </summary> | /// </summary> | ||||
/// <example> | /// <example> | ||||
/// <code lang="cs" title="Example method"> | |||||
/// <code language="cs" title="Example method"> | |||||
/// var user = await _client.GetUserAsync(168693960628371456); | /// var user = await _client.GetUserAsync(168693960628371456); | ||||
/// if (user != null) | /// if (user != null) | ||||
/// Console.WriteLine($"{user} is created at {user.CreatedAt}."; | /// Console.WriteLine($"{user} is created at {user.CreatedAt}."; | ||||
@@ -212,7 +212,7 @@ namespace Discord | |||||
/// Gets a user. | /// Gets a user. | ||||
/// </summary> | /// </summary> | ||||
/// <example> | /// <example> | ||||
/// <code lang="cs" title="Example method"> | |||||
/// <code language="cs" title="Example method"> | |||||
/// var user = await _client.GetUserAsync("Still", "2876"); | /// var user = await _client.GetUserAsync("Still", "2876"); | ||||
/// if (user != null) | /// if (user != null) | ||||
/// Console.WriteLine($"{user} is created at {user.CreatedAt}."; | /// Console.WriteLine($"{user} is created at {user.CreatedAt}."; | ||||
@@ -232,7 +232,7 @@ namespace Discord | |||||
/// </summary> | /// </summary> | ||||
/// <example> | /// <example> | ||||
/// The following example gets the most optimal voice region from the collection. | /// The following example gets the most optimal voice region from the collection. | ||||
/// <code lang="cs"> | |||||
/// <code language="cs"> | |||||
/// var regions = await client.GetVoiceRegionsAsync(); | /// var regions = await client.GetVoiceRegionsAsync(); | ||||
/// var optimalRegion = regions.FirstOrDefault(x => x.IsOptimal); | /// var optimalRegion = regions.FirstOrDefault(x => x.IsOptimal); | ||||
/// </code> | /// </code> | ||||
@@ -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; | ||||
} | } | ||||
@@ -76,6 +119,25 @@ namespace Discord | |||||
return DecodeBase64UserId(segments[0]).HasValue; | return DecodeBase64UserId(segments[0]).HasValue; | ||||
} | } | ||||
/// <summary> | |||||
/// The set of all characters that are not allowed inside of a token. | |||||
/// </summary> | |||||
internal static char[] IllegalTokenCharacters = new char[] | |||||
{ | |||||
' ', '\t', '\r', '\n' | |||||
}; | |||||
/// <summary> | |||||
/// Checks if the given token contains a whitespace or newline character | |||||
/// that would fail to log in. | |||||
/// </summary> | |||||
/// <param name="token"> The token to validate. </param> | |||||
/// <returns> | |||||
/// True if the token contains a whitespace or newline character. | |||||
/// </returns> | |||||
internal static bool CheckContainsIllegalCharacters(string token) | |||||
=> token.IndexOfAny(IllegalTokenCharacters) != -1; | |||||
/// <summary> | /// <summary> | ||||
/// Checks the validity of the supplied token of a specific type. | /// Checks the validity of the supplied token of a specific type. | ||||
/// </summary> | /// </summary> | ||||
@@ -88,6 +150,9 @@ namespace Discord | |||||
// A Null or WhiteSpace token of any type is invalid. | // A Null or WhiteSpace token of any type is invalid. | ||||
if (string.IsNullOrWhiteSpace(token)) | if (string.IsNullOrWhiteSpace(token)) | ||||
throw new ArgumentNullException(paramName: nameof(token), message: "A token cannot be null, empty, or contain only whitespace."); | throw new ArgumentNullException(paramName: nameof(token), message: "A token cannot be null, empty, or contain only whitespace."); | ||||
// ensure that there are no whitespace or newline characters | |||||
if (CheckContainsIllegalCharacters(token)) | |||||
throw new ArgumentException(message: "The token contains a whitespace or newline character. Ensure that the token has been properly trimmed.", paramName: nameof(token)); | |||||
switch (tokenType) | switch (tokenType) | ||||
{ | { | ||||
@@ -0,0 +1,46 @@ | |||||
using System; | |||||
using System.Threading.Tasks; | |||||
using JetBrains.Annotations; | |||||
namespace Discord.Net.Examples.Core.Entities.Channels | |||||
{ | |||||
[PublicAPI] | |||||
internal class GuildChannelExamples | |||||
{ | |||||
#region AddPermissionOverwriteAsyncRole | |||||
public async Task MuteRoleAsync(IRole role, IGuildChannel channel) | |||||
{ | |||||
if (role == null) throw new ArgumentNullException(nameof(role)); | |||||
if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||||
// Fetches the previous overwrite and bail if one is found | |||||
var previousOverwrite = channel.GetPermissionOverwrite(role); | |||||
if (previousOverwrite.HasValue && previousOverwrite.Value.SendMessages == PermValue.Deny) | |||||
throw new InvalidOperationException($"Role {role.Name} had already been muted in this channel."); | |||||
// Creates a new OverwritePermissions with send message set to deny and pass it into the method | |||||
await channel.AddPermissionOverwriteAsync(role, new OverwritePermissions(sendMessages: PermValue.Deny)); | |||||
} | |||||
#endregion | |||||
#region AddPermissionOverwriteAsyncUser | |||||
public async Task MuteUserAsync(IGuildUser user, IGuildChannel channel) | |||||
{ | |||||
if (user == null) throw new ArgumentNullException(nameof(user)); | |||||
if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||||
// Fetches the previous overwrite and bail if one is found | |||||
var previousOverwrite = channel.GetPermissionOverwrite(user); | |||||
if (previousOverwrite.HasValue && previousOverwrite.Value.SendMessages == PermValue.Deny) | |||||
throw new InvalidOperationException($"User {user.Username} had already been muted in this channel."); | |||||
// Creates a new OverwritePermissions with send message set to deny and pass it into the method | |||||
await channel.AddPermissionOverwriteAsync(user, new OverwritePermissions(sendMessages: PermValue.Deny)); | |||||
} | |||||
#endregion | |||||
} | |||||
} |
@@ -0,0 +1,114 @@ | |||||
using System; | |||||
using System.Linq; | |||||
using System.Net.Http; | |||||
using System.Threading.Tasks; | |||||
using JetBrains.Annotations; | |||||
namespace Discord.Net.Examples.Core.Entities.Channels | |||||
{ | |||||
[PublicAPI] | |||||
internal class MessageChannelExamples | |||||
{ | |||||
#region GetMessagesAsync.FromId.BeginningMessages | |||||
public async Task PrintFirstMessages(IMessageChannel channel, int messageCount) | |||||
{ | |||||
// Although the library does attempt to divide the messageCount by 100 | |||||
// to comply to Discord's maximum message limit per request, sending | |||||
// too many could still cause the queue to clog up. | |||||
// The purpose of this exception is to discourage users from sending | |||||
// too many requests at once. | |||||
if (messageCount > 1000) | |||||
throw new InvalidOperationException("Too many messages requested."); | |||||
// Setting fromMessageId to 0 will make Discord | |||||
// default to the first message in channel. | |||||
var messages = await channel.GetMessagesAsync( | |||||
0, Direction.After, messageCount) | |||||
.FlattenAsync(); | |||||
// Print message content | |||||
foreach (var message in messages) | |||||
Console.WriteLine($"{message.Author} posted '{message.Content}' at {message.CreatedAt}."); | |||||
} | |||||
#endregion | |||||
public async Task GetMessagesExampleBody(IMessageChannel channel) | |||||
{ | |||||
#pragma warning disable IDISP001 | |||||
#pragma warning disable IDISP014 | |||||
// We're just declaring this for the sample below. | |||||
// Ideally, you want to get or create your HttpClient | |||||
// from IHttpClientFactory. | |||||
// You get a bonus for reading the example source though! | |||||
var httpClient = new HttpClient(); | |||||
#pragma warning restore IDISP014 | |||||
#pragma warning restore IDISP001 | |||||
// Another dummy method | |||||
Task LongRunningAsync() | |||||
{ | |||||
return Task.Delay(0); | |||||
} | |||||
#region GetMessagesAsync.FromLimit.Standard | |||||
var messages = await channel.GetMessagesAsync(300).FlattenAsync(); | |||||
var userMessages = messages.Where(x => x.Author.Id == 53905483156684800); | |||||
#endregion | |||||
#region GetMessagesAsync.FromMessage | |||||
var oldMessage = await channel.SendMessageAsync("boi"); | |||||
var messagesFromMsg = await channel.GetMessagesAsync(oldMessage, Direction.Before, 5).FlattenAsync(); | |||||
#endregion | |||||
#region GetMessagesAsync.FromId.FromMessage | |||||
await channel.GetMessagesAsync(442012544660537354, Direction.Before, 5).FlattenAsync(); | |||||
#endregion | |||||
#region SendMessageAsync | |||||
var message = await channel.SendMessageAsync(DateTimeOffset.UtcNow.ToString("R")); | |||||
await Task.Delay(TimeSpan.FromSeconds(5)) | |||||
.ContinueWith(x => message.DeleteAsync()); | |||||
#endregion | |||||
#region SendFileAsync.FilePath | |||||
await channel.SendFileAsync("wumpus.txt", "good discord boi"); | |||||
#endregion | |||||
#region SendFileAsync.FilePath.EmbeddedImage | |||||
await channel.SendFileAsync("b1nzy.jpg", | |||||
embed: new EmbedBuilder {ImageUrl = "attachment://b1nzy.jpg"}.Build()); | |||||
#endregion | |||||
#region SendFileAsync.FileStream.EmbeddedImage | |||||
using (var b1nzyStream = await httpClient.GetStreamAsync("https://example.com/b1nzy")) | |||||
await channel.SendFileAsync(b1nzyStream, "b1nzy.jpg", | |||||
embed: new EmbedBuilder {ImageUrl = "attachment://b1nzy.jpg"}.Build()); | |||||
#endregion | |||||
#region EnterTypingState | |||||
using (channel.EnterTypingState()) await LongRunningAsync(); | |||||
#endregion | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,25 @@ | |||||
using System; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
using JetBrains.Annotations; | |||||
namespace Discord.Net.Examples.Core.Entities.Guilds | |||||
{ | |||||
[PublicAPI] | |||||
internal class GuildExamples | |||||
{ | |||||
#region CreateTextChannelAsync | |||||
public async Task CreateTextChannelUnderWumpus(IGuild guild, string name) | |||||
{ | |||||
var categories = await guild.GetCategoriesAsync(); | |||||
var targetCategory = categories.FirstOrDefault(x => x.Name == "wumpus"); | |||||
if (targetCategory == null) return; | |||||
await guild.CreateTextChannelAsync(name, x => | |||||
{ | |||||
x.CategoryId = targetCategory.Id; | |||||
x.Topic = $"This channel was created at {DateTimeOffset.UtcNow}."; | |||||
}); | |||||
} | |||||
#endregion | |||||
} | |||||
} |
@@ -0,0 +1,38 @@ | |||||
using System; | |||||
using System.Net; | |||||
using System.Threading.Tasks; | |||||
using JetBrains.Annotations; | |||||
namespace Discord.Net.Examples.Core.Entities.Users | |||||
{ | |||||
[PublicAPI] | |||||
internal class UserExamples | |||||
{ | |||||
#region GetAvatarUrl | |||||
public async Task GetAvatarAsync(IUser user, ITextChannel textChannel) | |||||
{ | |||||
var userAvatarUrl = user.GetAvatarUrl() ?? user.GetDefaultAvatarUrl(); | |||||
await textChannel.SendMessageAsync(userAvatarUrl); | |||||
} | |||||
#endregion | |||||
#region GetOrCreateDMChannelAsync | |||||
public async Task MessageUserAsync(IUser user) | |||||
{ | |||||
var channel = await user.GetOrCreateDMChannelAsync(); | |||||
try | |||||
{ | |||||
await channel.SendMessageAsync("Awesome stuff!"); | |||||
} | |||||
catch (Discord.Net.HttpException ex) when (ex.HttpCode == HttpStatusCode.Forbidden) | |||||
{ | |||||
Console.WriteLine($"Boo, I cannot message {user}."); | |||||
} | |||||
} | |||||
#endregion | |||||
} | |||||
} |
@@ -0,0 +1,21 @@ | |||||
<Project Sdk="Microsoft.NET.Sdk"> | |||||
<PropertyGroup> | |||||
<TargetFramework>netstandard2.0</TargetFramework> | |||||
</PropertyGroup> | |||||
<ItemGroup> | |||||
<Compile Remove="Core\Entities\Guilds\IGuild.Examples.cs" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<None Include="Core\Entities\Guilds\IGuild.Examples.cs" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" /> | |||||
<ProjectReference Include="..\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" /> | |||||
<PackageReference Include="JetBrains.Annotations" Version="2018.3.0" /> | |||||
</ItemGroup> | |||||
</Project> |
@@ -0,0 +1,117 @@ | |||||
using System; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
using Discord.WebSocket; | |||||
using JetBrains.Annotations; | |||||
namespace Discord.Net.Examples.WebSocket | |||||
{ | |||||
[PublicAPI] | |||||
internal class BaseSocketClientExamples | |||||
{ | |||||
#region ReactionAdded | |||||
public void HookReactionAdded(BaseSocketClient client) | |||||
=> client.ReactionAdded += HandleReactionAddedAsync; | |||||
public async Task HandleReactionAddedAsync(Cacheable<IUserMessage, ulong> cachedMessage, | |||||
ISocketMessageChannel originChannel, SocketReaction reaction) | |||||
{ | |||||
var message = await cachedMessage.GetOrDownloadAsync(); | |||||
if (message != null && reaction.User.IsSpecified) | |||||
Console.WriteLine($"{reaction.User.Value} just added a reaction '{reaction.Emote}' " + | |||||
$"to {message.Author}'s message ({message.Id})."); | |||||
} | |||||
#endregion | |||||
#region ChannelCreated | |||||
public void HookChannelCreated(BaseSocketClient client) | |||||
=> client.ChannelCreated += HandleChannelCreated; | |||||
public Task HandleChannelCreated(SocketChannel channel) | |||||
{ | |||||
if (channel is SocketGuildChannel guildChannel) | |||||
Console.WriteLine($"A new channel '{guildChannel.Name}'({guildChannel.Id}, {guildChannel.GetType()})" | |||||
+ $"has been created at {guildChannel.CreatedAt}."); | |||||
return Task.CompletedTask; | |||||
} | |||||
#endregion | |||||
#region ChannelDestroyed | |||||
public void HookChannelDestroyed(BaseSocketClient client) | |||||
=> client.ChannelDestroyed += HandleChannelDestroyed; | |||||
public Task HandleChannelDestroyed(SocketChannel channel) | |||||
{ | |||||
if (channel is SocketGuildChannel guildChannel) | |||||
Console.WriteLine( | |||||
$"A new channel '{guildChannel.Name}'({guildChannel.Id}, {guildChannel.GetType()}) has been deleted."); | |||||
return Task.CompletedTask; | |||||
} | |||||
#endregion | |||||
#region ChannelUpdated | |||||
public void HookChannelUpdated(BaseSocketClient client) | |||||
=> client.ChannelUpdated += HandleChannelRename; | |||||
public Task HandleChannelRename(SocketChannel beforeChannel, SocketChannel afterChannel) | |||||
{ | |||||
if (beforeChannel is SocketGuildChannel beforeGuildChannel && | |||||
afterChannel is SocketGuildChannel afterGuildChannel) | |||||
if (beforeGuildChannel.Name != afterGuildChannel.Name) | |||||
Console.WriteLine( | |||||
$"A channel ({beforeChannel.Id}) is renamed from {beforeGuildChannel.Name} to {afterGuildChannel.Name}."); | |||||
return Task.CompletedTask; | |||||
} | |||||
#endregion | |||||
#region MessageReceived | |||||
private readonly ulong[] _targetUserIds = {168693960628371456, 53905483156684800}; | |||||
public void HookMessageReceived(BaseSocketClient client) | |||||
=> client.MessageReceived += HandleMessageReceived; | |||||
public Task HandleMessageReceived(SocketMessage message) | |||||
{ | |||||
// check if the message is a user message as opposed to a system message (e.g. Clyde, pins, etc.) | |||||
if (!(message is SocketUserMessage userMessage)) return Task.CompletedTask; | |||||
// check if the message origin is a guild message channel | |||||
if (!(userMessage.Channel is SocketTextChannel textChannel)) return Task.CompletedTask; | |||||
// check if the target user was mentioned | |||||
var targetUsers = userMessage.MentionedUsers.Where(x => _targetUserIds.Contains(x.Id)); | |||||
foreach (var targetUser in targetUsers) | |||||
Console.WriteLine( | |||||
$"{targetUser} was mentioned in the message '{message.Content}' by {message.Author} in {textChannel.Name}."); | |||||
return Task.CompletedTask; | |||||
} | |||||
#endregion | |||||
#region MessageDeleted | |||||
public void HookMessageDeleted(BaseSocketClient client) | |||||
=> client.MessageDeleted += HandleMessageDelete; | |||||
public Task HandleMessageDelete(Cacheable<IMessage, ulong> cachedMessage, ISocketMessageChannel channel) | |||||
{ | |||||
// check if the message exists in cache; if not, we cannot report what was removed | |||||
if (!cachedMessage.HasValue) return Task.CompletedTask; | |||||
var message = cachedMessage.Value; | |||||
Console.WriteLine( | |||||
$"A message ({message.Id}) from {message.Author} was removed from the channel {channel.Name} ({channel.Id}):" | |||||
+ Environment.NewLine | |||||
+ message.Content); | |||||
return Task.CompletedTask; | |||||
} | |||||
#endregion | |||||
} | |||||
} |
@@ -19,6 +19,7 @@ namespace Discord.API.Rest | |||||
public Optional<string> Nonce { get; set; } | public Optional<string> Nonce { get; set; } | ||||
public Optional<bool> IsTTS { get; set; } | public Optional<bool> IsTTS { get; set; } | ||||
public Optional<Embed> Embed { get; set; } | public Optional<Embed> Embed { get; set; } | ||||
public bool IsSpoiler { get; set; } = false; | |||||
public UploadFileParams(Stream file) | public UploadFileParams(Stream file) | ||||
{ | { | ||||
@@ -28,7 +29,10 @@ namespace Discord.API.Rest | |||||
public IReadOnlyDictionary<string, object> ToDictionary() | public IReadOnlyDictionary<string, object> ToDictionary() | ||||
{ | { | ||||
var d = new Dictionary<string, object>(); | var d = new Dictionary<string, object>(); | ||||
d["file"] = new MultipartFile(File, Filename.GetValueOrDefault("unknown.dat")); | |||||
var filename = Filename.GetValueOrDefault("unknown.dat"); | |||||
if (IsSpoiler && !filename.StartsWith(AttachmentExtensions.SpoilerPrefix)) | |||||
filename = filename.Insert(0, AttachmentExtensions.SpoilerPrefix); | |||||
d["file"] = new MultipartFile(File, filename); | |||||
var payload = new Dictionary<string, object>(); | var payload = new Dictionary<string, object>(); | ||||
if (Content.IsSpecified) | if (Content.IsSpecified) | ||||
@@ -39,6 +43,8 @@ namespace Discord.API.Rest | |||||
payload["nonce"] = Nonce.Value; | payload["nonce"] = Nonce.Value; | ||||
if (Embed.IsSpecified) | if (Embed.IsSpecified) | ||||
payload["embed"] = Embed.Value; | payload["embed"] = Embed.Value; | ||||
if (IsSpoiler) | |||||
payload["hasSpoiler"] = IsSpoiler.ToString(); | |||||
var json = new StringBuilder(); | var json = new StringBuilder(); | ||||
using (var text = new StringWriter(json)) | using (var text = new StringWriter(json)) | ||||
@@ -22,6 +22,8 @@ namespace Discord.API.Rest | |||||
public Optional<string> AvatarUrl { get; set; } | public Optional<string> AvatarUrl { get; set; } | ||||
public Optional<Embed[]> Embeds { get; set; } | public Optional<Embed[]> Embeds { get; set; } | ||||
public bool IsSpoiler { get; set; } = false; | |||||
public UploadWebhookFileParams(Stream file) | public UploadWebhookFileParams(Stream file) | ||||
{ | { | ||||
File = file; | File = file; | ||||
@@ -30,7 +32,11 @@ namespace Discord.API.Rest | |||||
public IReadOnlyDictionary<string, object> ToDictionary() | public IReadOnlyDictionary<string, object> ToDictionary() | ||||
{ | { | ||||
var d = new Dictionary<string, object>(); | var d = new Dictionary<string, object>(); | ||||
d["file"] = new MultipartFile(File, Filename.GetValueOrDefault("unknown.dat")); | |||||
var filename = Filename.GetValueOrDefault("unknown.dat"); | |||||
if (IsSpoiler && !filename.StartsWith(AttachmentExtensions.SpoilerPrefix)) | |||||
filename = filename.Insert(0, AttachmentExtensions.SpoilerPrefix); | |||||
d["file"] = new MultipartFile(File, filename); | |||||
var payload = new Dictionary<string, object>(); | var payload = new Dictionary<string, object>(); | ||||
if (Content.IsSpecified) | if (Content.IsSpecified) | ||||
@@ -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; } | |||||
} | } | ||||
} | } |
@@ -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> | ||||
@@ -199,18 +199,18 @@ namespace Discord.Rest | |||||
/// <exception cref="IOException">An I/O error occurred while opening the file.</exception> | /// <exception cref="IOException">An I/O error occurred while opening the file.</exception> | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
public static async Task<RestUserMessage> SendFileAsync(IMessageChannel channel, BaseDiscordClient client, | public static async Task<RestUserMessage> SendFileAsync(IMessageChannel channel, BaseDiscordClient client, | ||||
string filePath, string text, bool isTTS, Embed embed, RequestOptions options) | |||||
string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler) | |||||
{ | { | ||||
string filename = Path.GetFileName(filePath); | string filename = Path.GetFileName(filePath); | ||||
using (var file = File.OpenRead(filePath)) | using (var file = File.OpenRead(filePath)) | ||||
return await SendFileAsync(channel, client, file, filename, text, isTTS, embed, options).ConfigureAwait(false); | |||||
return await SendFileAsync(channel, client, file, filename, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false); | |||||
} | } | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
public static async Task<RestUserMessage> SendFileAsync(IMessageChannel channel, BaseDiscordClient client, | public static async Task<RestUserMessage> SendFileAsync(IMessageChannel channel, BaseDiscordClient client, | ||||
Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) | |||||
Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler) | |||||
{ | { | ||||
var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS, Embed = embed != null ? embed.ToModel() : Optional<API.Embed>.Unspecified }; | |||||
var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS, Embed = embed != null ? embed.ToModel() : Optional<API.Embed>.Unspecified, IsSpoiler = isSpoiler }; | |||||
var model = await client.ApiClient.UploadFileAsync(channel.Id, args, options).ConfigureAwait(false); | var model = await client.ApiClient.UploadFileAsync(channel.Id, args, options).ConfigureAwait(false); | ||||
return RestUserMessage.Create(client, channel, client.CurrentUser, model); | return RestUserMessage.Create(client, channel, client.CurrentUser, model); | ||||
} | } | ||||
@@ -42,12 +42,12 @@ namespace Discord.Rest | |||||
/// A task that represents an asynchronous send operation for delivering the message. The task result | /// A task that represents an asynchronous send operation for delivering the message. The task result | ||||
/// contains the sent message. | /// contains the sent message. | ||||
/// </returns> | /// </returns> | ||||
new Task<RestUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); | |||||
new Task<RestUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false); | |||||
/// <summary> | /// <summary> | ||||
/// Sends a file to this message channel with an optional caption. | /// Sends a file to this message channel with an optional caption. | ||||
/// </summary> | /// </summary> | ||||
/// <remarks> | /// <remarks> | ||||
/// This method follows the same behavior as described in <see cref="IMessageChannel.SendFileAsync(Stream, string, string, bool, Embed, RequestOptions)"/>. | |||||
/// This method follows the same behavior as described in <see cref="IMessageChannel.SendFileAsync(Stream, string, string, bool, Embed, RequestOptions, bool)"/>. | |||||
/// Please visit its documentation for more details on this method. | /// Please visit its documentation for more details on this method. | ||||
/// </remarks> | /// </remarks> | ||||
/// <param name="stream">The <see cref="Stream" /> of the file to be sent.</param> | /// <param name="stream">The <see cref="Stream" /> of the file to be sent.</param> | ||||
@@ -60,7 +60,7 @@ namespace Discord.Rest | |||||
/// A task that represents an asynchronous send operation for delivering the message. The task result | /// A task that represents an asynchronous send operation for delivering the message. The task result | ||||
/// contains the sent message. | /// contains the sent message. | ||||
/// </returns> | /// </returns> | ||||
new Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); | |||||
new Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false); | |||||
/// <summary> | /// <summary> | ||||
/// Gets a message from this message channel. | /// Gets a message from this message channel. | ||||
@@ -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); | ||||
@@ -121,12 +121,12 @@ namespace Discord.Rest | |||||
/// <exception cref="NotSupportedException"><paramref name="filePath" /> is in an invalid format.</exception> | /// <exception cref="NotSupportedException"><paramref name="filePath" /> is in an invalid format.</exception> | ||||
/// <exception cref="IOException">An I/O error occurred while opening the file.</exception> | /// <exception cref="IOException">An I/O error occurred while opening the file.</exception> | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); | |||||
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options, isSpoiler); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); | |||||
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options, isSpoiler); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) | public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) | ||||
@@ -200,11 +200,11 @@ namespace Discord.Rest | |||||
async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) | async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) | ||||
=> await GetPinnedMessagesAsync(options).ConfigureAwait(false); | => await GetPinnedMessagesAsync(options).ConfigureAwait(false); | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) | |||||
=> await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); | |||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler) | |||||
=> await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) | |||||
=> await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); | |||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler) | |||||
=> await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) | async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) | ||||
=> await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); | => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); | ||||
@@ -123,12 +123,12 @@ namespace Discord.Rest | |||||
/// <exception cref="NotSupportedException"><paramref name="filePath" /> is in an invalid format.</exception> | /// <exception cref="NotSupportedException"><paramref name="filePath" /> is in an invalid format.</exception> | ||||
/// <exception cref="IOException">An I/O error occurred while opening the file.</exception> | /// <exception cref="IOException">An I/O error occurred while opening the file.</exception> | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); | |||||
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options, isSpoiler); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); | |||||
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options, isSpoiler); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public Task TriggerTypingAsync(RequestOptions options = null) | public Task TriggerTypingAsync(RequestOptions options = null) | ||||
@@ -178,11 +178,11 @@ namespace Discord.Rest | |||||
async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) | async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) | ||||
=> await GetPinnedMessagesAsync(options).ConfigureAwait(false); | => await GetPinnedMessagesAsync(options).ConfigureAwait(false); | ||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) | |||||
=> await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); | |||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler) | |||||
=> await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false); | |||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) | |||||
=> await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); | |||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler) | |||||
=> await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false); | |||||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) | async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) | ||||
=> await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); | => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); | ||||
@@ -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); | ||||
@@ -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."); | |||||
} | |||||
} | |||||
} |
@@ -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; } | ||||
@@ -129,13 +129,13 @@ namespace Discord.Rest | |||||
/// <exception cref="NotSupportedException"><paramref name="filePath" /> is in an invalid format.</exception> | /// <exception cref="NotSupportedException"><paramref name="filePath" /> is in an invalid format.</exception> | ||||
/// <exception cref="IOException">An I/O error occurred while opening the file.</exception> | /// <exception cref="IOException">An I/O error occurred while opening the file.</exception> | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); | |||||
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options, isSpoiler); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); | |||||
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false) | |||||
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options, isSpoiler); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) | public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) | ||||
@@ -266,12 +266,12 @@ namespace Discord.Rest | |||||
=> await GetPinnedMessagesAsync(options).ConfigureAwait(false); | => await GetPinnedMessagesAsync(options).ConfigureAwait(false); | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) | |||||
=> await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); | |||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler) | |||||
=> await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) | |||||
=> await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); | |||||
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler) | |||||
=> await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) | async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) | ||||
=> await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); | => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); | ||||
@@ -405,7 +405,7 @@ namespace Discord.Rest | |||||
/// </summary> | /// </summary> | ||||
/// <example> | /// <example> | ||||
/// The following example creates a new text channel under an existing category named <c>Wumpus</c> with a set topic. | /// The following example creates a new text channel under an existing category named <c>Wumpus</c> with a set topic. | ||||
/// <code lang="cs"> | |||||
/// <code language="cs"> | |||||
/// var categories = await guild.GetCategoriesAsync(); | /// var categories = await guild.GetCategoriesAsync(); | ||||
/// var targetCategory = categories.FirstOrDefault(x => x.Name == "wumpus"); | /// var targetCategory = categories.FirstOrDefault(x => x.Name == "wumpus"); | ||||
/// if (targetCategory == null) return; | /// if (targetCategory == null) return; | ||||
@@ -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; | ||||
@@ -1,4 +1,4 @@ | |||||
#pragma warning disable CS1591 | |||||
#pragma warning disable CS1591 | |||||
using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
@@ -9,6 +9,6 @@ namespace Discord.API.Gateway | |||||
[JsonProperty("channel_id")] | [JsonProperty("channel_id")] | ||||
public ulong ChannelId { get; set; } | public ulong ChannelId { get; set; } | ||||
[JsonProperty("ids")] | [JsonProperty("ids")] | ||||
public IEnumerable<ulong> Ids { get; set; } | |||||
public ulong[] Ids { get; set; } | |||||
} | } | ||||
} | } |
@@ -1,4 +1,5 @@ | |||||
using System; | using System; | ||||
using System.Collections.Generic; | |||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
@@ -18,6 +19,10 @@ namespace Discord.WebSocket | |||||
/// see the derived classes of <see cref="SocketChannel"/> for more details. | /// see the derived classes of <see cref="SocketChannel"/> for more details. | ||||
/// </para> | /// </para> | ||||
/// </remarks> | /// </remarks> | ||||
/// <example> | |||||
/// <code language="cs" region="ChannelCreated" | |||||
/// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs"/> | |||||
/// </example> | |||||
public event Func<SocketChannel, Task> ChannelCreated | public event Func<SocketChannel, Task> ChannelCreated | ||||
{ | { | ||||
add { _channelCreatedEvent.Add(value); } | add { _channelCreatedEvent.Add(value); } | ||||
@@ -36,6 +41,10 @@ namespace Discord.WebSocket | |||||
/// see the derived classes of <see cref="SocketChannel"/> for more details. | /// see the derived classes of <see cref="SocketChannel"/> for more details. | ||||
/// </para> | /// </para> | ||||
/// </remarks> | /// </remarks> | ||||
/// <example> | |||||
/// <code language="cs" region="ChannelDestroyed" | |||||
/// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs"/> | |||||
/// </example> | |||||
public event Func<SocketChannel, Task> ChannelDestroyed { | public event Func<SocketChannel, Task> ChannelDestroyed { | ||||
add { _channelDestroyedEvent.Add(value); } | add { _channelDestroyedEvent.Add(value); } | ||||
remove { _channelDestroyedEvent.Remove(value); } | remove { _channelDestroyedEvent.Remove(value); } | ||||
@@ -54,6 +63,10 @@ namespace Discord.WebSocket | |||||
/// <see cref="SocketChannel"/> for more details. | /// <see cref="SocketChannel"/> for more details. | ||||
/// </para> | /// </para> | ||||
/// </remarks> | /// </remarks> | ||||
/// <example> | |||||
/// <code language="cs" region="ChannelUpdated" | |||||
/// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs"/> | |||||
/// </example> | |||||
public event Func<SocketChannel, SocketChannel, Task> ChannelUpdated { | public event Func<SocketChannel, SocketChannel, Task> ChannelUpdated { | ||||
add { _channelUpdatedEvent.Add(value); } | add { _channelUpdatedEvent.Add(value); } | ||||
remove { _channelUpdatedEvent.Remove(value); } | remove { _channelUpdatedEvent.Remove(value); } | ||||
@@ -74,6 +87,11 @@ namespace Discord.WebSocket | |||||
/// derived classes of <see cref="SocketMessage"/> for more details. | /// derived classes of <see cref="SocketMessage"/> for more details. | ||||
/// </para> | /// </para> | ||||
/// </remarks> | /// </remarks> | ||||
/// <example> | |||||
/// <para>The example below checks if the newly received message contains the target user.</para> | |||||
/// <code language="cs" region="MessageReceived" | |||||
/// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs"/> | |||||
/// </example> | |||||
public event Func<SocketMessage, Task> MessageReceived { | public event Func<SocketMessage, Task> MessageReceived { | ||||
add { _messageReceivedEvent.Add(value); } | add { _messageReceivedEvent.Add(value); } | ||||
remove { _messageReceivedEvent.Remove(value); } | remove { _messageReceivedEvent.Remove(value); } | ||||
@@ -102,11 +120,47 @@ namespace Discord.WebSocket | |||||
/// <see cref="ISocketMessageChannel"/> parameter. | /// <see cref="ISocketMessageChannel"/> parameter. | ||||
/// </para> | /// </para> | ||||
/// </remarks> | /// </remarks> | ||||
/// <example> | |||||
/// <code language="cs" region="MessageDeleted" | |||||
/// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs" /> | |||||
/// </example> | |||||
public event Func<Cacheable<IMessage, ulong>, ISocketMessageChannel, Task> MessageDeleted { | public event Func<Cacheable<IMessage, ulong>, ISocketMessageChannel, Task> MessageDeleted { | ||||
add { _messageDeletedEvent.Add(value); } | add { _messageDeletedEvent.Add(value); } | ||||
remove { _messageDeletedEvent.Remove(value); } | remove { _messageDeletedEvent.Remove(value); } | ||||
} | } | ||||
internal readonly AsyncEvent<Func<Cacheable<IMessage, ulong>, ISocketMessageChannel, Task>> _messageDeletedEvent = new AsyncEvent<Func<Cacheable<IMessage, ulong>, ISocketMessageChannel, Task>>(); | internal readonly AsyncEvent<Func<Cacheable<IMessage, ulong>, ISocketMessageChannel, Task>> _messageDeletedEvent = new AsyncEvent<Func<Cacheable<IMessage, ulong>, ISocketMessageChannel, Task>>(); | ||||
/// <summary> Fired when multiple messages are bulk deleted. </summary> | |||||
/// <remarks> | |||||
/// <note> | |||||
/// The <see cref="MessageDeleted"/> event will not be fired for individual messages contained in this event. | |||||
/// </note> | |||||
/// <para> | |||||
/// This event is fired when multiple messages are bulk deleted. The event handler must return a | |||||
/// <see cref="Task"/> and accept an <see cref="IReadOnlyCollection{Cacheable{TEntity,TId}}"/> and | |||||
/// <see cref="ISocketMessageChannel"/> as its parameters. | |||||
/// </para> | |||||
/// <para> | |||||
/// <note type="important"> | |||||
/// It is not possible to retrieve the message via | |||||
/// <see cref="Cacheable{TEntity,TId}.DownloadAsync"/>; the message cannot be retrieved by Discord | |||||
/// after the message has been deleted. | |||||
/// </note> | |||||
/// If caching is enabled via <see cref="DiscordSocketConfig"/>, the | |||||
/// <see cref="Cacheable{TEntity,TId}"/> entity will contain the deleted message; otherwise, in event | |||||
/// that the message cannot be retrieved, the snowflake ID of the message is preserved in the | |||||
/// <see cref="ulong"/>. | |||||
/// </para> | |||||
/// <para> | |||||
/// The source channel of the removed message will be passed into the | |||||
/// <see cref="ISocketMessageChannel"/> parameter. | |||||
/// </para> | |||||
/// </remarks> | |||||
public event Func<IReadOnlyCollection<Cacheable<IMessage, ulong>>, ISocketMessageChannel, Task> MessagesBulkDeleted | |||||
{ | |||||
add { _messagesBulkDeletedEvent.Add(value); } | |||||
remove { _messagesBulkDeletedEvent.Remove(value); } | |||||
} | |||||
internal readonly AsyncEvent<Func<IReadOnlyCollection<Cacheable<IMessage, ulong>>, ISocketMessageChannel, Task>> _messagesBulkDeletedEvent = new AsyncEvent<Func<IReadOnlyCollection<Cacheable<IMessage, ulong>>, ISocketMessageChannel, Task>>(); | |||||
/// <summary> Fired when a message is updated. </summary> | /// <summary> Fired when a message is updated. </summary> | ||||
/// <remarks> | /// <remarks> | ||||
/// <para> | /// <para> | ||||
@@ -134,6 +188,35 @@ namespace Discord.WebSocket | |||||
} | } | ||||
internal readonly AsyncEvent<Func<Cacheable<IMessage, ulong>, SocketMessage, ISocketMessageChannel, Task>> _messageUpdatedEvent = new AsyncEvent<Func<Cacheable<IMessage, ulong>, SocketMessage, ISocketMessageChannel, Task>>(); | internal readonly AsyncEvent<Func<Cacheable<IMessage, ulong>, SocketMessage, ISocketMessageChannel, Task>> _messageUpdatedEvent = new AsyncEvent<Func<Cacheable<IMessage, ulong>, SocketMessage, ISocketMessageChannel, Task>>(); | ||||
/// <summary> Fired when a reaction is added to a message. </summary> | /// <summary> Fired when a reaction is added to a message. </summary> | ||||
/// <remarks> | |||||
/// <para> | |||||
/// This event is fired when a reaction is added to a user message. The event handler must return a | |||||
/// <see cref="Task"/> and accept a <see cref="Cacheable{TEntity,TId}"/>, an | |||||
/// <see cref="ISocketMessageChannel"/>, and a <see cref="SocketReaction"/> as its parameter. | |||||
/// </para> | |||||
/// <para> | |||||
/// If caching is enabled via <see cref="DiscordSocketConfig"/>, the | |||||
/// <see cref="Cacheable{TEntity,TId}"/> entity will contain the original message; otherwise, in event | |||||
/// that the message cannot be retrieved, the snowflake ID of the message is preserved in the | |||||
/// <see cref="ulong"/>. | |||||
/// </para> | |||||
/// <para> | |||||
/// The source channel of the reaction addition will be passed into the | |||||
/// <see cref="ISocketMessageChannel"/> parameter. | |||||
/// </para> | |||||
/// <para> | |||||
/// The reaction that was added will be passed into the <see cref="SocketReaction"/> parameter. | |||||
/// </para> | |||||
/// <note> | |||||
/// When fetching the reaction from this event, a user may not be provided under | |||||
/// <see cref="SocketReaction.User"/>. Please see the documentation of the property for more | |||||
/// information. | |||||
/// </note> | |||||
/// </remarks> | |||||
/// <example> | |||||
/// <code language="cs" region="ReactionAdded" | |||||
/// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs"/> | |||||
/// </example> | |||||
public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task> ReactionAdded { | public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task> ReactionAdded { | ||||
add { _reactionAddedEvent.Add(value); } | add { _reactionAddedEvent.Add(value); } | ||||
remove { _reactionAddedEvent.Remove(value); } | remove { _reactionAddedEvent.Remove(value); } | ||||
@@ -97,13 +97,15 @@ namespace Discord.WebSocket | |||||
/// <remarks> | /// <remarks> | ||||
/// This method gets the user present in the WebSocket cache with the given condition. | /// This method gets the user present in the WebSocket cache with the given condition. | ||||
/// <note type="warning"> | /// <note type="warning"> | ||||
/// Sometimes a user may return <c>null</c> due to Discord not sending offline users in large | |||||
/// guilds (i.e. guild with 100+ members) actively. To download users on startup, consider enabling | |||||
/// <see cref="DiscordSocketConfig.AlwaysDownloadUsers"/>. | |||||
/// Sometimes a user may return <c>null</c> due to Discord not sending offline users in large guilds | |||||
/// (i.e. guild with 100+ members) actively. To download users on startup and to see more information | |||||
/// about this subject, see <see cref="Discord.WebSocket.DiscordSocketConfig.AlwaysDownloadUsers" />. | |||||
/// </note> | /// </note> | ||||
/// <note> | /// <note> | ||||
/// This method does not attempt to fetch users that the logged-in user does not have access to (i.e. | /// This method does not attempt to fetch users that the logged-in user does not have access to (i.e. | ||||
/// users who don't share mutual guild(s) with the current user). | |||||
/// users who don't share mutual guild(s) with the current user). If you wish to get a user that you do | |||||
/// not have access to, consider using the REST implementation of | |||||
/// <see cref="DiscordRestClient.GetUserAsync(System.UInt64,Discord.RequestOptions)" />. | |||||
/// </note> | /// </note> | ||||
/// </remarks> | /// </remarks> | ||||
/// <returns> | /// <returns> | ||||
@@ -114,20 +116,22 @@ namespace Discord.WebSocket | |||||
/// <summary> | /// <summary> | ||||
/// Gets a user. | /// Gets a user. | ||||
/// </summary> | /// </summary> | ||||
/// <param name="username">The name of the user.</param> | |||||
/// <param name="discriminator">The discriminator value of the user.</param> | |||||
/// <remarks> | /// <remarks> | ||||
/// This method gets the user present in the WebSocket cache with the given condition. | /// This method gets the user present in the WebSocket cache with the given condition. | ||||
/// <note type="warning"> | /// <note type="warning"> | ||||
/// Sometimes a user may return <c>null</c> due to Discord not sending offline users in large | |||||
/// guilds (i.e. guild with 100+ members) actively. To download users on startup, consider enabling | |||||
/// <see cref="DiscordSocketConfig.AlwaysDownloadUsers"/>. | |||||
/// Sometimes a user may return <c>null</c> due to Discord not sending offline users in large guilds | |||||
/// (i.e. guild with 100+ members) actively. To download users on startup and to see more information | |||||
/// about this subject, see <see cref="Discord.WebSocket.DiscordSocketConfig.AlwaysDownloadUsers" />. | |||||
/// </note> | /// </note> | ||||
/// <note> | /// <note> | ||||
/// This method does not attempt to fetch users that the logged-in user does not have access to (i.e. | /// This method does not attempt to fetch users that the logged-in user does not have access to (i.e. | ||||
/// users who don't share mutual guild(s) with the current user). | |||||
/// users who don't share mutual guild(s) with the current user). If you wish to get a user that you do | |||||
/// not have access to, consider using the REST implementation of | |||||
/// <see cref="DiscordRestClient.GetUserAsync(System.UInt64,Discord.RequestOptions)" />. | |||||
/// </note> | /// </note> | ||||
/// </remarks> | /// </remarks> | ||||
/// <param name="username">The name of the user.</param> | |||||
/// <param name="discriminator">The discriminator value of the user.</param> | |||||
/// <returns> | /// <returns> | ||||
/// A generic WebSocket-based user; <c>null</c> when the user cannot be found. | /// A generic WebSocket-based user; <c>null</c> when the user cannot be found. | ||||
/// </returns> | /// </returns> | ||||