diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 03ec54546..a344594aa 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -1,11 +1,28 @@
-# To get started with Dependabot version updates, you'll need to specify which
-# package ecosystems to update and where the package manifests are located.
-# Please see the documentation for all configuration options:
-# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
-
version: 2
updates:
- - package-ecosystem: "nuget" # See documentation for possible values
- directory: "/" # Location of package manifests
+ # Maintain dependencies for NuGet
+ - package-ecosystem: "nuget"
+ directory: "/"
+ schedule:
+ interval: "daily"
+ assignees:
+ - "NyuwBot"
+ reviewers:
+ - "Aiko-IT-Systems/discatsharp"
+ - "Lulalaby"
+ labels:
+ - "dependencies"
+
+ # Maintain dependencies for GitHub Actions
+ - package-ecosystem: "github-actions"
+ directory: "/"
schedule:
interval: "daily"
+ assignees:
+ - "NyuwBot"
+ reviewers:
+ - "Aiko-IT-Systems/discatsharp"
+ - "Lulalaby"
+ labels:
+ - "ci/cd"
+ - "dependencies"
diff --git a/.github/labeler.yml b/.github/labeler.yml
new file mode 100644
index 000000000..29290d17c
--- /dev/null
+++ b/.github/labeler.yml
@@ -0,0 +1,26 @@
+version: 1
+labels:
+ - label: "ci/cd"
+ files:
+ - ".github/.*"
+ - ".github/workflows/.*"
+ - label: "branding"
+ files:
+ - ".*.jpg"
+ - ".*.png"
+ - ".*.svg"
+ - ".*.jpeg"
+ - ".*.guf"
+ - ".*.css"
+ - label: "documentation"
+ files:
+ - "DisCatSharp.Docs/.*"
+ - label: "core"
+ files:
+ - "DisCatSharp/.*"
+ - label: "experimental"
+ title: ^[Exp]:.*
+ - label: "dependencies"
+ files:
+ - ".*.csproj"
+ - ".*.sln"
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 729f0837c..7b1eea54f 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -1,57 +1,77 @@
name: "DisCatSharp Docs"
on:
push:
branches: [ main ]
workflow_dispatch:
jobs:
build:
- runs-on: ubuntu-latest
+ runs-on: windows-latest
steps:
- uses: actions/checkout@v3
with:
path: DisCatSharp
+
- uses: actions/checkout@v3
with:
repository: Aiko-IT-Systems/DisCatSharp.Docs
path: DisCatSharp.Docs
token: ${{ secrets.NYUW_TOKEN_GH }}
-
- - name: Get SSH
- uses: webfactory/ssh-agent@v0.5.3
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v2
with:
- ssh-private-key: ${{ secrets.AITSYS_SSH }}
+ dotnet-version: 6.0.202
+
+ - name: Restore dependencies
+ working-directory: ./DisCatSharp
+ run: dotnet restore
+
+ - name: Build Projects
+ working-directory: ./DisCatSharp
+ run: dotnet build --no-restore
- name: Build Docs
working-directory: ./DisCatSharp
shell: pwsh
run: |
./rebuild-docs.ps1 -DocsPath "./DisCatSharp.Docs" -Output ".." -PackageName "dcs-docs"
- name: Purge old docs
working-directory: ./DisCatSharp.Docs
+ shell: pwsh
run: |
- shopt -s extglob
- rm -rf !(.git|.gitignore)
+ Get-ChildItem -Exclude .git* | Remove-Item -Recurse -Force
- name: Extract new docs
+ shell: pwsh
run: |
- tar -xf dcs-docs.tar.xz -C ./DisCatSharp.Docs
+ Expand-Archive -Path dcs-docs.zip DisCatSharp.Docs/
- name: Commit and push changes
uses: EndBug/add-and-commit@main
with:
cwd: ./DisCatSharp.Docs
default_author: user_info
author_name: DisCatSharp
author_email: team@aitsys.dev
committer_name: NyuwBot
committer_email: nyuw@aitsys.dev
commit: --signoff
message: 'Docs update for commit ${{ github.repository }} (${{ github.sha }})'
- - name: Publish to Prod
- run: |
- ssh -o StrictHostKeyChecking=no -T root@80.153.182.68 -f 'cd /var/www/dcs/docs && git pull -f'
+ publish:
+ runs-on: ubuntu-latest
+ needs: build
+
+ steps:
+ - name: Get SSH Agent
+ uses: webfactory/ssh-agent@v0.5.4
+ with:
+ ssh-private-key: ${{ secrets.AITSYS_SSH }}
+
+ - name: Publish on server
+ run: |
+ ssh -o StrictHostKeyChecking=no -T root@80.153.182.68 -f 'cd /var/www/dcs/docs && git pull -f'
diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
index 068fad201..c91fd07d0 100644
--- a/.github/workflows/dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -1,30 +1,30 @@
name: "DisCatSharp .NET"
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
workflow_dispatch:
jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
- dotnet: [6.0.x]
+ dotnet: [6.0.202]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v2
with:
dotnet-version: ${{ matrix.dotnet }}
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore
- name: Test
run: dotnet test --no-build --verbosity normal
diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml
new file mode 100644
index 000000000..f885850bf
--- /dev/null
+++ b/.github/workflows/labeler.yml
@@ -0,0 +1,16 @@
+name: Label PRs
+
+on:
+ pull_request_target:
+ types:
+ - opened
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: srvaroa/labeler@v0.9
+ env:
+ GITHUB_TOKEN: "${{ secrets.NYUW_TOKEN_GH }}"
diff --git a/.gitignore b/.gitignore
index 57018eb60..902ce304f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,308 +1,325 @@
# Created by https://www.gitignore.io/api/visualstudio
### VisualStudio ###
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
*.vcxproj.filters
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# NuGET
[Nn]uget/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# DNX
project.lock.json
project.fragment.lock.json
artifacts/
**/Properties/launchSettings.json
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush
.cr/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/
### VisualStudio Patch ###
build/
### Built docs
DisCatSharp.Docs/_site/
# Docs build artifacts
docfx/
7zip/
# Build artifacts
artifacts/
### Test files
config.json
ffmpeg.exe
*.pcm
appveyor-test.ps1
*.diff
CodeMap1.dgml
.vscode/launch.json
*.patch
dcs-docs-preview.tar.xz
dcs-docs.tar.xz
+dcs-docs.zip
+dcs-docs-preview.zip
_site/
+
+#Ignore thumbnails created by Windows
+Thumbs.db
+#Ignore files built by Visual Studio
+*.exe
+*.bak
+*.cache
+[Bb]in
+[Dd]ebug*/
+*.lib
+obj/
+[Rr]elease*/
+[Tt]est[Rr]esult*
+#Nuget packages folder
+packages/
diff --git a/DisCatSharp.ApplicationCommands/DisCatSharp.ApplicationCommands.csproj b/DisCatSharp.ApplicationCommands/DisCatSharp.ApplicationCommands.csproj
index ea5fbec84..61ec08f77 100644
--- a/DisCatSharp.ApplicationCommands/DisCatSharp.ApplicationCommands.csproj
+++ b/DisCatSharp.ApplicationCommands/DisCatSharp.ApplicationCommands.csproj
@@ -1,41 +1,32 @@
DisCatSharp.ApplicationCommands
DisCatSharp.ApplicationCommands
DisCatSharp.ApplicationCommands
ApplicationCommands for DisCatSharp
discord, discord-api, bots, discord-bots, chat, dcs, discatsharp, csharp, dotnet, vb-net, fsharp, slash, slashcommands, contextmenu
-
- LICENSE.md
-
-
- True
-
-
-
-
diff --git a/DisCatSharp.ApplicationCommands/GlobalSuppressions.cs b/DisCatSharp.ApplicationCommands/GlobalSuppressions.cs
index 99b05b664..45e03fe21 100644
--- a/DisCatSharp.ApplicationCommands/GlobalSuppressions.cs
+++ b/DisCatSharp.ApplicationCommands/GlobalSuppressions.cs
@@ -1,39 +1,44 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Style", "IDE0044:Add readonly modifier", Justification = "", Scope = "member", Target = "~F:DisCatSharp.ApplicationCommands.ApplicationCommandsExtension.s_registeredCommands")]
[assembly: SuppressMessage("Style", "IDE0018:Inline variable declaration", Justification = "", Scope = "member", Target = "~M:DisCatSharp.ApplicationCommands.RegistrationWorker.BuildGuildCreateList(System.UInt64,System.Collections.Generic.List{DisCatSharp.Entities.DiscordApplicationCommand})~System.Collections.Generic.List{DisCatSharp.Entities.DiscordApplicationCommand}")]
[assembly: SuppressMessage("Style", "IDE0018:Inline variable declaration", Justification = "", Scope = "member", Target = "~M:DisCatSharp.ApplicationCommands.RegistrationWorker.BuildGuildDeleteList(System.UInt64,System.Collections.Generic.List{DisCatSharp.Entities.DiscordApplicationCommand})~System.Collections.Generic.List{System.UInt64}")]
[assembly: SuppressMessage("Style", "IDE0018:Inline variable declaration", Justification = "", Scope = "member", Target = "~M:DisCatSharp.ApplicationCommands.RegistrationWorker.BuildGuildOverwriteList(System.UInt64,System.Collections.Generic.List{DisCatSharp.Entities.DiscordApplicationCommand})~System.ValueTuple{System.Collections.Generic.Dictionary{System.UInt64,DisCatSharp.Entities.DiscordApplicationCommand},System.Collections.Generic.List{DisCatSharp.Entities.DiscordApplicationCommand}}")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "", Scope = "member", Target = "~M:DisCatSharp.ApplicationCommands.ApplicationCommandsExtension.CheckRegistrationStartup(System.Boolean)")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "", Scope = "member", Target = "~M:DisCatSharp.ApplicationCommands.ApplicationCommandsExtension.RegisterCommands(System.Collections.Generic.IEnumerable{DisCatSharp.ApplicationCommands.ApplicationCommandsModuleConfiguration},System.Nullable{System.UInt64})~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "", Scope = "member", Target = "~M:DisCatSharp.ApplicationCommands.ApplicationCommandsExtension.UpdateAsync~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Performance", "CA1842:Do not use 'WhenAll' with a single task", Justification = "", Scope = "member", Target = "~M:DisCatSharp.ApplicationCommands.DefaultHelpModule.DefaultHelpAutoCompleteLevelOneProvider.Provider(DisCatSharp.ApplicationCommands.AutocompleteContext)~System.Threading.Tasks.Task{System.Collections.Generic.IEnumerable{DisCatSharp.Entities.DiscordApplicationCommandAutocompleteChoice}}")]
[assembly: SuppressMessage("Performance", "CA1842:Do not use 'WhenAll' with a single task", Justification = "", Scope = "member", Target = "~M:DisCatSharp.ApplicationCommands.DefaultHelpModule.DefaultHelpAutoCompleteLevelTwoProvider.Provider(DisCatSharp.ApplicationCommands.AutocompleteContext)~System.Threading.Tasks.Task{System.Collections.Generic.IEnumerable{DisCatSharp.Entities.DiscordApplicationCommandAutocompleteChoice}}")]
[assembly: SuppressMessage("Performance", "CA1842:Do not use 'WhenAll' with a single task", Justification = "", Scope = "member", Target = "~M:DisCatSharp.ApplicationCommands.DefaultHelpModule.DefaultHelpAutoCompleteProvider.Provider(DisCatSharp.ApplicationCommands.AutocompleteContext)~System.Threading.Tasks.Task{System.Collections.Generic.IEnumerable{DisCatSharp.Entities.DiscordApplicationCommandAutocompleteChoice}}")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "", Scope = "member", Target = "~M:DisCatSharp.ApplicationCommands.ApplicationCommandEqualityChecks.IsEqualTo(DisCatSharp.Entities.DiscordApplicationCommand,DisCatSharp.Entities.DiscordApplicationCommand)~System.Boolean")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "", Scope = "member", Target = "~M:DisCatSharp.ApplicationCommands.RegistrationWorker.BuildGlobalOverwriteList(System.Collections.Generic.List{DisCatSharp.Entities.DiscordApplicationCommand})~System.ValueTuple{System.Collections.Generic.Dictionary{System.UInt64,DisCatSharp.Entities.DiscordApplicationCommand},System.Collections.Generic.List{DisCatSharp.Entities.DiscordApplicationCommand}}")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "", Scope = "member", Target = "~M:DisCatSharp.ApplicationCommands.RegistrationWorker.BuildGuildOverwriteList(System.UInt64,System.Collections.Generic.List{DisCatSharp.Entities.DiscordApplicationCommand})~System.ValueTuple{System.Collections.Generic.Dictionary{System.UInt64,DisCatSharp.Entities.DiscordApplicationCommand},System.Collections.Generic.List{DisCatSharp.Entities.DiscordApplicationCommand}}")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "", Scope = "member", Target = "~M:DisCatSharp.ApplicationCommands.RegistrationWorker.RegisterGlobalCommandsAsync(System.Collections.Generic.List{DisCatSharp.Entities.DiscordApplicationCommand})~System.Threading.Tasks.Task{System.Collections.Generic.List{DisCatSharp.Entities.DiscordApplicationCommand}}")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "", Scope = "member", Target = "~M:DisCatSharp.ApplicationCommands.RegistrationWorker.RegisterGuilldCommandsAsync(System.UInt64,System.Collections.Generic.List{DisCatSharp.Entities.DiscordApplicationCommand})~System.Threading.Tasks.Task{System.Collections.Generic.List{DisCatSharp.Entities.DiscordApplicationCommand}}")]
+[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:DisCatSharp.ApplicationCommands.DefaultHelpModule.DefaultHelpAsync(DisCatSharp.ApplicationCommands.InteractionContext,System.String,System.String,System.String)~System.Threading.Tasks.Task")]
+[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:DisCatSharp.ApplicationCommands.ApplicationCommandsExtension.RunPreexecutionChecksAsync(System.Reflection.MethodInfo,DisCatSharp.ApplicationCommands.BaseContext)~System.Threading.Tasks.Task")]
+[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~P:DisCatSharp.ApplicationCommands.ApplicationCommandsExtension.GlobalCommands")]
+[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~P:DisCatSharp.ApplicationCommands.ApplicationCommandsExtension.GuildCommands")]
+[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~P:DisCatSharp.ApplicationCommands.ApplicationCommandsExtension.RegisteredCommands")]
diff --git a/DisCatSharp.ApplicationCommands/global.json b/DisCatSharp.ApplicationCommands/global.json
new file mode 100644
index 000000000..90a51a911
--- /dev/null
+++ b/DisCatSharp.ApplicationCommands/global.json
@@ -0,0 +1,6 @@
+{
+ "sdk": {
+ "version": "6.0.202",
+ "rollForward": "major"
+ }
+}
diff --git a/DisCatSharp.CommandsNext/DisCatSharp.CommandsNext.csproj b/DisCatSharp.CommandsNext/DisCatSharp.CommandsNext.csproj
index 712863f21..46aceab9a 100644
--- a/DisCatSharp.CommandsNext/DisCatSharp.CommandsNext.csproj
+++ b/DisCatSharp.CommandsNext/DisCatSharp.CommandsNext.csproj
@@ -1,40 +1,31 @@
DisCatSharp.CommandsNext
DisCatSharp.CommandsNext
- DisCatSharp.CommandsNext
- CommandNext extension for DisCatSharp.
- discord, discord-api, bots, discord-bots, chat, dcs, discatsharp, csharp, dotnet, vb-net, fsharp, commands, commandsnext
-
- LICENSE.md
+ DisCatSharp.CommandsNext
+ CommandNext extension for DisCatSharp.
+ discord, discord-api, bots, discord-bots, chat, dcs, discatsharp, csharp, dotnet, vb-net, fsharp, commands, commandsnext
-
+
-
-
- True
-
-
-
-
diff --git a/DisCatSharp.CommandsNext/GlobalSuppressions.cs b/DisCatSharp.CommandsNext/GlobalSuppressions.cs
index cdd81b5f2..8072fefed 100644
--- a/DisCatSharp.CommandsNext/GlobalSuppressions.cs
+++ b/DisCatSharp.CommandsNext/GlobalSuppressions.cs
@@ -1,25 +1,27 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "", Scope = "member", Target = "~M:DisCatSharp.CommandsNext.Attributes.RequireUserPermissionsAttribute.ExecuteCheckAsync(DisCatSharp.CommandsNext.CommandContext,System.Boolean)~System.Threading.Tasks.Task{System.Boolean}")]
+[assembly: SuppressMessage("Performance", "CA1826:Do not use Enumerable methods on indexable collections", Justification = "", Scope = "member", Target = "~M:DisCatSharp.CommandsNext.Converters.DiscordMemberConverter.DisCatSharp#CommandsNext#Converters#IArgumentConverter#ConvertAsync(System.String,DisCatSharp.CommandsNext.CommandContext)~System.Threading.Tasks.Task{DisCatSharp.Entities.Optional{DisCatSharp.Entities.DiscordMember}}")]
+[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:DisCatSharp.CommandsNext.CommandsNextExtension.DefaultHelpModule.DefaultHelpAsync(DisCatSharp.CommandsNext.CommandContext,System.String[])~System.Threading.Tasks.Task")]
diff --git a/DisCatSharp.CommandsNext/global.json b/DisCatSharp.CommandsNext/global.json
new file mode 100644
index 000000000..90a51a911
--- /dev/null
+++ b/DisCatSharp.CommandsNext/global.json
@@ -0,0 +1,6 @@
+{
+ "sdk": {
+ "version": "6.0.202",
+ "rollForward": "major"
+ }
+}
diff --git a/DisCatSharp.Common/DisCatSharp.Common.csproj b/DisCatSharp.Common/DisCatSharp.Common.csproj
index ebb0acaef..097cfbfec 100644
--- a/DisCatSharp.Common/DisCatSharp.Common.csproj
+++ b/DisCatSharp.Common/DisCatSharp.Common.csproj
@@ -1,46 +1,38 @@
DisCatSharp.Common
DisCatSharp.Common
True
True
True
Portable
DisCatSharp.Common
Assortment of various common types and utilities for DisCatSharp's projects.
common utilities dotnet dotnet-core dotnetfx netfx netcore csharp
- LICENSE.md
enable
False
-
+
-
-
- True
-
-
-
-
diff --git a/DisCatSharp.Common/global.json b/DisCatSharp.Common/global.json
new file mode 100644
index 000000000..90a51a911
--- /dev/null
+++ b/DisCatSharp.Common/global.json
@@ -0,0 +1,6 @@
+{
+ "sdk": {
+ "version": "6.0.202",
+ "rollForward": "major"
+ }
+}
diff --git a/DisCatSharp.Configuration.Tests/DisCatSharp.Configuration.Tests.csproj b/DisCatSharp.Configuration.Tests/DisCatSharp.Configuration.Tests.csproj
index b62452f69..3345d75a3 100644
--- a/DisCatSharp.Configuration.Tests/DisCatSharp.Configuration.Tests.csproj
+++ b/DisCatSharp.Configuration.Tests/DisCatSharp.Configuration.Tests.csproj
@@ -1,53 +1,53 @@
net6.0
enable
1591;NU5128;DV2001
false
false
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
runtime; build; native; contentfiles; analyzers; buildtransitive
all
Always
Always
Always
Always
Always
Always
diff --git a/DisCatSharp.Configuration.Tests/global.json b/DisCatSharp.Configuration.Tests/global.json
new file mode 100644
index 000000000..90a51a911
--- /dev/null
+++ b/DisCatSharp.Configuration.Tests/global.json
@@ -0,0 +1,6 @@
+{
+ "sdk": {
+ "version": "6.0.202",
+ "rollForward": "major"
+ }
+}
diff --git a/DisCatSharp.Configuration/DisCatSharp.Configuration.csproj b/DisCatSharp.Configuration/DisCatSharp.Configuration.csproj
index 38117056c..f4618a4d9 100644
--- a/DisCatSharp.Configuration/DisCatSharp.Configuration.csproj
+++ b/DisCatSharp.Configuration/DisCatSharp.Configuration.csproj
@@ -1,43 +1,34 @@
net6.0
enable
DisCatSharp.Configuration
DisCatSharp.Configuration
True
DisCatSharp.Configuration
Configuration for DisCatSharp.
discord, discord-api, bots, discord-bots, net-sdk, dcs, discatsharp, csharp, dotnet, vb-net, fsharp
-
- LICENSE.md
-
+
-
-
- True
-
-
-
-
diff --git a/DisCatSharp.Configuration/global.json b/DisCatSharp.Configuration/global.json
new file mode 100644
index 000000000..90a51a911
--- /dev/null
+++ b/DisCatSharp.Configuration/global.json
@@ -0,0 +1,6 @@
+{
+ "sdk": {
+ "version": "6.0.202",
+ "rollForward": "major"
+ }
+}
diff --git a/DisCatSharp.Docs/articles/application_commands/translations/using.md b/DisCatSharp.Docs/articles/application_commands/translations/using.md
index a1ed12190..78f4577bb 100644
--- a/DisCatSharp.Docs/articles/application_commands/translations/using.md
+++ b/DisCatSharp.Docs/articles/application_commands/translations/using.md
@@ -1,201 +1,197 @@
---
uid: application_commands_translations_using
title: Using Translations
---
# Using Translations
## Why Do We Outsource Translation In External JSON Files
Pretty simple: It's common to have translations external stored.
This makes it easier to modify them, while keeping the code itself clean.
## Adding Translations
Translations are added the same way like permissions are added to Application Commands:
```cs
const string TRANSLATION_PATH = "translations/";
-Client.GetApplicationCommands().RegisterGuildCommands(1215484634894646844, perms => {
- perms.AddRole(915747497668915230, true);
-}, translations =>
+Client.GetApplicationCommands().RegisterGuildCommands(1215484634894646844, translations =>
{
string json = File.ReadAllText(TRANSLATION_PATH + "my_command.json");
translations.AddTranslation(json);
});
-Client.GetApplicationCommands().RegisterGuildCommands(1215484634894646844, perms => {
- perms.AddRole(915747497668915230, true);
-}, translations =>
+Client.GetApplicationCommands().RegisterGuildCommands(1215484634894646844, translations =>
{
string json = File.ReadAllText(TRANSLATION_PATH + "my_simple_command.json");
translations.AddTranslation(json);
});
```
> [!WARNING]
> If you add a translation to a class, you have to supply translations for every command in this class. Otherwise it will fail.
## Creating The Translation JSON
We split the translation in two categories.
One for slash command groups and one for normal slash commands and context menu commands.
The `name` key in the JSON will be mapped to the command / option / choice names internally.
### Translation For Slash Command Groups
Imagine, your class look like the following example:
```cs
public class MyCommand : ApplicationCommandsModule
{
[SlashCommandGroup("my_command", "This is description of the command group.")]
public class MyCommandGroup : ApplicationCommandsModule
{
[SlashCommand("first", "This is description of the command.")]
public async Task MySlashCommand(InteractionContext context)
{
await context.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder()
{
Content = "This is first subcommand."
});
}
[SlashCommand("second", "This is description of the command.")]
public async Task MySecondCommand(InteractionContext context, [Option("value", "Some string value.")] string value)
{
await context.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder()
{
Content = "This is second subcommand. The value was " + value
});
}
}
}
```
The translation json is a object of [Command Group Objects](xref:application_commands_translations_reference#command-group-object)
A correct translation json for english and german would look like that:
```json
[
{
"name": "my_command",
"name_translations": {
"en-US": "my_command",
"de": "mein_befehl"
},
"description_translations": {
"en-US": "This is description of the command group.",
"de": "Das ist die description der Befehl Gruppe."
},
"commands": [
{
"name": "first",
"type": 1, // Type 1 for slash command
"name_translations": {
"en-US": "first",
"de": "erste"
},
"description_translations": {
"en-US": "This is description of the command.",
"de": "Das ist die Beschreibung des Befehls."
}
},
{
"name": "second",
"type": 1, // Type 1 for slash command
"name_translations": {
"en-US": "second",
"de": "zweite"
},
"description_translations": {
"en-US": "This is description of the command.",
"de": "Das ist die Beschreibung des Befehls."
},
"options": [
{
"name": "value",
"name_translations": {
"en-US": "value",
"de": "wert"
},
"description_translations": {
"en-US": "Some string value.",
"de": "Ein string Wert."
}
}
]
}
]
}
]
```
### Translation For Slash Commands & Context Menu Commands
Now imagine, that your class look like this example:
```cs
public class MySimpleCommands : ApplicationCommandsModule
{
[SlashCommand("my_command", "This is description of the command.")]
public async Task MySlashCommand(InteractionContext context)
{
}
[ContextMenu(ApplicationCommandType.User, "My Command")]
public async Task MyContextMenuCommand(ContextMenuContext context)
{
}
}
```
The slash command is a simple [Command Object](xref:application_commands_translations_reference#command-object).
Same goes for the context menu command, but note that it can't have a description.
Slash Commands has the [type](xref:application_commands_translations_reference#application-command-type) `1` and context menu commands the [type](xref:application_commands_translations_reference#application-command-type) `2` or `3`.
We use this to determine, where the translation belongs to.
A correct json for this example would look like that:
```json
[
{
"name":"my_command",
"type": 1, // Type 1 for slash command
"name_translations":{
"en-US":"my_command",
"de":"mein_befehl"
},
"description_translations":{
"en-US":"This is description of the command.",
"de":"Das ist die Beschreibung des Befehls."
}
},
{
"name":"My Command",
"type": 2, // Type 2 for user context menu command
"name_translations":{
"en-US":"My Command",
"de":"Mein Befehl"
}
}
]
```
## Available Locales
Discord has a limited choice of locales, in particular, the ones you can select in the client.
To see the available locales, visit [this](xref:application_commands_translations_reference#valid-locales) page.
## Can We Get The User And Guild Locale?
Yes, you can!
Discord sends the user on all [interaction types](xref:DisCatSharp.InteractionType), except `Ping`.
We introduced two new properties `Locale` and `GuildLocale` on [InteractionContext](xref:DisCatSharp.ApplicationCommands.InteractionContext), [ContextMenuContext](xref:DisCatSharp.ApplicationCommands.ContextMenuContext), [AutoCompleteContext](xref:DisCatSharp.ApplicationCommands.AutocompleteContext) and [DiscordInteraction](xref:DisCatSharp.Entities.DiscordInteraction).
`Locale` is the locale of the user and always represented.
`GuildLocale` is only represented, when the interaction is **not** in a DM.
Furthermore we cache known user locales on the [DiscordUser](xref:DisCatSharp.Entities.DiscordUser#DisCatSharp_Entities_DiscordUser_Locale) object.
diff --git a/DisCatSharp.Docs/dcs/plugins/DisCatSharp.DocFx.CustomMemberIndexer.deps.json b/DisCatSharp.Docs/dcs/plugins/DisCatSharp.DocFx.CustomMemberIndexer.deps.json
index beef69613..9ea96e586 100644
--- a/DisCatSharp.Docs/dcs/plugins/DisCatSharp.DocFx.CustomMemberIndexer.deps.json
+++ b/DisCatSharp.Docs/dcs/plugins/DisCatSharp.DocFx.CustomMemberIndexer.deps.json
@@ -1,1068 +1,1068 @@
{
"runtimeTarget": {
"name": ".NETStandard,Version=v2.0/",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETStandard,Version=v2.0": {},
".NETStandard,Version=v2.0/": {
"DisCatSharp.DocFx.CustomMemberIndexer/1.0.0": {
"dependencies": {
"CsQuery": "1.3.5-beta5",
"Microsoft.Composition": "1.0.31",
- "Microsoft.DocAsCode.Common": "2.58.9",
- "Microsoft.DocAsCode.Plugins": "2.58.9",
+ "Microsoft.DocAsCode.Common": "2.59.2",
+ "Microsoft.DocAsCode.Plugins": "2.59.2",
"NETStandard.Library": "2.0.3"
},
"runtime": {
"DisCatSharp.DocFx.CustomMemberIndexer.dll": {}
}
},
"CsQuery/1.3.5-beta5": {
"runtime": {
"lib/net40/CsQuery.dll": {
"assemblyVersion": "1.3.5.124",
"fileVersion": "1.3.5.124"
}
}
},
"Microsoft.Composition/1.0.31": {
"dependencies": {
"System.Composition": "1.0.31"
}
},
"Microsoft.CSharp/4.0.1": {
"dependencies": {
"System.Collections": "4.3.0",
"System.Diagnostics.Debug": "4.3.0",
"System.Dynamic.Runtime": "4.0.11",
"System.Globalization": "4.3.0",
"System.Linq": "4.3.0",
"System.Linq.Expressions": "4.3.0",
"System.ObjectModel": "4.3.0",
"System.Reflection": "4.3.0",
"System.Reflection.Extensions": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
"System.Reflection.TypeExtensions": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0",
"System.Runtime.InteropServices": "4.1.0",
"System.Threading": "4.3.0"
},
"runtime": {
"lib/netstandard1.3/Microsoft.CSharp.dll": {
"assemblyVersion": "4.0.1.0",
"fileVersion": "1.0.24212.1"
}
}
},
- "Microsoft.DocAsCode.Common/2.58.9": {
+ "Microsoft.DocAsCode.Common/2.59.2": {
"dependencies": {
- "Microsoft.DocAsCode.Plugins": "2.58.9",
- "Microsoft.DocAsCode.YamlSerialization": "2.58.9"
+ "Microsoft.DocAsCode.Plugins": "2.59.2",
+ "Microsoft.DocAsCode.YamlSerialization": "2.59.2"
},
"runtime": {
"lib/netstandard2.0/Microsoft.DocAsCode.Common.dll": {
- "assemblyVersion": "2.58.9.0",
- "fileVersion": "2.58.9.12548"
+ "assemblyVersion": "2.59.2.0",
+ "fileVersion": "2.59.2.13435"
}
}
},
- "Microsoft.DocAsCode.Plugins/2.58.9": {
+ "Microsoft.DocAsCode.Plugins/2.59.2": {
"dependencies": {
"Newtonsoft.Json": "9.0.1",
"System.Collections.Immutable": "5.0.0"
},
"runtime": {
"lib/netstandard2.0/Microsoft.DocAsCode.Plugins.dll": {
- "assemblyVersion": "2.58.9.0",
- "fileVersion": "2.58.9.12548"
+ "assemblyVersion": "2.59.2.0",
+ "fileVersion": "2.59.2.13435"
}
}
},
- "Microsoft.DocAsCode.YamlSerialization/2.58.9": {
+ "Microsoft.DocAsCode.YamlSerialization/2.59.2": {
"dependencies": {
"System.Reflection.Emit.Lightweight": "4.3.0",
"YamlDotNet.Signed": "5.1.0"
},
"runtime": {
"lib/netstandard2.0/Microsoft.DocAsCode.YamlSerialization.dll": {
- "assemblyVersion": "2.58.9.0",
- "fileVersion": "2.58.9.12548"
+ "assemblyVersion": "2.59.2.0",
+ "fileVersion": "2.59.2.13435"
}
}
},
"Microsoft.NETCore.Platforms/1.1.0": {},
"Microsoft.NETCore.Targets/1.1.0": {},
"NETStandard.Library/2.0.3": {
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0"
}
},
"Newtonsoft.Json/9.0.1": {
"dependencies": {
"Microsoft.CSharp": "4.0.1",
"System.Collections": "4.3.0",
"System.Diagnostics.Debug": "4.3.0",
"System.Dynamic.Runtime": "4.0.11",
"System.Globalization": "4.3.0",
"System.IO": "4.3.0",
"System.Linq": "4.3.0",
"System.Linq.Expressions": "4.3.0",
"System.ObjectModel": "4.3.0",
"System.Reflection": "4.3.0",
"System.Reflection.Extensions": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0",
"System.Runtime.Serialization.Primitives": "4.1.1",
"System.Text.Encoding": "4.3.0",
"System.Text.Encoding.Extensions": "4.0.11",
"System.Text.RegularExpressions": "4.1.0",
"System.Threading": "4.3.0",
"System.Threading.Tasks": "4.3.0",
"System.Xml.ReaderWriter": "4.0.11",
"System.Xml.XDocument": "4.0.11"
},
"runtime": {
"lib/netstandard1.0/Newtonsoft.Json.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.1.19813"
}
}
},
"System.Buffers/4.5.1": {
"runtime": {
"lib/netstandard2.0/System.Buffers.dll": {
"assemblyVersion": "4.0.3.0",
"fileVersion": "4.6.28619.1"
}
}
},
"System.Collections/4.3.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0"
}
},
"System.Collections.Immutable/5.0.0": {
"dependencies": {
"System.Memory": "4.5.4"
},
"runtime": {
"lib/netstandard2.0/System.Collections.Immutable.dll": {
"assemblyVersion": "5.0.0.0",
"fileVersion": "5.0.20.51904"
}
}
},
"System.Composition/1.0.31": {
"dependencies": {
"System.Composition.AttributedModel": "1.0.31",
"System.Composition.Convention": "1.0.31",
"System.Composition.Hosting": "1.0.31",
"System.Composition.Runtime": "1.0.31",
"System.Composition.TypedParts": "1.0.31"
}
},
"System.Composition.AttributedModel/1.0.31": {
"dependencies": {
"System.Reflection": "4.3.0",
"System.Runtime": "4.3.0"
},
"runtime": {
"lib/netstandard1.0/System.Composition.AttributedModel.dll": {
"assemblyVersion": "1.0.31.0",
"fileVersion": "4.6.24705.1"
}
}
},
"System.Composition.Convention/1.0.31": {
"dependencies": {
"System.Collections": "4.3.0",
"System.Composition.AttributedModel": "1.0.31",
"System.Diagnostics.Debug": "4.3.0",
"System.Diagnostics.Tools": "4.3.0",
"System.Globalization": "4.3.0",
"System.Linq": "4.3.0",
"System.Linq.Expressions": "4.3.0",
"System.Reflection": "4.3.0",
"System.Reflection.Extensions": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Threading": "4.3.0"
},
"runtime": {
"lib/netstandard1.0/System.Composition.Convention.dll": {
"assemblyVersion": "1.0.31.0",
"fileVersion": "4.6.24705.1"
}
}
},
"System.Composition.Hosting/1.0.31": {
"dependencies": {
"System.Collections": "4.3.0",
"System.Composition.Runtime": "1.0.31",
"System.Diagnostics.Debug": "4.3.0",
"System.Diagnostics.Tools": "4.3.0",
"System.Globalization": "4.3.0",
"System.Linq": "4.3.0",
"System.Linq.Expressions": "4.3.0",
"System.ObjectModel": "4.3.0",
"System.Reflection": "4.3.0",
"System.Reflection.Extensions": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Threading": "4.3.0"
},
"runtime": {
"lib/netstandard1.0/System.Composition.Hosting.dll": {
"assemblyVersion": "1.0.31.0",
"fileVersion": "4.6.24705.1"
}
}
},
"System.Composition.Runtime/1.0.31": {
"dependencies": {
"System.Collections": "4.3.0",
"System.Diagnostics.Debug": "4.3.0",
"System.Diagnostics.Tools": "4.3.0",
"System.Globalization": "4.3.0",
"System.Linq": "4.3.0",
"System.Reflection": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0"
},
"runtime": {
"lib/netstandard1.0/System.Composition.Runtime.dll": {
"assemblyVersion": "1.0.31.0",
"fileVersion": "4.6.24705.1"
}
}
},
"System.Composition.TypedParts/1.0.31": {
"dependencies": {
"System.Collections": "4.3.0",
"System.Composition.AttributedModel": "1.0.31",
"System.Composition.Hosting": "1.0.31",
"System.Composition.Runtime": "1.0.31",
"System.Diagnostics.Debug": "4.3.0",
"System.Diagnostics.Tools": "4.3.0",
"System.Globalization": "4.3.0",
"System.Linq": "4.3.0",
"System.Linq.Expressions": "4.3.0",
"System.Reflection": "4.3.0",
"System.Reflection.Extensions": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0"
},
"runtime": {
"lib/netstandard1.0/System.Composition.TypedParts.dll": {
"assemblyVersion": "1.0.31.0",
"fileVersion": "4.6.24705.1"
}
}
},
"System.Diagnostics.Debug/4.3.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0"
}
},
"System.Diagnostics.Tools/4.3.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0"
}
},
"System.Dynamic.Runtime/4.0.11": {
"dependencies": {
"System.Collections": "4.3.0",
"System.Diagnostics.Debug": "4.3.0",
"System.Globalization": "4.3.0",
"System.Linq": "4.3.0",
"System.Linq.Expressions": "4.3.0",
"System.ObjectModel": "4.3.0",
"System.Reflection": "4.3.0",
"System.Reflection.Emit": "4.3.0",
"System.Reflection.Emit.ILGeneration": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
"System.Reflection.TypeExtensions": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0",
"System.Threading": "4.3.0"
},
"runtime": {
"lib/netstandard1.3/System.Dynamic.Runtime.dll": {
"assemblyVersion": "4.0.11.0",
"fileVersion": "1.0.24212.1"
}
}
},
"System.Globalization/4.3.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0"
}
},
"System.IO/4.3.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"System.Text.Encoding": "4.3.0",
"System.Threading.Tasks": "4.3.0"
}
},
"System.IO.FileSystem/4.0.1": {
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.IO": "4.3.0",
"System.IO.FileSystem.Primitives": "4.0.1",
"System.Runtime": "4.3.0",
"System.Runtime.Handles": "4.0.1",
"System.Text.Encoding": "4.3.0",
"System.Threading.Tasks": "4.3.0"
}
},
"System.IO.FileSystem.Primitives/4.0.1": {
"dependencies": {
"System.Runtime": "4.3.0"
},
"runtime": {
"lib/netstandard1.3/System.IO.FileSystem.Primitives.dll": {
"assemblyVersion": "4.0.1.0",
"fileVersion": "1.0.24212.1"
}
}
},
"System.Linq/4.3.0": {
"dependencies": {
"System.Collections": "4.3.0",
"System.Diagnostics.Debug": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0"
},
"runtime": {
"lib/netstandard1.6/System.Linq.dll": {
"assemblyVersion": "4.1.1.0",
"fileVersion": "4.6.24705.1"
}
}
},
"System.Linq.Expressions/4.3.0": {
"dependencies": {
"System.Collections": "4.3.0",
"System.Diagnostics.Debug": "4.3.0",
"System.Globalization": "4.3.0",
"System.IO": "4.3.0",
"System.Linq": "4.3.0",
"System.ObjectModel": "4.3.0",
"System.Reflection": "4.3.0",
"System.Reflection.Emit": "4.3.0",
"System.Reflection.Emit.ILGeneration": "4.3.0",
"System.Reflection.Emit.Lightweight": "4.3.0",
"System.Reflection.Extensions": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
"System.Reflection.TypeExtensions": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0",
"System.Threading": "4.3.0"
},
"runtime": {
"lib/netstandard1.6/System.Linq.Expressions.dll": {
"assemblyVersion": "4.1.1.0",
"fileVersion": "4.6.24705.1"
}
}
},
"System.Memory/4.5.4": {
"dependencies": {
"System.Buffers": "4.5.1",
"System.Numerics.Vectors": "4.4.0",
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
},
"runtime": {
"lib/netstandard2.0/System.Memory.dll": {
"assemblyVersion": "4.0.1.1",
"fileVersion": "4.6.28619.1"
}
}
},
"System.Numerics.Vectors/4.4.0": {
"runtime": {
"lib/netstandard2.0/System.Numerics.Vectors.dll": {
"assemblyVersion": "4.1.3.0",
"fileVersion": "4.6.25519.3"
}
}
},
"System.ObjectModel/4.3.0": {
"dependencies": {
"System.Collections": "4.3.0",
"System.Diagnostics.Debug": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Threading": "4.3.0"
},
"runtime": {
"lib/netstandard1.3/System.ObjectModel.dll": {
"assemblyVersion": "4.0.13.0",
"fileVersion": "4.6.24705.1"
}
}
},
"System.Reflection/4.3.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.IO": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
"System.Runtime": "4.3.0"
}
},
"System.Reflection.Emit/4.3.0": {
"dependencies": {
"System.IO": "4.3.0",
"System.Reflection": "4.3.0",
"System.Reflection.Emit.ILGeneration": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
"System.Runtime": "4.3.0"
},
"runtime": {
"lib/netstandard1.3/System.Reflection.Emit.dll": {
"assemblyVersion": "4.0.2.0",
"fileVersion": "4.6.24705.1"
}
}
},
"System.Reflection.Emit.ILGeneration/4.3.0": {
"dependencies": {
"System.Reflection": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
"System.Runtime": "4.3.0"
},
"runtime": {
"lib/netstandard1.3/System.Reflection.Emit.ILGeneration.dll": {
"assemblyVersion": "4.0.2.0",
"fileVersion": "4.6.24705.1"
}
}
},
"System.Reflection.Emit.Lightweight/4.3.0": {
"dependencies": {
"System.Reflection": "4.3.0",
"System.Reflection.Emit.ILGeneration": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
"System.Runtime": "4.3.0"
},
"runtime": {
"lib/netstandard1.3/System.Reflection.Emit.Lightweight.dll": {
"assemblyVersion": "4.0.2.0",
"fileVersion": "4.6.24705.1"
}
}
},
"System.Reflection.Extensions/4.3.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Reflection": "4.3.0",
"System.Runtime": "4.3.0"
}
},
"System.Reflection.Primitives/4.3.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0"
}
},
"System.Reflection.TypeExtensions/4.3.0": {
"dependencies": {
"System.Reflection": "4.3.0",
"System.Runtime": "4.3.0"
},
"runtime": {
"lib/netstandard1.5/System.Reflection.TypeExtensions.dll": {
"assemblyVersion": "4.1.1.0",
"fileVersion": "4.6.24705.1"
}
}
},
"System.Resources.ResourceManager/4.3.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Globalization": "4.3.0",
"System.Reflection": "4.3.0",
"System.Runtime": "4.3.0"
}
},
"System.Runtime/4.3.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0"
}
},
"System.Runtime.CompilerServices.Unsafe/4.5.3": {
"runtime": {
"lib/netstandard2.0/System.Runtime.CompilerServices.Unsafe.dll": {
"assemblyVersion": "4.0.4.1",
"fileVersion": "4.6.28619.1"
}
}
},
"System.Runtime.Extensions/4.3.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0"
}
},
"System.Runtime.Handles/4.0.1": {
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0"
}
},
"System.Runtime.InteropServices/4.1.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Reflection": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Handles": "4.0.1"
}
},
"System.Runtime.Serialization.Primitives/4.1.1": {
"dependencies": {
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0"
},
"runtime": {
"lib/netstandard1.3/System.Runtime.Serialization.Primitives.dll": {
"assemblyVersion": "4.1.1.0",
"fileVersion": "1.0.24212.1"
}
}
},
"System.Text.Encoding/4.3.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0"
}
},
"System.Text.Encoding.Extensions/4.0.11": {
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"System.Text.Encoding": "4.3.0"
}
},
"System.Text.RegularExpressions/4.1.0": {
"dependencies": {
"System.Collections": "4.3.0",
"System.Globalization": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0",
"System.Threading": "4.3.0"
},
"runtime": {
"lib/netstandard1.6/System.Text.RegularExpressions.dll": {
"assemblyVersion": "4.1.0.0",
"fileVersion": "1.0.24212.1"
}
}
},
"System.Threading/4.3.0": {
"dependencies": {
"System.Runtime": "4.3.0",
"System.Threading.Tasks": "4.3.0"
},
"runtime": {
"lib/netstandard1.3/System.Threading.dll": {
"assemblyVersion": "4.0.12.0",
"fileVersion": "4.6.24705.1"
}
}
},
"System.Threading.Tasks/4.3.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0"
}
},
"System.Threading.Tasks.Extensions/4.0.0": {
"dependencies": {
"System.Collections": "4.3.0",
"System.Runtime": "4.3.0",
"System.Threading.Tasks": "4.3.0"
},
"runtime": {
"lib/netstandard1.0/System.Threading.Tasks.Extensions.dll": {
"assemblyVersion": "4.0.0.0",
"fileVersion": "1.0.24212.1"
}
}
},
"System.Xml.ReaderWriter/4.0.11": {
"dependencies": {
"System.Collections": "4.3.0",
"System.Diagnostics.Debug": "4.3.0",
"System.Globalization": "4.3.0",
"System.IO": "4.3.0",
"System.IO.FileSystem": "4.0.1",
"System.IO.FileSystem.Primitives": "4.0.1",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0",
"System.Runtime.InteropServices": "4.1.0",
"System.Text.Encoding": "4.3.0",
"System.Text.Encoding.Extensions": "4.0.11",
"System.Text.RegularExpressions": "4.1.0",
"System.Threading.Tasks": "4.3.0",
"System.Threading.Tasks.Extensions": "4.0.0"
},
"runtime": {
"lib/netstandard1.3/System.Xml.ReaderWriter.dll": {
"assemblyVersion": "4.0.11.0",
"fileVersion": "1.0.24212.1"
}
}
},
"System.Xml.XDocument/4.0.11": {
"dependencies": {
"System.Collections": "4.3.0",
"System.Diagnostics.Debug": "4.3.0",
"System.Diagnostics.Tools": "4.3.0",
"System.Globalization": "4.3.0",
"System.IO": "4.3.0",
"System.Reflection": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0",
"System.Text.Encoding": "4.3.0",
"System.Threading": "4.3.0",
"System.Xml.ReaderWriter": "4.0.11"
},
"runtime": {
"lib/netstandard1.3/System.Xml.XDocument.dll": {
"assemblyVersion": "4.0.11.0",
"fileVersion": "1.0.24212.1"
}
}
},
"YamlDotNet.Signed/5.1.0": {
"runtime": {
"lib/netstandard1.3/YamlDotNet.dll": {
"assemblyVersion": "5.0.0.0",
"fileVersion": "5.1.0.0"
}
}
}
}
},
"libraries": {
"DisCatSharp.DocFx.CustomMemberIndexer/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"CsQuery/1.3.5-beta5": {
"type": "package",
"serviceable": true,
"sha512": "sha512-pMZvX7bHebyfGVJADRGxAC3m8dovsyhSk/lDZtvTboZiPedfwoF9kh2rJXFTjFcbC8VPhPdohwvUC4jSEuLwxg==",
"path": "csquery/1.3.5-beta5",
"hashPath": "csquery.1.3.5-beta5.nupkg.sha512"
},
"Microsoft.Composition/1.0.31": {
"type": "package",
"serviceable": true,
"sha512": "sha512-R8V1rw4ldOoKIg0QzDY033V8uKrNR0VRKuKVuA1wzuIVeBLwYGghF0y+WbmPI245xSnjRh5eMxxBaxDX9DYZmA==",
"path": "microsoft.composition/1.0.31",
"hashPath": "microsoft.composition.1.0.31.nupkg.sha512"
},
"Microsoft.CSharp/4.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-17h8b5mXa87XYKrrVqdgZ38JefSUqLChUQpXgSnpzsM0nDOhE40FTeNWOJ/YmySGV6tG6T8+hjz6vxbknHJr6A==",
"path": "microsoft.csharp/4.0.1",
"hashPath": "microsoft.csharp.4.0.1.nupkg.sha512"
},
- "Microsoft.DocAsCode.Common/2.58.9": {
+ "Microsoft.DocAsCode.Common/2.59.2": {
"type": "package",
"serviceable": true,
- "sha512": "sha512-1C/+wx0cQtsmi7d5QHIT77HV5MTKAHKUo3ywSbIWz0L3LkDktuEAScmZ0xrxwUFPcThKdsRw702Bai6QriAPUg==",
- "path": "microsoft.docascode.common/2.58.9",
- "hashPath": "microsoft.docascode.common.2.58.9.nupkg.sha512"
+ "sha512": "sha512-ezlS+U8v152D+Bn8iDMW0PWmaYEYw6PQhRSeCUDMlLYlA0KfbI6HBPMkwAdLRxWIh7nUREIzC6EoFfjuwqv22w==",
+ "path": "microsoft.docascode.common/2.59.2",
+ "hashPath": "microsoft.docascode.common.2.59.2.nupkg.sha512"
},
- "Microsoft.DocAsCode.Plugins/2.58.9": {
+ "Microsoft.DocAsCode.Plugins/2.59.2": {
"type": "package",
"serviceable": true,
- "sha512": "sha512-y+7Gj2sPURzM+tv7kB/nnnwWYnPiapVnK44Jmg8e6xVVEjTWstEwoc/iY1BBJZsOKNJGamR+9x5kpr6zApV48g==",
- "path": "microsoft.docascode.plugins/2.58.9",
- "hashPath": "microsoft.docascode.plugins.2.58.9.nupkg.sha512"
+ "sha512": "sha512-u+2kcqCPZEkaMt0vnNiAJx70FeU3I9HIe0r0fLbbqqKS/mmDz7ir3Km0HJqbSnqsf1dykpNi1l/+AFvSoWbWEw==",
+ "path": "microsoft.docascode.plugins/2.59.2",
+ "hashPath": "microsoft.docascode.plugins.2.59.2.nupkg.sha512"
},
- "Microsoft.DocAsCode.YamlSerialization/2.58.9": {
+ "Microsoft.DocAsCode.YamlSerialization/2.59.2": {
"type": "package",
"serviceable": true,
- "sha512": "sha512-dQqzGf6S5xjDACZ5TFcBcrK1+pe2aaYvaGTgqpfNhhCtcNQX+LwByX8lZfICkqMiHv+cDXGg6+PYuxLv7d4UpA==",
- "path": "microsoft.docascode.yamlserialization/2.58.9",
- "hashPath": "microsoft.docascode.yamlserialization.2.58.9.nupkg.sha512"
+ "sha512": "sha512-dCLxa63Ey+duzopUDlPCwOYQTuhMwP3uyd/40ZZqp9x/CShV9romA5+WZqY2SxVhw1iG9s70kc0DnBk6XBii4g==",
+ "path": "microsoft.docascode.yamlserialization/2.59.2",
+ "hashPath": "microsoft.docascode.yamlserialization.2.59.2.nupkg.sha512"
},
"Microsoft.NETCore.Platforms/1.1.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==",
"path": "microsoft.netcore.platforms/1.1.0",
"hashPath": "microsoft.netcore.platforms.1.1.0.nupkg.sha512"
},
"Microsoft.NETCore.Targets/1.1.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==",
"path": "microsoft.netcore.targets/1.1.0",
"hashPath": "microsoft.netcore.targets.1.1.0.nupkg.sha512"
},
"NETStandard.Library/2.0.3": {
"type": "package",
"serviceable": true,
"sha512": "sha512-st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==",
"path": "netstandard.library/2.0.3",
"hashPath": "netstandard.library.2.0.3.nupkg.sha512"
},
"Newtonsoft.Json/9.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-U82mHQSKaIk+lpSVCbWYKNavmNH1i5xrExDEquU1i6I5pV6UMOqRnJRSlKO3cMPfcpp0RgDY+8jUXHdQ4IfXvw==",
"path": "newtonsoft.json/9.0.1",
"hashPath": "newtonsoft.json.9.0.1.nupkg.sha512"
},
"System.Buffers/4.5.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==",
"path": "system.buffers/4.5.1",
"hashPath": "system.buffers.4.5.1.nupkg.sha512"
},
"System.Collections/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==",
"path": "system.collections/4.3.0",
"hashPath": "system.collections.4.3.0.nupkg.sha512"
},
"System.Collections.Immutable/5.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-FXkLXiK0sVVewcso0imKQoOxjoPAj42R8HtjjbSjVPAzwDfzoyoznWxgA3c38LDbN9SJux1xXoXYAhz98j7r2g==",
"path": "system.collections.immutable/5.0.0",
"hashPath": "system.collections.immutable.5.0.0.nupkg.sha512"
},
"System.Composition/1.0.31": {
"type": "package",
"serviceable": true,
"sha512": "sha512-I+D26qpYdoklyAVUdqwUBrEIckMNjAYnuPJy/h9dsQItpQwVREkDFs4b4tkBza0kT2Yk48Lcfsv2QQ9hWsh9Iw==",
"path": "system.composition/1.0.31",
"hashPath": "system.composition.1.0.31.nupkg.sha512"
},
"System.Composition.AttributedModel/1.0.31": {
"type": "package",
"serviceable": true,
"sha512": "sha512-NHWhkM3ZkspmA0XJEsKdtTt1ViDYuojgSND3yHhTzwxepiwqZf+BCWuvCbjUt4fe0NxxQhUDGJ5km6sLjo9qnQ==",
"path": "system.composition.attributedmodel/1.0.31",
"hashPath": "system.composition.attributedmodel.1.0.31.nupkg.sha512"
},
"System.Composition.Convention/1.0.31": {
"type": "package",
"serviceable": true,
"sha512": "sha512-GLjh2Ju71k6C0qxMMtl4efHa68NmWeIUYh4fkUI8xbjQrEBvFmRwMDFcylT8/PR9SQbeeL48IkFxU/+gd0nYEQ==",
"path": "system.composition.convention/1.0.31",
"hashPath": "system.composition.convention.1.0.31.nupkg.sha512"
},
"System.Composition.Hosting/1.0.31": {
"type": "package",
"serviceable": true,
"sha512": "sha512-fN1bT4RX4vUqjbgoyuJFVUizAl2mYF5VAb+bVIxIYZSSc0BdnX+yGAxcavxJuDDCQ1K+/mdpgyEFc8e9ikjvrg==",
"path": "system.composition.hosting/1.0.31",
"hashPath": "system.composition.hosting.1.0.31.nupkg.sha512"
},
"System.Composition.Runtime/1.0.31": {
"type": "package",
"serviceable": true,
"sha512": "sha512-0LEJN+2NVM89CE4SekDrrk5tHV5LeATltkp+9WNYrR+Huiyt0vaCqHbbHtVAjPyeLWIc8dOz/3kthRBj32wGQg==",
"path": "system.composition.runtime/1.0.31",
"hashPath": "system.composition.runtime.1.0.31.nupkg.sha512"
},
"System.Composition.TypedParts/1.0.31": {
"type": "package",
"serviceable": true,
"sha512": "sha512-0Zae/FtzeFgDBBuILeIbC/T9HMYbW4olAmi8XqqAGosSOWvXfiQLfARZEhiGd0LVXaYgXr0NhxiU1LldRP1fpQ==",
"path": "system.composition.typedparts/1.0.31",
"hashPath": "system.composition.typedparts.1.0.31.nupkg.sha512"
},
"System.Diagnostics.Debug/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==",
"path": "system.diagnostics.debug/4.3.0",
"hashPath": "system.diagnostics.debug.4.3.0.nupkg.sha512"
},
"System.Diagnostics.Tools/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-UUvkJfSYJMM6x527dJg2VyWPSRqIVB0Z7dbjHst1zmwTXz5CcXSYJFWRpuigfbO1Lf7yfZiIaEUesfnl/g5EyA==",
"path": "system.diagnostics.tools/4.3.0",
"hashPath": "system.diagnostics.tools.4.3.0.nupkg.sha512"
},
"System.Dynamic.Runtime/4.0.11": {
"type": "package",
"serviceable": true,
"sha512": "sha512-db34f6LHYM0U0JpE+sOmjar27BnqTVkbLJhgfwMpTdgTigG/Hna3m2MYVwnFzGGKnEJk2UXFuoVTr8WUbU91/A==",
"path": "system.dynamic.runtime/4.0.11",
"hashPath": "system.dynamic.runtime.4.0.11.nupkg.sha512"
},
"System.Globalization/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==",
"path": "system.globalization/4.3.0",
"hashPath": "system.globalization.4.3.0.nupkg.sha512"
},
"System.IO/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==",
"path": "system.io/4.3.0",
"hashPath": "system.io.4.3.0.nupkg.sha512"
},
"System.IO.FileSystem/4.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-IBErlVq5jOggAD69bg1t0pJcHaDbJbWNUZTPI96fkYWzwYbN6D9wRHMULLDd9dHsl7C2YsxXL31LMfPI1SWt8w==",
"path": "system.io.filesystem/4.0.1",
"hashPath": "system.io.filesystem.4.0.1.nupkg.sha512"
},
"System.IO.FileSystem.Primitives/4.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-kWkKD203JJKxJeE74p8aF8y4Qc9r9WQx4C0cHzHPrY3fv/L/IhWnyCHaFJ3H1QPOH6A93whlQ2vG5nHlBDvzWQ==",
"path": "system.io.filesystem.primitives/4.0.1",
"hashPath": "system.io.filesystem.primitives.4.0.1.nupkg.sha512"
},
"System.Linq/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==",
"path": "system.linq/4.3.0",
"hashPath": "system.linq.4.3.0.nupkg.sha512"
},
"System.Linq.Expressions/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-PGKkrd2khG4CnlyJwxwwaWWiSiWFNBGlgXvJpeO0xCXrZ89ODrQ6tjEWS/kOqZ8GwEOUATtKtzp1eRgmYNfclg==",
"path": "system.linq.expressions/4.3.0",
"hashPath": "system.linq.expressions.4.3.0.nupkg.sha512"
},
"System.Memory/4.5.4": {
"type": "package",
"serviceable": true,
"sha512": "sha512-1MbJTHS1lZ4bS4FmsJjnuGJOu88ZzTT2rLvrhW7Ygic+pC0NWA+3hgAen0HRdsocuQXCkUTdFn9yHJJhsijDXw==",
"path": "system.memory/4.5.4",
"hashPath": "system.memory.4.5.4.nupkg.sha512"
},
"System.Numerics.Vectors/4.4.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-UiLzLW+Lw6HLed1Hcg+8jSRttrbuXv7DANVj0DkL9g6EnnzbL75EB7EWsw5uRbhxd/4YdG8li5XizGWepmG3PQ==",
"path": "system.numerics.vectors/4.4.0",
"hashPath": "system.numerics.vectors.4.4.0.nupkg.sha512"
},
"System.ObjectModel/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-bdX+80eKv9bN6K4N+d77OankKHGn6CH711a6fcOpMQu2Fckp/Ft4L/kW9WznHpyR0NRAvJutzOMHNNlBGvxQzQ==",
"path": "system.objectmodel/4.3.0",
"hashPath": "system.objectmodel.4.3.0.nupkg.sha512"
},
"System.Reflection/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==",
"path": "system.reflection/4.3.0",
"hashPath": "system.reflection.4.3.0.nupkg.sha512"
},
"System.Reflection.Emit/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-228FG0jLcIwTVJyz8CLFKueVqQK36ANazUManGaJHkO0icjiIypKW7YLWLIWahyIkdh5M7mV2dJepllLyA1SKg==",
"path": "system.reflection.emit/4.3.0",
"hashPath": "system.reflection.emit.4.3.0.nupkg.sha512"
},
"System.Reflection.Emit.ILGeneration/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==",
"path": "system.reflection.emit.ilgeneration/4.3.0",
"hashPath": "system.reflection.emit.ilgeneration.4.3.0.nupkg.sha512"
},
"System.Reflection.Emit.Lightweight/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-oadVHGSMsTmZsAF864QYN1t1QzZjIcuKU3l2S9cZOwDdDueNTrqq1yRj7koFfIGEnKpt6NjpL3rOzRhs4ryOgA==",
"path": "system.reflection.emit.lightweight/4.3.0",
"hashPath": "system.reflection.emit.lightweight.4.3.0.nupkg.sha512"
},
"System.Reflection.Extensions/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==",
"path": "system.reflection.extensions/4.3.0",
"hashPath": "system.reflection.extensions.4.3.0.nupkg.sha512"
},
"System.Reflection.Primitives/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==",
"path": "system.reflection.primitives/4.3.0",
"hashPath": "system.reflection.primitives.4.3.0.nupkg.sha512"
},
"System.Reflection.TypeExtensions/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-7u6ulLcZbyxB5Gq0nMkQttcdBTx57ibzw+4IOXEfR+sXYQoHvjW5LTLyNr8O22UIMrqYbchJQJnos4eooYzYJA==",
"path": "system.reflection.typeextensions/4.3.0",
"hashPath": "system.reflection.typeextensions.4.3.0.nupkg.sha512"
},
"System.Resources.ResourceManager/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==",
"path": "system.resources.resourcemanager/4.3.0",
"hashPath": "system.resources.resourcemanager.4.3.0.nupkg.sha512"
},
"System.Runtime/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==",
"path": "system.runtime/4.3.0",
"hashPath": "system.runtime.4.3.0.nupkg.sha512"
},
"System.Runtime.CompilerServices.Unsafe/4.5.3": {
"type": "package",
"serviceable": true,
"sha512": "sha512-3TIsJhD1EiiT0w2CcDMN/iSSwnNnsrnbzeVHSKkaEgV85txMprmuO+Yq2AdSbeVGcg28pdNDTPK87tJhX7VFHw==",
"path": "system.runtime.compilerservices.unsafe/4.5.3",
"hashPath": "system.runtime.compilerservices.unsafe.4.5.3.nupkg.sha512"
},
"System.Runtime.Extensions/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==",
"path": "system.runtime.extensions/4.3.0",
"hashPath": "system.runtime.extensions.4.3.0.nupkg.sha512"
},
"System.Runtime.Handles/4.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-nCJvEKguXEvk2ymk1gqj625vVnlK3/xdGzx0vOKicQkoquaTBJTP13AIYkocSUwHCLNBwUbXTqTWGDxBTWpt7g==",
"path": "system.runtime.handles/4.0.1",
"hashPath": "system.runtime.handles.4.0.1.nupkg.sha512"
},
"System.Runtime.InteropServices/4.1.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-16eu3kjHS633yYdkjwShDHZLRNMKVi/s0bY8ODiqJ2RfMhDMAwxZaUaWVnZ2P71kr/or+X9o/xFWtNqz8ivieQ==",
"path": "system.runtime.interopservices/4.1.0",
"hashPath": "system.runtime.interopservices.4.1.0.nupkg.sha512"
},
"System.Runtime.Serialization.Primitives/4.1.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-HZ6Du5QrTG8MNJbf4e4qMO3JRAkIboGT5Fk804uZtg3Gq516S7hAqTm2UZKUHa7/6HUGdVy3AqMQKbns06G/cg==",
"path": "system.runtime.serialization.primitives/4.1.1",
"hashPath": "system.runtime.serialization.primitives.4.1.1.nupkg.sha512"
},
"System.Text.Encoding/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==",
"path": "system.text.encoding/4.3.0",
"hashPath": "system.text.encoding.4.3.0.nupkg.sha512"
},
"System.Text.Encoding.Extensions/4.0.11": {
"type": "package",
"serviceable": true,
"sha512": "sha512-jtbiTDtvfLYgXn8PTfWI+SiBs51rrmO4AAckx4KR6vFK9Wzf6tI8kcRdsYQNwriUeQ1+CtQbM1W4cMbLXnj/OQ==",
"path": "system.text.encoding.extensions/4.0.11",
"hashPath": "system.text.encoding.extensions.4.0.11.nupkg.sha512"
},
"System.Text.RegularExpressions/4.1.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-i88YCXpRTjCnoSQZtdlHkAOx4KNNik4hMy83n0+Ftlb7jvV6ZiZWMpnEZHhjBp6hQVh8gWd/iKNPzlPF7iyA2g==",
"path": "system.text.regularexpressions/4.1.0",
"hashPath": "system.text.regularexpressions.4.1.0.nupkg.sha512"
},
"System.Threading/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==",
"path": "system.threading/4.3.0",
"hashPath": "system.threading.4.3.0.nupkg.sha512"
},
"System.Threading.Tasks/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==",
"path": "system.threading.tasks/4.3.0",
"hashPath": "system.threading.tasks.4.3.0.nupkg.sha512"
},
"System.Threading.Tasks.Extensions/4.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-pH4FZDsZQ/WmgJtN4LWYmRdJAEeVkyriSwrv2Teoe5FOU0Yxlb6II6GL8dBPOfRmutHGATduj3ooMt7dJ2+i+w==",
"path": "system.threading.tasks.extensions/4.0.0",
"hashPath": "system.threading.tasks.extensions.4.0.0.nupkg.sha512"
},
"System.Xml.ReaderWriter/4.0.11": {
"type": "package",
"serviceable": true,
"sha512": "sha512-ZIiLPsf67YZ9zgr31vzrFaYQqxRPX9cVHjtPSnmx4eN6lbS/yEyYNr2vs1doGDEscF0tjCZFsk9yUg1sC9e8tg==",
"path": "system.xml.readerwriter/4.0.11",
"hashPath": "system.xml.readerwriter.4.0.11.nupkg.sha512"
},
"System.Xml.XDocument/4.0.11": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Mk2mKmPi0nWaoiYeotq1dgeNK1fqWh61+EK+w4Wu8SWuTYLzpUnschb59bJtGywaPq7SmTuPf44wrXRwbIrukg==",
"path": "system.xml.xdocument/4.0.11",
"hashPath": "system.xml.xdocument.4.0.11.nupkg.sha512"
},
"YamlDotNet.Signed/5.1.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-3iZF8/sDp/AxnA3YZ3gR+8irfd2FiBsqdR9fywzcpeBdaqUDRncqejAv3+jIwwCoHJz3No6JT7u+NzTeVE+5VA==",
"path": "yamldotnet.signed/5.1.0",
"hashPath": "yamldotnet.signed.5.1.0.nupkg.sha512"
}
}
}
\ No newline at end of file
diff --git a/DisCatSharp.Docs/dcs/plugins/DisCatSharp.DocFx.CustomMemberIndexer.dll b/DisCatSharp.Docs/dcs/plugins/DisCatSharp.DocFx.CustomMemberIndexer.dll
index 45e978f4d..255d514ec 100644
Binary files a/DisCatSharp.Docs/dcs/plugins/DisCatSharp.DocFx.CustomMemberIndexer.dll and b/DisCatSharp.Docs/dcs/plugins/DisCatSharp.DocFx.CustomMemberIndexer.dll differ
diff --git a/DisCatSharp.Docs/dcs/src/scripts/search-worker.js b/DisCatSharp.Docs/dcs/src/scripts/search-worker.js
index 60852af4c..d794bb2de 100644
--- a/DisCatSharp.Docs/dcs/src/scripts/search-worker.js
+++ b/DisCatSharp.Docs/dcs/src/scripts/search-worker.js
@@ -1,80 +1,80 @@
(function () {
importScripts('lunr.min.js');
var lunrIndex;
var stopWords = null;
var searchData = {};
lunr.tokenizer.separator = /[\s\-\.\(\)]+/;
var stopWordsRequest = new XMLHttpRequest();
- stopWordsRequest.open('GET', '../search-stopwords.json');
+ stopWordsRequest.open('GET', '/search-stopwords.json');
stopWordsRequest.onload = function () {
if (this.status != 200) {
return;
}
stopWords = JSON.parse(this.responseText);
buildIndex();
}
stopWordsRequest.send();
var searchDataRequest = new XMLHttpRequest();
- searchDataRequest.open('GET', '../index.json');
+ searchDataRequest.open('GET', '/index.json');
searchDataRequest.onload = function () {
if (this.status != 200) {
return;
}
searchData = JSON.parse(this.responseText);
buildIndex();
postMessage({ e: 'index-ready' });
}
searchDataRequest.send();
onmessage = function (oEvent) {
var q = oEvent.data.q;
var hits = lunrIndex.search(q);
var results = [];
hits.forEach(function (hit) {
var item = searchData[hit.ref];
results.push({ 'href': item.href, 'title': item.title, 'keywords': item.keywords });
});
postMessage({ e: 'query-ready', q: q, d: results });
}
function buildIndex() {
if (stopWords !== null && !isEmpty(searchData)) {
lunrIndex = lunr(function () {
this.pipeline.remove(lunr.stopWordFilter);
this.ref('href');
this.field('title', { boost: 50 });
this.field('keywords', { boost: 20 });
for (var prop in searchData) {
if (searchData.hasOwnProperty(prop)) {
this.add(searchData[prop]);
}
}
var docfxStopWordFilter = lunr.generateStopWordFilter(stopWords);
lunr.Pipeline.registerFunction(docfxStopWordFilter, 'docfxStopWordFilter');
this.pipeline.add(docfxStopWordFilter);
this.searchPipeline.add(docfxStopWordFilter);
});
}
}
function isEmpty(obj) {
if(!obj) return true;
for (var prop in obj) {
if (obj.hasOwnProperty(prop))
return false;
}
return true;
}
})();
diff --git a/DisCatSharp.Docs/dcs/src/styles/main.css b/DisCatSharp.Docs/dcs/src/styles/main.css
index 5ca71731d..dd14c104e 100644
--- a/DisCatSharp.Docs/dcs/src/styles/main.css
+++ b/DisCatSharp.Docs/dcs/src/styles/main.css
@@ -1,31 +1,35 @@
+:root {
+ color-scheme: dark;
+}
+
#logo
{
max-height: 50px;
}
.navbar-default {
background-color: #2f3136;
}
h1.logo-center
{
text-align: center;
margin-top: 0;
}
article p > img
{
box-shadow: 3px 3px 6px #111;
border-radius: 3px;
border: 1px solid #222;
}
h1.delete-this
{
display: none;
}
span.xref
{
color: #e87408;
}
diff --git a/DisCatSharp.Docs/docfx.json b/DisCatSharp.Docs/docfx.json
index 8c43850d7..9b85b07b6 100644
--- a/DisCatSharp.Docs/docfx.json
+++ b/DisCatSharp.Docs/docfx.json
@@ -1,106 +1,102 @@
{
"metadata": [
{
"src": [
{
"src": "../",
"files": [
"**.csproj"
],
"exclude": [
"**/obj/**",
"**/bin/**",
"_site/**"
]
}
],
"dest": "api",
"filter": "filter_config.yml"
}
],
"build": {
"content": [
{
"files": [
"api/**.yml",
"api/**.md"
]
},
{
"files": [
"**.md",
"toc.yml",
"faq/**.yml",
"faq/**.md",
"articles/**.yml",
"articles/**.md",
"natives/**.yml",
"natives/**.md"
],
"exclude": [
"**/bin/**",
"**/obj/**",
"_site/**",
"dcs/**"
]
}
],
"resource": [
{
"files": [
"images/**",
"natives/**.zip"
],
"exclude": [
"**/bin/**",
"**/obj/**",
"_site/**",
"images/_**"
]
}
],
"overwrite": [
{
"files": [],
"exclude": [
"**/bin/**",
"**/obj/**",
"_site/**"
]
}
],
"dest": "_site",
"globalMetadata": {
"_appFooter": "© 2021-2022 Aiko IT Systems",
"_enableSearch": true,
"_enableNewTab": true,
"_appTitle": "DisCatSharp Docs",
"_gitUrlPattern": "git",
"_gitContribute": {
"repo": "https://github.com/Aiko-IT-Systems/DisCatSharp",
"branch": "main"
- },
- //"_disableNavbar": false,
- //"_disableBreadcrumb": false,
- //"_disableAffix": true,
- //"_disableToc": false,
- //"_disableContribution": true
+ }
},
"disableGitFeatures": false,
"exportRawModel": true,
+ "rawModelOutputFolder": "_site/.bot-query/raw/",
"globalMetadataFiles": [],
"fileMetadataFiles": [],
"template": [
"dcs"
],
"postProcessors": ["ExtractSearchIndex", "CustomMemberIndexer"],
"noLangKeyword": false,
"keepFileLink": false,
"cleanupCacheHistory": false,
"sitemap": {
"baseUrl": "https://docs.dcs.aitsys.dev/",
"changefreq": "hourly",
"priority": 1.0
}
}
}
diff --git a/DisCatSharp.EventHandlers.Tests/global.json b/DisCatSharp.EventHandlers.Tests/global.json
new file mode 100644
index 000000000..90a51a911
--- /dev/null
+++ b/DisCatSharp.EventHandlers.Tests/global.json
@@ -0,0 +1,6 @@
+{
+ "sdk": {
+ "version": "6.0.202",
+ "rollForward": "major"
+ }
+}
diff --git a/DisCatSharp.Hosting.DependencyInjection/DisCatSharp.Hosting.DependencyInjection.csproj b/DisCatSharp.Hosting.DependencyInjection/DisCatSharp.Hosting.DependencyInjection.csproj
index cd1432f82..edbb9056a 100644
--- a/DisCatSharp.Hosting.DependencyInjection/DisCatSharp.Hosting.DependencyInjection.csproj
+++ b/DisCatSharp.Hosting.DependencyInjection/DisCatSharp.Hosting.DependencyInjection.csproj
@@ -1,38 +1,29 @@
net6.0
enable
True
DisCatSharp.Hosting.DependencyInjection
Dependency Injection for DisCatSharp.Hosting.
discord, discord-api, bots, discord-bots, net-sdk, dcs, discatsharp, csharp, dotnet, vb-net, fsharp
-
- LICENSE.md
-
-
- True
-
-
-
-
diff --git a/DisCatSharp.Hosting.DependencyInjection/global.json b/DisCatSharp.Hosting.DependencyInjection/global.json
new file mode 100644
index 000000000..90a51a911
--- /dev/null
+++ b/DisCatSharp.Hosting.DependencyInjection/global.json
@@ -0,0 +1,6 @@
+{
+ "sdk": {
+ "version": "6.0.202",
+ "rollForward": "major"
+ }
+}
diff --git a/DisCatSharp.Hosting.Tests/DisCatSharp.Hosting.Tests.csproj b/DisCatSharp.Hosting.Tests/DisCatSharp.Hosting.Tests.csproj
index e02ba5b6c..8f77ba521 100644
--- a/DisCatSharp.Hosting.Tests/DisCatSharp.Hosting.Tests.csproj
+++ b/DisCatSharp.Hosting.Tests/DisCatSharp.Hosting.Tests.csproj
@@ -1,49 +1,49 @@
net6.0
enable
1591;NU5128;DV2001
false
false
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
runtime; build; native; contentfiles; analyzers; buildtransitive
all
Always
Always
Always
Always
diff --git a/DisCatSharp.Hosting.Tests/global.json b/DisCatSharp.Hosting.Tests/global.json
new file mode 100644
index 000000000..90a51a911
--- /dev/null
+++ b/DisCatSharp.Hosting.Tests/global.json
@@ -0,0 +1,6 @@
+{
+ "sdk": {
+ "version": "6.0.202",
+ "rollForward": "major"
+ }
+}
diff --git a/DisCatSharp.Hosting/DisCatSharp.Hosting.csproj b/DisCatSharp.Hosting/DisCatSharp.Hosting.csproj
index 49eea7954..1b42203af 100644
--- a/DisCatSharp.Hosting/DisCatSharp.Hosting.csproj
+++ b/DisCatSharp.Hosting/DisCatSharp.Hosting.csproj
@@ -1,40 +1,31 @@
net6.0
enable
True
DisCatSharp.Hosting
Hosting for DisCatSharp.
discord, discord-api, bots, discord-bots, net-sdk, dcs, discatsharp, csharp, dotnet, vb-net, fsharp
-
- LICENSE.md
-
-
- True
-
-
-
-
diff --git a/DisCatSharp.Hosting/global.json b/DisCatSharp.Hosting/global.json
new file mode 100644
index 000000000..90a51a911
--- /dev/null
+++ b/DisCatSharp.Hosting/global.json
@@ -0,0 +1,6 @@
+{
+ "sdk": {
+ "version": "6.0.202",
+ "rollForward": "major"
+ }
+}
diff --git a/DisCatSharp.Interactivity/DisCatSharp.Interactivity.csproj b/DisCatSharp.Interactivity/DisCatSharp.Interactivity.csproj
index a55d3130e..d3a703399 100644
--- a/DisCatSharp.Interactivity/DisCatSharp.Interactivity.csproj
+++ b/DisCatSharp.Interactivity/DisCatSharp.Interactivity.csproj
@@ -1,40 +1,31 @@
DisCatSharp.Interactivity
DisCatSharp.Interactivity
DisCatSharp.Interactivity
Interactivity extension for DisCatSharp.
discord, discord-api, bots, discord-bots, chat, dcs, discatsharp, csharp, dotnet, vb-net, fsharp, interactive, pagination, reactions
-
- LICENSE.md
-
-
- True
-
-
-
-
diff --git a/DisCatSharp.Interactivity/EventHandling/Components/Requests/InteractionPaginationRequest.cs b/DisCatSharp.Interactivity/EventHandling/Components/Requests/InteractionPaginationRequest.cs
index f040e5933..658274844 100644
--- a/DisCatSharp.Interactivity/EventHandling/Components/Requests/InteractionPaginationRequest.cs
+++ b/DisCatSharp.Interactivity/EventHandling/Components/Requests/InteractionPaginationRequest.cs
@@ -1,265 +1,265 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using DisCatSharp.Entities;
using DisCatSharp.Interactivity.Enums;
namespace DisCatSharp.Interactivity.EventHandling
{
///
/// The interaction pagination request.
///
internal class InteractionPaginationRequest : IPaginationRequest
{
private int _index;
private readonly List _pages = new();
private readonly TaskCompletionSource _tcs = new();
private DiscordInteraction _lastInteraction;
private CancellationTokenSource _interactionCts;
private readonly CancellationToken _token;
private readonly DiscordUser _user;
private readonly DiscordMessage _message;
private readonly PaginationButtons _buttons;
private readonly PaginationBehaviour _wrapBehavior;
private readonly ButtonPaginationBehavior _behaviorBehavior;
///
/// Initializes a new instance of the class.
///
/// The interaction.
/// The message.
/// The user.
/// The behavior.
/// The behavior behavior.
/// The buttons.
/// The pages.
/// The token.
public InteractionPaginationRequest(DiscordInteraction interaction, DiscordMessage message, DiscordUser user,
PaginationBehaviour behavior, ButtonPaginationBehavior behaviorBehavior,
PaginationButtons buttons, IEnumerable pages, CancellationToken token)
{
this._user = user;
this._token = token;
this._buttons = new PaginationButtons(buttons);
this._message = message;
this._wrapBehavior = behavior;
this._behaviorBehavior = behaviorBehavior;
this._pages.AddRange(pages);
this.RegenerateCts(interaction);
this._token.Register(() => this._tcs.TrySetResult(false));
}
///
/// Gets the page count.
///
public int PageCount => this._pages.Count;
///
/// Regenerates the cts.
///
/// The interaction.
internal void RegenerateCts(DiscordInteraction interaction)
{
this._interactionCts?.Dispose();
this._lastInteraction = interaction;
this._interactionCts = new CancellationTokenSource(TimeSpan.FromSeconds((60 * 15) - 5));
this._interactionCts.Token.Register(() => this._tcs.TrySetResult(false));
}
///
/// Gets the page.
///
public Task GetPageAsync()
{
var page = Task.FromResult(this._pages[this._index]);
if (this.PageCount is 1)
{
- this._buttons.ButtonArray.Select(b => b.Disable());
+ _ = this._buttons.ButtonArray.Select(b => b.Disable());
this._buttons.Stop.Enable();
return page;
}
if (this._wrapBehavior is PaginationBehaviour.WrapAround)
return page;
this._buttons.SkipLeft.Disabled = this._index < 2;
this._buttons.Left.Disabled = this._index < 1;
this._buttons.Right.Disabled = this._index == this.PageCount - 1;
this._buttons.SkipRight.Disabled = this._index >= this.PageCount - 2;
return page;
}
///
/// Skips the left page.
///
public Task SkipLeftAsync()
{
if (this._wrapBehavior is PaginationBehaviour.WrapAround)
{
this._index = this._index is 0 ? this._pages.Count - 1 : 0;
return Task.CompletedTask;
}
this._index = 0;
return Task.CompletedTask;
}
///
/// Skips the right page.
///
public Task SkipRightAsync()
{
if (this._wrapBehavior is PaginationBehaviour.WrapAround)
{
this._index = this._index == this.PageCount - 1 ? 0 : this.PageCount - 1;
return Task.CompletedTask;
}
this._index = this._pages.Count - 1;
return Task.CompletedTask;
}
///
/// Gets the next page.
///
/// A Task.
public Task NextPageAsync()
{
this._index++;
if (this._wrapBehavior is PaginationBehaviour.WrapAround)
{
if (this._index >= this.PageCount)
this._index = 0;
return Task.CompletedTask;
}
this._index = Math.Min(this._index, this.PageCount - 1);
return Task.CompletedTask;
}
///
/// Gets the previous page.
///
public Task PreviousPageAsync()
{
this._index--;
if (this._wrapBehavior is PaginationBehaviour.WrapAround)
{
if (this._index is -1)
this._index = this._pages.Count - 1;
return Task.CompletedTask;
}
this._index = Math.Max(this._index, 0);
return Task.CompletedTask;
}
///
/// Gets the emojis.
///
public Task GetEmojisAsync()
=> Task.FromException(new NotSupportedException("Emojis aren't supported for this request."));
///
/// Gets the buttons.
///
public Task> GetButtonsAsync()
=> Task.FromResult((IEnumerable)this._buttons.ButtonArray);
///
/// Gets the message.
///
public Task GetMessageAsync() => Task.FromResult(this._message);
///
/// Gets the user.
///
public Task GetUserAsync() => Task.FromResult(this._user);
///
/// Gets the task completion source.
///
public Task> GetTaskCompletionSourceAsync() => Task.FromResult(this._tcs);
///
/// Cleanup.
///
public async Task DoCleanupAsync()
{
switch (this._behaviorBehavior)
{
case ButtonPaginationBehavior.Disable:
var buttons = this._buttons.ButtonArray
.Select(b => new DiscordButtonComponent(b))
.Select(b => b.Disable());
var builder = new DiscordWebhookBuilder()
.WithContent(this._pages[this._index].Content)
.AddEmbed(this._pages[this._index].Embed)
.AddComponents(buttons);
await this._lastInteraction.EditOriginalResponseAsync(builder).ConfigureAwait(false);
break;
case ButtonPaginationBehavior.DeleteButtons:
builder = new DiscordWebhookBuilder()
.WithContent(this._pages[this._index].Content)
.AddEmbed(this._pages[this._index].Embed);
await this._lastInteraction.EditOriginalResponseAsync(builder).ConfigureAwait(false);
break;
case ButtonPaginationBehavior.DeleteMessage:
await this._lastInteraction.DeleteOriginalResponseAsync().ConfigureAwait(false);
break;
case ButtonPaginationBehavior.Ignore:
break;
}
}
}
}
diff --git a/DisCatSharp.Interactivity/EventHandling/Paginator.cs b/DisCatSharp.Interactivity/EventHandling/Paginator.cs
index 8711e1df4..3e3da649f 100644
--- a/DisCatSharp.Interactivity/EventHandling/Paginator.cs
+++ b/DisCatSharp.Interactivity/EventHandling/Paginator.cs
@@ -1,314 +1,305 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Threading.Tasks;
using ConcurrentCollections;
using DisCatSharp.Entities;
using DisCatSharp.EventArgs;
using DisCatSharp.Interactivity.Enums;
using Microsoft.Extensions.Logging;
namespace DisCatSharp.Interactivity.EventHandling
{
///
/// The paginator.
///
internal class Paginator : IPaginator
{
DiscordClient _client;
ConcurrentHashSet _requests;
///
/// Creates a new EventWaiter object.
///
/// Discord client
public Paginator(DiscordClient client)
{
this._client = client;
this._requests = new ConcurrentHashSet();
this._client.MessageReactionAdded += this.HandleReactionAdd;
this._client.MessageReactionRemoved += this.HandleReactionRemove;
this._client.MessageReactionsCleared += this.HandleReactionClear;
}
///
/// Dos the pagination async.
///
/// The request.
public async Task DoPaginationAsync(IPaginationRequest request)
{
await this.ResetReactionsAsync(request).ConfigureAwait(false);
this._requests.Add(request);
try
{
var tcs = await request.GetTaskCompletionSourceAsync().ConfigureAwait(false);
await tcs.Task.ConfigureAwait(false);
}
catch (Exception ex)
{
this._client.Logger.LogError(InteractivityEvents.InteractivityPaginationError, ex, "Exception occurred while paginating");
}
finally
{
this._requests.TryRemove(request);
try
{
await request.DoCleanupAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
this._client.Logger.LogError(InteractivityEvents.InteractivityPaginationError, ex, "Exception occurred while paginating");
}
}
}
///
/// Handles the reaction add.
///
/// The client.
/// The event's arguments.
private Task HandleReactionAdd(DiscordClient client, MessageReactionAddEventArgs eventArgs)
{
if (this._requests.Count == 0)
return Task.CompletedTask;
_ = Task.Run(async () =>
{
foreach (var req in this._requests)
{
var emojis = await req.GetEmojisAsync().ConfigureAwait(false);
var msg = await req.GetMessageAsync().ConfigureAwait(false);
var usr = await req.GetUserAsync().ConfigureAwait(false);
if (msg.Id == eventArgs.Message.Id)
{
if (eventArgs.User.Id == usr.Id)
{
if (req.PageCount > 1 &&
(eventArgs.Emoji == emojis.Left ||
eventArgs.Emoji == emojis.SkipLeft ||
eventArgs.Emoji == emojis.Right ||
eventArgs.Emoji == emojis.SkipRight ||
eventArgs.Emoji == emojis.Stop))
{
await this.PaginateAsync(req, eventArgs.Emoji).ConfigureAwait(false);
}
else if (eventArgs.Emoji == emojis.Stop &&
req is PaginationRequest paginationRequest &&
paginationRequest.PaginationDeletion == PaginationDeletion.DeleteMessage)
{
await this.PaginateAsync(req, eventArgs.Emoji).ConfigureAwait(false);
}
else
{
await msg.DeleteReactionAsync(eventArgs.Emoji, eventArgs.User).ConfigureAwait(false);
}
}
else if (eventArgs.User.Id != this._client.CurrentUser.Id)
{
if (eventArgs.Emoji != emojis.Left &&
eventArgs.Emoji != emojis.SkipLeft &&
eventArgs.Emoji != emojis.Right &&
eventArgs.Emoji != emojis.SkipRight &&
eventArgs.Emoji != emojis.Stop)
{
await msg.DeleteReactionAsync(eventArgs.Emoji, eventArgs.User).ConfigureAwait(false);
}
}
}
}
});
return Task.CompletedTask;
}
///
/// Handles the reaction remove.
///
/// The client.
/// The event's arguments.
private Task HandleReactionRemove(DiscordClient client, MessageReactionRemoveEventArgs eventArgs)
{
if (this._requests.Count == 0)
return Task.CompletedTask;
_ = Task.Run(async () =>
{
foreach (var req in this._requests)
{
var emojis = await req.GetEmojisAsync().ConfigureAwait(false);
var msg = await req.GetMessageAsync().ConfigureAwait(false);
var usr = await req.GetUserAsync().ConfigureAwait(false);
if (msg.Id == eventArgs.Message.Id)
{
if (eventArgs.User.Id == usr.Id)
{
if (req.PageCount > 1 &&
(eventArgs.Emoji == emojis.Left ||
eventArgs.Emoji == emojis.SkipLeft ||
eventArgs.Emoji == emojis.Right ||
eventArgs.Emoji == emojis.SkipRight ||
eventArgs.Emoji == emojis.Stop))
{
await this.PaginateAsync(req, eventArgs.Emoji).ConfigureAwait(false);
}
else if (eventArgs.Emoji == emojis.Stop &&
req is PaginationRequest paginationRequest &&
paginationRequest.PaginationDeletion == PaginationDeletion.DeleteMessage)
{
await this.PaginateAsync(req, eventArgs.Emoji).ConfigureAwait(false);
}
}
}
}
});
return Task.CompletedTask;
}
///
/// Handles the reaction clear.
///
/// The client.
/// The eventArgs.
private Task HandleReactionClear(DiscordClient client, MessageReactionsClearEventArgs eventArgs)
{
if (this._requests.Count == 0)
return Task.CompletedTask;
_ = Task.Run(async () =>
{
foreach (var req in this._requests)
{
var msg = await req.GetMessageAsync().ConfigureAwait(false);
if (msg.Id == eventArgs.Message.Id)
{
await this.ResetReactionsAsync(req).ConfigureAwait(false);
}
}
});
return Task.CompletedTask;
}
///
/// Resets the reactions async.
///
/// The p.
private async Task ResetReactionsAsync(IPaginationRequest p)
{
var msg = await p.GetMessageAsync().ConfigureAwait(false);
var emojis = await p.GetEmojisAsync().ConfigureAwait(false);
- // Test permissions to avoid a 403:
- // https://totally-not.a-sketchy.site/3pXpRLK.png
- // Yes, this is an issue
- // No, we should not require people to guarantee MANAGE_MESSAGES
- // Need to check following:
- // - In guild?
- // - If yes, check if have permission
- // - If all above fail (DM || guild && no permission), skip this
var chn = msg.Channel;
var gld = chn?.Guild;
var mbr = gld?.CurrentMember;
- if (mbr != null /* == is guild and cache is valid */ && (chn.PermissionsFor(mbr) & Permissions.ManageChannels) != 0) /* == has permissions */
+ if (mbr != null && (chn.PermissionsFor(mbr) & Permissions.ManageMessages) != 0)
await msg.DeleteAllReactionsAsync("Pagination").ConfigureAwait(false);
- // ENDOF: 403 fix
if (p.PageCount > 1)
{
if (emojis.SkipLeft != null)
await msg.CreateReactionAsync(emojis.SkipLeft).ConfigureAwait(false);
if (emojis.Left != null)
await msg.CreateReactionAsync(emojis.Left).ConfigureAwait(false);
if (emojis.Right != null)
await msg.CreateReactionAsync(emojis.Right).ConfigureAwait(false);
if (emojis.SkipRight != null)
await msg.CreateReactionAsync(emojis.SkipRight).ConfigureAwait(false);
if (emojis.Stop != null)
await msg.CreateReactionAsync(emojis.Stop).ConfigureAwait(false);
}
else if (emojis.Stop != null && p is PaginationRequest paginationRequest && paginationRequest.PaginationDeletion == PaginationDeletion.DeleteMessage)
{
await msg.CreateReactionAsync(emojis.Stop).ConfigureAwait(false);
}
}
///
/// Paginates the async.
///
/// The p.
/// The emoji.
private async Task PaginateAsync(IPaginationRequest p, DiscordEmoji emoji)
{
var emojis = await p.GetEmojisAsync().ConfigureAwait(false);
var msg = await p.GetMessageAsync().ConfigureAwait(false);
if (emoji == emojis.SkipLeft)
await p.SkipLeftAsync().ConfigureAwait(false);
else if (emoji == emojis.Left)
await p.PreviousPageAsync().ConfigureAwait(false);
else if (emoji == emojis.Right)
await p.NextPageAsync().ConfigureAwait(false);
else if (emoji == emojis.SkipRight)
await p.SkipRightAsync().ConfigureAwait(false);
else if (emoji == emojis.Stop)
{
var tcs = await p.GetTaskCompletionSourceAsync().ConfigureAwait(false);
tcs.TrySetResult(true);
return;
}
var page = await p.GetPageAsync().ConfigureAwait(false);
var builder = new DiscordMessageBuilder()
.WithContent(page.Content)
.WithEmbed(page.Embed);
await builder.ModifyAsync(msg).ConfigureAwait(false);
}
~Paginator()
{
this.Dispose();
}
///
/// Disposes this EventWaiter
///
public void Dispose()
{
this._client.MessageReactionAdded -= this.HandleReactionAdd;
this._client.MessageReactionRemoved -= this.HandleReactionRemove;
this._client.MessageReactionsCleared -= this.HandleReactionClear;
this._client = null;
this._requests.Clear();
this._requests = null;
}
}
}
diff --git a/DisCatSharp.Interactivity/GlobalSuppressions.cs b/DisCatSharp.Interactivity/GlobalSuppressions.cs
index b33d83feb..386c3f81b 100644
--- a/DisCatSharp.Interactivity/GlobalSuppressions.cs
+++ b/DisCatSharp.Interactivity/GlobalSuppressions.cs
@@ -1,28 +1,33 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "", Scope = "member", Target = "~M:DisCatSharp.Interactivity.InteractivityExtension.HandleInvalidInteraction(DisCatSharp.Entities.DiscordInteraction)~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "", Scope = "member", Target = "~F:DisCatSharp.Interactivity.EventHandling.PaginationRequest._timeout")]
[assembly: SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "", Scope = "member", Target = "~F:DisCatSharp.Interactivity.InteractivityExtension._componentInteractionWaiter")]
[assembly: SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "", Scope = "member", Target = "~F:DisCatSharp.Interactivity.InteractivityExtension._modalInteractionWaiter")]
+[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "", Scope = "member", Target = "~M:DisCatSharp.Interactivity.EventHandling.EventWaiter`1.CollectMatchesAsync(DisCatSharp.Interactivity.EventHandling.CollectRequest{`0})~System.Threading.Tasks.Task{System.Collections.ObjectModel.ReadOnlyCollection{`0}}")]
+[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "", Scope = "member", Target = "~M:DisCatSharp.Interactivity.EventHandling.EventWaiter`1.WaitForMatchAsync(DisCatSharp.Interactivity.EventHandling.MatchRequest{`0})~System.Threading.Tasks.Task{`0}")]
+[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:DisCatSharp.Interactivity.InteractivityExtension.SplitString(System.String,System.Int32)~System.Collections.Generic.List{System.String}")]
+[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:DisCatSharp.Interactivity.EventHandling.Paginator.PaginateAsync(DisCatSharp.Interactivity.EventHandling.IPaginationRequest,DisCatSharp.Entities.DiscordEmoji)~System.Threading.Tasks.Task")]
+[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:DisCatSharp.Interactivity.EventHandling.Paginator.ResetReactionsAsync(DisCatSharp.Interactivity.EventHandling.IPaginationRequest)~System.Threading.Tasks.Task")]
diff --git a/DisCatSharp.Interactivity/global.json b/DisCatSharp.Interactivity/global.json
new file mode 100644
index 000000000..90a51a911
--- /dev/null
+++ b/DisCatSharp.Interactivity/global.json
@@ -0,0 +1,6 @@
+{
+ "sdk": {
+ "version": "6.0.202",
+ "rollForward": "major"
+ }
+}
diff --git a/DisCatSharp.Lavalink/DisCatSharp.Lavalink.csproj b/DisCatSharp.Lavalink/DisCatSharp.Lavalink.csproj
index 780b45e7a..0b5ab864f 100644
--- a/DisCatSharp.Lavalink/DisCatSharp.Lavalink.csproj
+++ b/DisCatSharp.Lavalink/DisCatSharp.Lavalink.csproj
@@ -1,40 +1,31 @@
DisCatSharp.Lavalink
DisCatSharp.Lavalink
true
DisCatSharp.Lavalink
Lavalink implementation for DisCatSharp.
discord, discord-api, bots, discord-bots, chat, dcs, discatsharp, csharp, dotnet, vb-net, fsharp, audio, voice, radio, music, lavalink, lavaplayer
-
- LICENSE.md
-
-
- True
-
-
-
-
diff --git a/DisCatSharp.Lavalink/GlobalSuppressions.cs b/DisCatSharp.Lavalink/GlobalSuppressions.cs
index 1e34a019f..8e5e316a6 100644
--- a/DisCatSharp.Lavalink/GlobalSuppressions.cs
+++ b/DisCatSharp.Lavalink/GlobalSuppressions.cs
@@ -1,28 +1,34 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "", Scope = "member", Target = "~M:DisCatSharp.Lavalink.LavalinkGuildConnection.#ctor(DisCatSharp.Lavalink.LavalinkNodeConnection,DisCatSharp.Entities.DiscordChannel,DisCatSharp.EventArgs.VoiceStateUpdateEventArgs)")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "", Scope = "member", Target = "~M:DisCatSharp.Lavalink.LavalinkNodeConnection.StartAsync~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "", Scope = "member", Target = "~M:DisCatSharp.Lavalink.LavalinkNodeConnection.WebSocket_OnMessage(DisCatSharp.Net.WebSocket.IWebSocketClient,DisCatSharp.EventArgs.SocketMessageEventArgs)~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "", Scope = "member", Target = "~M:DisCatSharp.Lavalink.LavalinkNodeConnection.WsSendAsync(System.String)~System.Threading.Tasks.Task")]
+[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "", Scope = "member", Target = "~M:DisCatSharp.Lavalink.LavalinkNodeConnection.WebSocket_OnDisconnect(DisCatSharp.Net.WebSocket.IWebSocketClient,DisCatSharp.EventArgs.SocketCloseEventArgs)~System.Threading.Tasks.Task")]
+[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "", Scope = "member", Target = "~M:DisCatSharp.Lavalink.LavalinkRestClient.InternalDecodeTrackAsync(System.Uri)~System.Threading.Tasks.Task{DisCatSharp.Lavalink.LavalinkTrack}")]
+[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "", Scope = "member", Target = "~M:DisCatSharp.Lavalink.LavalinkRestClient.InternalFreeAddressAsync(System.Uri,System.String)~System.Threading.Tasks.Task")]
+[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "", Scope = "member", Target = "~M:DisCatSharp.Lavalink.LavalinkRestClient.InternalFreeAllAddressesAsync(System.Uri)~System.Threading.Tasks.Task")]
+[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:DisCatSharp.Lavalink.Entities.LavalinkRouteStatus.GetLavalinkRoutePlannerType(System.String)~System.Nullable{DisCatSharp.Lavalink.LavalinkRoutePlannerType}")]
+[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:DisCatSharp.Lavalink.LavalinkExtension.FilterByLoad(DisCatSharp.Lavalink.LavalinkNodeConnection[])~DisCatSharp.Lavalink.LavalinkNodeConnection")]
diff --git a/DisCatSharp.Lavalink/global.json b/DisCatSharp.Lavalink/global.json
new file mode 100644
index 000000000..90a51a911
--- /dev/null
+++ b/DisCatSharp.Lavalink/global.json
@@ -0,0 +1,6 @@
+{
+ "sdk": {
+ "version": "6.0.202",
+ "rollForward": "major"
+ }
+}
diff --git a/DisCatSharp.Tests/global.json b/DisCatSharp.Tests/global.json
new file mode 100644
index 000000000..90a51a911
--- /dev/null
+++ b/DisCatSharp.Tests/global.json
@@ -0,0 +1,6 @@
+{
+ "sdk": {
+ "version": "6.0.202",
+ "rollForward": "major"
+ }
+}
diff --git a/DisCatSharp.VoiceNext.Natives/DisCatSharp.VoiceNext.Natives.csproj b/DisCatSharp.VoiceNext.Natives/DisCatSharp.VoiceNext.Natives.csproj
index 71ef73db1..fb247669f 100644
--- a/DisCatSharp.VoiceNext.Natives/DisCatSharp.VoiceNext.Natives.csproj
+++ b/DisCatSharp.VoiceNext.Natives/DisCatSharp.VoiceNext.Natives.csproj
@@ -1,45 +1,36 @@
net6.0
false
win-x86;win-x64
true
false
symbols.nupkg
DisCatSharp.VoiceNext.Natives
Voice Natives for DisCatSharp.
discord, discord-api, bots, discord-bots, chat, dcs, discatsharp, csharp, dotnet, vb-net, fsharp, audio, voice, radio, music
-
- LICENSE.md
true
runtimes
-
-
- True
-
-
-
-
diff --git a/DisCatSharp.VoiceNext.Natives/global.json b/DisCatSharp.VoiceNext.Natives/global.json
new file mode 100644
index 000000000..90a51a911
--- /dev/null
+++ b/DisCatSharp.VoiceNext.Natives/global.json
@@ -0,0 +1,6 @@
+{
+ "sdk": {
+ "version": "6.0.202",
+ "rollForward": "major"
+ }
+}
diff --git a/DisCatSharp.VoiceNext/DisCatSharp.VoiceNext.csproj b/DisCatSharp.VoiceNext/DisCatSharp.VoiceNext.csproj
index fbaf8ae5b..e27724095 100644
--- a/DisCatSharp.VoiceNext/DisCatSharp.VoiceNext.csproj
+++ b/DisCatSharp.VoiceNext/DisCatSharp.VoiceNext.csproj
@@ -1,45 +1,36 @@
DisCatSharp.VoiceNext
DisCatSharp.VoiceNext
true
DisCatSharp.VoiceNext
Voice extension for DisCatSharp.
discord, discord-api, bots, discord-bots, chat, dcs, discatsharp, csharp, dotnet, vb-net, fsharp, audio, voice, radio, music
-
- LICENSE.md
-
-
- True
-
-
-
-
diff --git a/DisCatSharp.VoiceNext/GlobalSuppressions.cs b/DisCatSharp.VoiceNext/GlobalSuppressions.cs
index c1235c608..083ba1ad0 100644
--- a/DisCatSharp.VoiceNext/GlobalSuppressions.cs
+++ b/DisCatSharp.VoiceNext/GlobalSuppressions.cs
@@ -1,33 +1,50 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.VoiceNextConnection.Dispose")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.VoiceNextConnection.VoiceWS_SocketMessage(DisCatSharp.Net.WebSocket.IWebSocketClient,DisCatSharp.EventArgs.SocketMessageEventArgs)~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.VoiceNextConnection.WsSendAsync(System.String)~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Performance", "CA1806:Do not ignore method results", Justification = "", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.Codec.Interop.OpusGetLastPacketDuration(System.IntPtr,System.Int32@)")]
+[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.VoiceNextConnection.HandleDispatch(Newtonsoft.Json.Linq.JObject)~System.Threading.Tasks.Task")]
+[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.VoiceNextConnection.ProcessKeepalive(System.Byte[])")]
+[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.VoiceNextConnection.Stage1(DisCatSharp.VoiceNext.Entities.VoiceReadyPayload)~System.Threading.Tasks.Task")]
+[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.VoiceNextConnection.Stage2(DisCatSharp.VoiceNext.Entities.VoiceSessionDescriptionPayload)~System.Threading.Tasks.Task")]
+[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.VoiceNextConnection.VoiceSenderTask~System.Threading.Tasks.Task")]
+[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.VoiceNextConnection.VoiceWS_SocketClosed(DisCatSharp.Net.WebSocket.IWebSocketClient,DisCatSharp.EventArgs.SocketCloseEventArgs)~System.Threading.Tasks.Task")]
+[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.Codec.Opus.GetLastPacketSampleCount(DisCatSharp.VoiceNext.Codec.OpusDecoder)~System.Int32")]
+[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.Codec.Opus.ProcessPacketLoss(DisCatSharp.VoiceNext.Codec.OpusDecoder,System.Int32,System.Span{System.Byte}@)")]
+[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.Codec.Rtp.CalculatePacketSize(System.Int32,DisCatSharp.VoiceNext.Codec.EncryptionMode)~System.Int32")]
+[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.Codec.Rtp.DecodeHeader(System.ReadOnlySpan{System.Byte},System.UInt16@,System.UInt32@,System.UInt32@,System.Boolean@)")]
+[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.Codec.Rtp.EncodeHeader(System.UInt16,System.UInt32,System.UInt32,System.Span{System.Byte})")]
+[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.Codec.Rtp.GetDataFromPacket(System.ReadOnlySpan{System.Byte},System.ReadOnlySpan{System.Byte}@,DisCatSharp.VoiceNext.Codec.EncryptionMode)")]
+[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.Codec.Rtp.IsRtpHeader(System.ReadOnlySpan{System.Byte})~System.Boolean")]
+[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.Codec.Sodium.AppendNonce(System.ReadOnlySpan{System.Byte},System.Span{System.Byte},DisCatSharp.VoiceNext.Codec.EncryptionMode)")]
+[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.Codec.Sodium.GenerateNonce(System.ReadOnlySpan{System.Byte},System.Span{System.Byte})")]
+[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.Codec.Sodium.GenerateNonce(System.UInt32,System.Span{System.Byte})")]
+[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.Codec.Sodium.GetNonce(System.ReadOnlySpan{System.Byte},System.Span{System.Byte},DisCatSharp.VoiceNext.Codec.EncryptionMode)")]
diff --git a/DisCatSharp.VoiceNext/VoiceNextConnection.cs b/DisCatSharp.VoiceNext/VoiceNextConnection.cs
index ff2e96e1c..c0e07234c 100644
--- a/DisCatSharp.VoiceNext/VoiceNextConnection.cs
+++ b/DisCatSharp.VoiceNext/VoiceNextConnection.cs
@@ -1,1361 +1,1361 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using DisCatSharp.Common.Utilities;
using DisCatSharp.Entities;
using DisCatSharp.EventArgs;
using DisCatSharp.Net;
using DisCatSharp.Net.Udp;
using DisCatSharp.Net.WebSocket;
using DisCatSharp.VoiceNext.Codec;
using DisCatSharp.VoiceNext.Entities;
using DisCatSharp.VoiceNext.EventArgs;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace DisCatSharp.VoiceNext
{
internal delegate Task VoiceDisconnectedEventHandler(DiscordGuild guild);
///
/// VoiceNext connection to a voice channel.
///
public sealed class VoiceNextConnection : IDisposable
{
///
/// Triggered whenever a user speaks in the connected voice channel.
///
public event AsyncEventHandler UserSpeaking
{
add => this._userSpeaking.Register(value);
remove => this._userSpeaking.Unregister(value);
}
private readonly AsyncEvent _userSpeaking;
///
/// Triggered whenever a user joins voice in the connected guild.
///
public event AsyncEventHandler UserJoined
{
add => this._userJoined.Register(value);
remove => this._userJoined.Unregister(value);
}
private readonly AsyncEvent _userJoined;
///
/// Triggered whenever a user leaves voice in the connected guild.
///
public event AsyncEventHandler UserLeft
{
add => this._userLeft.Register(value);
remove => this._userLeft.Unregister(value);
}
private readonly AsyncEvent _userLeft;
///
/// Triggered whenever voice data is received from the connected voice channel.
///
public event AsyncEventHandler VoiceReceived
{
add => this._voiceReceived.Register(value);
remove => this._voiceReceived.Unregister(value);
}
private readonly AsyncEvent _voiceReceived;
///
/// Triggered whenever voice WebSocket throws an exception.
///
public event AsyncEventHandler VoiceSocketErrored
{
add => this._voiceSocketError.Register(value);
remove => this._voiceSocketError.Unregister(value);
}
private readonly AsyncEvent _voiceSocketError;
internal event VoiceDisconnectedEventHandler VoiceDisconnected;
///
/// Gets the unix epoch.
///
private static DateTimeOffset s_unixEpoch { get; } = new(1970, 1, 1, 0, 0, 0, TimeSpan.Zero);
///
/// Gets the discord.
///
private readonly DiscordClient _discord;
///
/// Gets the guild.
///
private readonly DiscordGuild _guild;
///
/// Gets the transmitting s s r cs.
///
private readonly ConcurrentDictionary _transmittingSsrCs;
///
/// Gets the udp client.
///
private readonly BaseUdpClient _udpClient;
///
/// Gets or sets the voice ws.
///
private IWebSocketClient _voiceWs;
///
/// Gets or sets the heartbeat task.
///
private Task _heartbeatTask;
///
/// Gets or sets the heartbeat interval.
///
private int _heartbeatInterval;
///
/// Gets or sets the last heartbeat.
///
private DateTimeOffset _lastHeartbeat;
///
/// Gets or sets the token source.
///
private CancellationTokenSource _tokenSource;
///
/// Gets the token.
///
private CancellationToken TOKEN
=> this._tokenSource.Token;
///
/// Gets or sets the server data.
///
internal VoiceServerUpdatePayload ServerData { get; set; }
///
/// Gets or sets the state data.
///
internal VoiceStateUpdatePayload StateData { get; set; }
///
/// Gets or sets a value indicating whether resume.
///
internal bool Resume { get; set; }
///
/// Gets the configuration.
///
private readonly VoiceNextConfiguration _configuration;
///
/// Gets or sets the opus.
///
private Opus _opus;
///
/// Gets or sets the sodium.
///
private Sodium _sodium;
///
/// Gets or sets the rtp.
///
private Rtp _rtp;
///
/// Gets or sets the selected encryption mode.
///
private EncryptionMode _selectedEncryptionMode;
///
/// Gets or sets the nonce.
///
private uint _nonce;
///
/// Gets or sets the sequence.
///
private ushort _sequence;
///
/// Gets or sets the timestamp.
///
private uint _timestamp;
///
/// Gets or sets the s s r c.
///
private uint _ssrc;
///
/// Gets or sets the key.
///
private byte[] _key;
///
/// Gets or sets the discovered endpoint.
///
private IpEndpoint _discoveredEndpoint;
///
/// Gets or sets the web socket endpoint.
///
internal ConnectionEndpoint WebSocketEndpoint { get; set; }
///
/// Gets or sets the udp endpoint.
///
internal ConnectionEndpoint UdpEndpoint { get; set; }
///
/// Gets or sets the ready wait.
///
private readonly TaskCompletionSource _readyWait;
///
/// Gets or sets a value indicating whether is initialized.
///
private bool _isInitialized;
///
/// Gets or sets a value indicating whether is disposed.
///
private bool _isDisposed;
///
/// Gets or sets the playing wait.
///
private TaskCompletionSource _playingWait;
///
/// Gets the pause event.
///
private readonly AsyncManualResetEvent _pauseEvent;
///
/// Gets or sets the transmit stream.
///
private VoiceTransmitSink _transmitStream;
///
/// Gets the transmit channel.
///
private readonly Channel _transmitChannel;
///
/// Gets the keepalive timestamps.
///
private readonly ConcurrentDictionary _keepaliveTimestamps;
private ulong _lastKeepalive;
///
/// Gets or sets the sender task.
///
private Task _senderTask;
///
/// Gets or sets the sender token source.
///
private CancellationTokenSource _senderTokenSource;
///
/// Gets the sender token.
///
private CancellationToken SENDER_TOKEN
=> this._senderTokenSource.Token;
///
/// Gets or sets the receiver task.
///
private Task _receiverTask;
///
/// Gets or sets the receiver token source.
///
private CancellationTokenSource _receiverTokenSource;
///
/// Gets the receiver token.
///
private CancellationToken RECEIVER_TOKEN
=> this._receiverTokenSource.Token;
///
/// Gets or sets the keepalive task.
///
private Task _keepaliveTask;
///
/// Gets or sets the keepalive token source.
///
private CancellationTokenSource _keepaliveTokenSource;
///
/// Gets the keepalive token.
///
private CancellationToken KEEPALIVE_TOKEN
=> this._keepaliveTokenSource.Token;
private volatile bool _isSpeaking;
///
/// Gets the audio format used by the Opus encoder.
///
public AudioFormat AudioFormat => this._configuration.AudioFormat;
///
/// Gets whether this connection is still playing audio.
///
public bool IsPlaying
=> this._playingWait != null && !this._playingWait.Task.IsCompleted;
///
/// Gets the websocket round-trip time in ms.
///
public int WebSocketPing
=> Volatile.Read(ref this._wsPing);
private int _wsPing;
///
/// Gets the UDP round-trip time in ms.
///
public int UdpPing
=> Volatile.Read(ref this._udpPing);
private int _udpPing;
private int _queueCount;
///
/// Gets the channel this voice client is connected to.
///
public DiscordChannel TargetChannel { get; internal set; }
///
/// Initializes a new instance of the class.
///
/// The client.
/// The guild.
/// The channel.
/// The config.
/// The server.
/// The state.
internal VoiceNextConnection(DiscordClient client, DiscordGuild guild, DiscordChannel channel, VoiceNextConfiguration config, VoiceServerUpdatePayload server, VoiceStateUpdatePayload state)
{
this._discord = client;
this._guild = guild;
this.TargetChannel = channel;
this._transmittingSsrCs = new ConcurrentDictionary();
this._userSpeaking = new AsyncEvent("VNEXT_USER_SPEAKING", TimeSpan.Zero, this._discord.EventErrorHandler);
this._userJoined = new AsyncEvent("VNEXT_USER_JOINED", TimeSpan.Zero, this._discord.EventErrorHandler);
this._userLeft = new AsyncEvent("VNEXT_USER_LEFT", TimeSpan.Zero, this._discord.EventErrorHandler);
this._voiceReceived = new AsyncEvent("VNEXT_VOICE_RECEIVED", TimeSpan.Zero, this._discord.EventErrorHandler);
this._voiceSocketError = new AsyncEvent("VNEXT_WS_ERROR", TimeSpan.Zero, this._discord.EventErrorHandler);
this._tokenSource = new CancellationTokenSource();
this._configuration = config;
this._isInitialized = false;
this._isDisposed = false;
this._opus = new Opus(this.AudioFormat);
//this.Sodium = new Sodium();
this._rtp = new Rtp();
this.ServerData = server;
this.StateData = state;
var eps = this.ServerData.Endpoint;
var epi = eps.LastIndexOf(':');
var eph = string.Empty;
var epp = 443;
if (epi != -1)
{
eph = eps[..epi];
epp = int.Parse(eps[(epi + 1)..]);
}
else
{
eph = eps;
}
this.WebSocketEndpoint = new ConnectionEndpoint { Hostname = eph, Port = epp };
this._readyWait = new TaskCompletionSource();
this._playingWait = null;
this._transmitChannel = Channel.CreateBounded(new BoundedChannelOptions(this._configuration.PacketQueueSize));
this._keepaliveTimestamps = new ConcurrentDictionary();
this._pauseEvent = new AsyncManualResetEvent(true);
this._udpClient = this._discord.Configuration.UdpClientFactory();
this._voiceWs = this._discord.Configuration.WebSocketClientFactory(this._discord.Configuration.Proxy, this._discord.ServiceProvider);
this._voiceWs.Disconnected += this.VoiceWS_SocketClosed;
this._voiceWs.MessageReceived += this.VoiceWS_SocketMessage;
this._voiceWs.Connected += this.VoiceWS_SocketOpened;
this._voiceWs.ExceptionThrown += this.VoiceWs_SocketException;
}
~VoiceNextConnection()
{
this.Dispose();
}
///
/// Connects to the specified voice channel.
///
/// A task representing the connection operation.
internal Task ConnectAsync()
{
var gwuri = new UriBuilder
{
Scheme = "wss",
Host = this.WebSocketEndpoint.Hostname,
Query = "encoding=json&v=4"
};
return this._voiceWs.ConnectAsync(gwuri.Uri);
}
///
/// Reconnects .
///
/// A Task.
internal Task ReconnectAsync()
=> this._voiceWs.DisconnectAsync();
///
/// Starts .
///
/// A Task.
internal async Task StartAsync()
{
// Let's announce our intentions to the server
var vdp = new VoiceDispatch();
if (!this.Resume)
{
vdp.OpCode = 0;
vdp.Payload = new VoiceIdentifyPayload
{
ServerId = this.ServerData.GuildId,
UserId = this.StateData.UserId.Value,
SessionId = this.StateData.SessionId,
Token = this.ServerData.Token
};
this.Resume = true;
}
else
{
vdp.OpCode = 7;
vdp.Payload = new VoiceIdentifyPayload
{
ServerId = this.ServerData.GuildId,
SessionId = this.StateData.SessionId,
Token = this.ServerData.Token
};
}
var vdj = JsonConvert.SerializeObject(vdp, Formatting.None);
await this.WsSendAsync(vdj).ConfigureAwait(false);
}
///
/// Waits the for ready async.
///
/// A Task.
internal Task WaitForReadyAsync()
=> this._readyWait.Task;
///
/// Enqueues the packet async.
///
/// The packet.
/// The token.
/// A Task.
internal async Task EnqueuePacketAsync(RawVoicePacket packet, CancellationToken token = default)
{
await this._transmitChannel.Writer.WriteAsync(packet, token).ConfigureAwait(false);
this._queueCount++;
}
///
/// Prepares the packet.
///
/// The pcm.
/// The target.
/// The length.
/// A bool.
internal bool PreparePacket(ReadOnlySpan pcm, out byte[] target, out int length)
{
target = null;
length = 0;
if (this._isDisposed)
return false;
var audioFormat = this.AudioFormat;
var packetArray = ArrayPool.Shared.Rent(this._rtp.CalculatePacketSize(audioFormat.SampleCountToSampleSize(audioFormat.CalculateMaximumFrameSize()), this._selectedEncryptionMode));
var packet = packetArray.AsSpan();
this._rtp.EncodeHeader(this._sequence, this._timestamp, this._ssrc, packet);
var opus = packet.Slice(Rtp.HEADER_SIZE, pcm.Length);
this._opus.Encode(pcm, ref opus);
this._sequence++;
this._timestamp += (uint)audioFormat.CalculateFrameSize(audioFormat.CalculateSampleDuration(pcm.Length));
Span nonce = stackalloc byte[Sodium.NonceSize];
switch (this._selectedEncryptionMode)
{
case EncryptionMode.XSalsa20Poly1305:
this._sodium.GenerateNonce(packet[..Rtp.HEADER_SIZE], nonce);
break;
case EncryptionMode.XSalsa20Poly1305Suffix:
this._sodium.GenerateNonce(nonce);
break;
case EncryptionMode.XSalsa20Poly1305Lite:
this._sodium.GenerateNonce(this._nonce++, nonce);
break;
default:
ArrayPool.Shared.Return(packetArray);
throw new Exception("Unsupported encryption mode.");
}
Span encrypted = stackalloc byte[Sodium.CalculateTargetSize(opus)];
this._sodium.Encrypt(opus, encrypted, nonce);
encrypted.CopyTo(packet[Rtp.HEADER_SIZE..]);
packet = packet[..this._rtp.CalculatePacketSize(encrypted.Length, this._selectedEncryptionMode)];
this._sodium.AppendNonce(nonce, packet, this._selectedEncryptionMode);
target = packetArray;
length = packet.Length;
return true;
}
///
/// Voices the sender task.
///
/// A Task.
private async Task VoiceSenderTask()
{
var token = this.SENDER_TOKEN;
var client = this._udpClient;
var reader = this._transmitChannel.Reader;
byte[] data = null;
var length = 0;
var synchronizerTicks = (double)Stopwatch.GetTimestamp();
var synchronizerResolution = Stopwatch.Frequency * 0.005;
var tickResolution = 10_000_000.0 / Stopwatch.Frequency;
this._discord.Logger.LogDebug(VoiceNextEvents.Misc, "Timer accuracy: {0}/{1} (high resolution? {2})", Stopwatch.Frequency, synchronizerResolution, Stopwatch.IsHighResolution);
while (!token.IsCancellationRequested)
{
await this._pauseEvent.WaitAsync().ConfigureAwait(false);
var hasPacket = reader.TryRead(out var rawPacket);
if (hasPacket)
{
this._queueCount--;
if (this._playingWait == null || this._playingWait.Task.IsCompleted)
this._playingWait = new TaskCompletionSource();
}
// Provided by Laura#0090 (214796473689178133); this is Python, but adaptable:
//
// delay = max(0, self.delay + ((start_time + self.delay * loops) + - time.time()))
//
// self.delay
// sample size
// start_time
// time since streaming started
// loops
// number of samples sent
// time.time()
// DateTime.Now
if (hasPacket)
{
hasPacket = this.PreparePacket(rawPacket.Bytes.Span, out data, out length);
if (rawPacket.RentedBuffer != null)
ArrayPool.Shared.Return(rawPacket.RentedBuffer);
}
var durationModifier = hasPacket ? rawPacket.Duration / 5 : 4;
var cts = Math.Max(Stopwatch.GetTimestamp() - synchronizerTicks, 0);
if (cts < synchronizerResolution * durationModifier)
await Task.Delay(TimeSpan.FromTicks((long)(((synchronizerResolution * durationModifier) - cts) * tickResolution))).ConfigureAwait(false);
synchronizerTicks += synchronizerResolution * durationModifier;
if (!hasPacket)
continue;
await this.SendSpeakingAsync(true).ConfigureAwait(false);
await client.SendAsync(data, length).ConfigureAwait(false);
ArrayPool.Shared.Return(data);
if (!rawPacket.Silence && this._queueCount == 0)
{
var nullpcm = new byte[this.AudioFormat.CalculateSampleSize(20)];
for (var i = 0; i < 3; i++)
{
var nullpacket = new byte[nullpcm.Length];
var nullpacketmem = nullpacket.AsMemory();
await this.EnqueuePacketAsync(new RawVoicePacket(nullpacketmem, 20, true)).ConfigureAwait(false);
}
}
else if (this._queueCount == 0)
{
await this.SendSpeakingAsync(false).ConfigureAwait(false);
this._playingWait?.SetResult(true);
}
}
}
///
/// Processes the packet.
///
/// The data.
/// The opus.
/// The pcm.
/// The pcm packets.
/// The voice sender.
/// The output format.
/// A bool.
private bool ProcessPacket(ReadOnlySpan data, ref Memory opus, ref Memory pcm, IList> pcmPackets, out AudioSender voiceSender, out AudioFormat outputFormat)
{
voiceSender = null;
outputFormat = default;
if (!this._rtp.IsRtpHeader(data))
return false;
this._rtp.DecodeHeader(data, out var shortSequence, out var timestamp, out var ssrc, out var hasExtension);
if (!this._transmittingSsrCs.TryGetValue(ssrc, out var vtx))
{
var decoder = this._opus.CreateDecoder();
vtx = new AudioSender(ssrc, decoder)
{
// user isn't present as we haven't received a speaking event yet.
User = null
};
}
voiceSender = vtx;
- ulong sequence = vtx.GetTrueSequenceAfterWrapping(shortSequence);
+ var sequence = vtx.GetTrueSequenceAfterWrapping(shortSequence);
ushort gap = 0;
if (vtx.LastTrueSequence is ulong lastTrueSequence)
{
if (sequence <= lastTrueSequence) // out-of-order packet; discard
return false;
gap = (ushort)(sequence - 1 - lastTrueSequence);
if (gap >= 5)
this._discord.Logger.LogWarning(VoiceNextEvents.VoiceReceiveFailure, "5 or more voice packets were dropped when receiving");
}
Span nonce = stackalloc byte[Sodium.NonceSize];
this._sodium.GetNonce(data, nonce, this._selectedEncryptionMode);
this._rtp.GetDataFromPacket(data, out var encryptedOpus, this._selectedEncryptionMode);
var opusSize = Sodium.CalculateSourceSize(encryptedOpus);
opus = opus[..opusSize];
var opusSpan = opus.Span;
try
{
this._sodium.Decrypt(encryptedOpus, opusSpan, nonce);
// Strip extensions, if any
if (hasExtension)
{
// RFC 5285, 4.2 One-Byte header
// http://www.rfcreader.com/#rfc5285_line186
if (opusSpan[0] == 0xBE && opusSpan[1] == 0xDE)
{
var headerLen = (opusSpan[2] << 8) | opusSpan[3];
var i = 4;
for (; i < headerLen + 4; i++)
{
var @byte = opusSpan[i];
// ID is currently unused since we skip it anyway
//var id = (byte)(@byte >> 4);
var length = (byte)(@byte & 0x0F) + 1;
i += length;
}
// Strip extension padding too
while (opusSpan[i] == 0)
i++;
opusSpan = opusSpan[i..];
}
// TODO: consider implementing RFC 5285, 4.3. Two-Byte Header
}
if (opusSpan[0] == 0x90)
{
// I'm not 100% sure what this header is/does, however removing the data causes no
// real issues, and has the added benefit of removing a lot of noise.
opusSpan = opusSpan[2..];
}
if (gap == 1)
{
var lastSampleCount = this._opus.GetLastPacketSampleCount(vtx.Decoder);
var fecpcm = new byte[this.AudioFormat.SampleCountToSampleSize(lastSampleCount)];
var fecpcmMem = fecpcm.AsSpan();
this._opus.Decode(vtx.Decoder, opusSpan, ref fecpcmMem, true, out _);
pcmPackets.Add(fecpcm.AsMemory(0, fecpcmMem.Length));
}
else if (gap > 1)
{
var lastSampleCount = this._opus.GetLastPacketSampleCount(vtx.Decoder);
for (var i = 0; i < gap; i++)
{
var fecpcm = new byte[this.AudioFormat.SampleCountToSampleSize(lastSampleCount)];
var fecpcmMem = fecpcm.AsSpan();
this._opus.ProcessPacketLoss(vtx.Decoder, lastSampleCount, ref fecpcmMem);
pcmPackets.Add(fecpcm.AsMemory(0, fecpcmMem.Length));
}
}
var pcmSpan = pcm.Span;
this._opus.Decode(vtx.Decoder, opusSpan, ref pcmSpan, false, out outputFormat);
pcm = pcm[..pcmSpan.Length];
}
finally
{
vtx.LastTrueSequence = sequence;
}
return true;
}
///
/// Processes the voice packet.
///
/// The data.
/// A Task.
private async Task ProcessVoicePacket(byte[] data)
{
if (data.Length < 13) // minimum packet length
return;
try
{
var pcm = new byte[this.AudioFormat.CalculateMaximumFrameSize()];
var pcmMem = pcm.AsMemory();
var opus = new byte[pcm.Length];
var opusMem = opus.AsMemory();
var pcmFillers = new List>();
if (!this.ProcessPacket(data, ref opusMem, ref pcmMem, pcmFillers, out var vtx, out var audioFormat))
return;
foreach (var pcmFiller in pcmFillers)
await this._voiceReceived.InvokeAsync(this, new VoiceReceiveEventArgs(this._discord.ServiceProvider)
{
Ssrc = vtx.Ssrc,
User = vtx.User,
PcmData = pcmFiller,
OpusData = Array.Empty().AsMemory(),
AudioFormat = audioFormat,
AudioDuration = audioFormat.CalculateSampleDuration(pcmFiller.Length)
}).ConfigureAwait(false);
await this._voiceReceived.InvokeAsync(this, new VoiceReceiveEventArgs(this._discord.ServiceProvider)
{
Ssrc = vtx.Ssrc,
User = vtx.User,
PcmData = pcmMem,
OpusData = opusMem,
AudioFormat = audioFormat,
AudioDuration = audioFormat.CalculateSampleDuration(pcmMem.Length)
}).ConfigureAwait(false);
}
catch (Exception ex)
{
this._discord.Logger.LogError(VoiceNextEvents.VoiceReceiveFailure, ex, "Exception occurred when decoding incoming audio data");
}
}
///
/// Processes the keepalive.
///
/// The data.
private void ProcessKeepalive(byte[] data)
{
try
{
var keepalive = BinaryPrimitives.ReadUInt64LittleEndian(data);
if (!this._keepaliveTimestamps.TryRemove(keepalive, out var timestamp))
return;
var tdelta = (int)((Stopwatch.GetTimestamp() - timestamp) / (double)Stopwatch.Frequency * 1000);
this._discord.Logger.LogDebug(VoiceNextEvents.VoiceKeepalive, "Received UDP keepalive {0} (ping {1}ms)", keepalive, tdelta);
Volatile.Write(ref this._udpPing, tdelta);
}
catch (Exception ex)
{
this._discord.Logger.LogError(VoiceNextEvents.VoiceKeepalive, ex, "Exception occurred when handling keepalive");
}
}
///
/// Udps the receiver task.
///
/// A Task.
private async Task UdpReceiverTask()
{
var token = this.RECEIVER_TOKEN;
var client = this._udpClient;
while (!token.IsCancellationRequested)
{
var data = await client.ReceiveAsync().ConfigureAwait(false);
if (data.Length == 8)
this.ProcessKeepalive(data);
else if (this._configuration.EnableIncoming)
await this.ProcessVoicePacket(data).ConfigureAwait(false);
}
}
///
/// Sends a speaking status to the connected voice channel.
///
/// Whether the current user is speaking or not.
/// A task representing the sending operation.
public async Task SendSpeakingAsync(bool speaking = true)
{
if (!this._isInitialized)
throw new InvalidOperationException("The connection is not initialized");
if (this._isSpeaking != speaking)
{
this._isSpeaking = speaking;
var pld = new VoiceDispatch
{
OpCode = 5,
Payload = new VoiceSpeakingPayload
{
Speaking = speaking,
Delay = 0
}
};
var plj = JsonConvert.SerializeObject(pld, Formatting.None);
await this.WsSendAsync(plj).ConfigureAwait(false);
}
}
///
/// Gets a transmit stream for this connection, optionally specifying a packet size to use with the stream. If a stream is already configured, it will return the existing one.
///
/// Duration, in ms, to use for audio packets.
/// Transmit stream.
public VoiceTransmitSink GetTransmitSink(int sampleDuration = 20)
{
if (!AudioFormat.AllowedSampleDurations.Contains(sampleDuration))
throw new ArgumentOutOfRangeException(nameof(sampleDuration), "Invalid PCM sample duration specified.");
if (this._transmitStream == null)
this._transmitStream = new VoiceTransmitSink(this, sampleDuration);
return this._transmitStream;
}
///
/// Asynchronously waits for playback to be finished. Playback is finished when speaking = false is signaled.
///
/// A task representing the waiting operation.
public async Task WaitForPlaybackFinishAsync()
{
if (this._playingWait != null)
await this._playingWait.Task.ConfigureAwait(false);
}
///
/// Pauses playback.
///
public void Pause()
=> this._pauseEvent.Reset();
///
/// Asynchronously resumes playback.
///
///
public async Task ResumeAsync()
=> await this._pauseEvent.SetAsync().ConfigureAwait(false);
///
/// Disconnects and disposes this voice connection.
///
public void Disconnect()
=> this.Dispose();
///
/// Disconnects and disposes this voice connection.
///
public void Dispose()
{
if (this._isDisposed)
return;
try
{
this._isDisposed = true;
this._isInitialized = false;
this._tokenSource?.Cancel();
this._senderTokenSource?.Cancel();
this._receiverTokenSource?.Cancel();
}
catch (Exception ex)
{
this._discord.Logger.LogError(ex, ex.Message);
}
try
{
this._voiceWs.DisconnectAsync().ConfigureAwait(false).GetAwaiter().GetResult();
this._udpClient.Close();
}
catch { }
try
{
this._keepaliveTokenSource?.Cancel();
this._tokenSource?.Dispose();
this._senderTokenSource?.Dispose();
this._receiverTokenSource?.Dispose();
this._keepaliveTokenSource?.Dispose();
this._opus?.Dispose();
this._opus = null;
this._sodium?.Dispose();
this._sodium = null;
this._rtp?.Dispose();
this._rtp = null;
}
catch (Exception ex)
{
this._discord.Logger.LogError(ex, ex.Message);
}
this.VoiceDisconnected?.Invoke(this._guild);
GC.SuppressFinalize(this);
}
///
/// Heartbeats .
///
/// A Task.
private async Task HeartbeatAsync()
{
await Task.Yield();
var token = this.TOKEN;
while (true)
{
try
{
token.ThrowIfCancellationRequested();
var dt = DateTime.Now;
this._discord.Logger.LogTrace(VoiceNextEvents.VoiceHeartbeat, "Sent heartbeat");
var hbd = new VoiceDispatch
{
OpCode = 3,
Payload = UnixTimestamp(dt)
};
var hbj = JsonConvert.SerializeObject(hbd);
await this.WsSendAsync(hbj).ConfigureAwait(false);
this._lastHeartbeat = dt;
await Task.Delay(this._heartbeatInterval).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
return;
}
}
}
///
/// Keepalives .
///
/// A Task.
private async Task KeepaliveAsync()
{
await Task.Yield();
var token = this.KEEPALIVE_TOKEN;
var client = this._udpClient;
while (!token.IsCancellationRequested)
{
var timestamp = Stopwatch.GetTimestamp();
var keepalive = Volatile.Read(ref this._lastKeepalive);
Volatile.Write(ref this._lastKeepalive, keepalive + 1);
this._keepaliveTimestamps.TryAdd(keepalive, timestamp);
var packet = new byte[8];
BinaryPrimitives.WriteUInt64LittleEndian(packet, keepalive);
await client.SendAsync(packet, packet.Length).ConfigureAwait(false);
await Task.Delay(5000, token).ConfigureAwait(false);
}
}
///
/// Stage1S .
///
/// The voice ready.
/// A Task.
private async Task Stage1(VoiceReadyPayload voiceReady)
{
// IP Discovery
this._udpClient.Setup(this.UdpEndpoint);
var pck = new byte[70];
PreparePacket(pck);
await this._udpClient.SendAsync(pck, pck.Length).ConfigureAwait(false);
var ipd = await this._udpClient.ReceiveAsync().ConfigureAwait(false);
ReadPacket(ipd, out var ip, out var port);
this._discoveredEndpoint = new IpEndpoint
{
Address = ip,
Port = port
};
this._discord.Logger.LogTrace(VoiceNextEvents.VoiceHandshake, "Endpoint discovery finished - discovered endpoint is {0}:{1}", ip, port);
void PreparePacket(byte[] packet)
{
var ssrc = this._ssrc;
var packetSpan = packet.AsSpan();
MemoryMarshal.Write(packetSpan, ref ssrc);
Helpers.ZeroFill(packetSpan);
}
void ReadPacket(byte[] packet, out System.Net.IPAddress decodedIp, out ushort decodedPort)
{
var packetSpan = packet.AsSpan();
var ipString = Utilities.UTF8.GetString(packet, 4, 64 /* 70 - 6 */).TrimEnd('\0');
decodedIp = System.Net.IPAddress.Parse(ipString);
decodedPort = BinaryPrimitives.ReadUInt16LittleEndian(packetSpan[68 /* 70 - 2 */..]);
}
// Select voice encryption mode
var selectedEncryptionMode = Sodium.SelectMode(voiceReady.Modes);
this._selectedEncryptionMode = selectedEncryptionMode.Value;
// Ready
this._discord.Logger.LogTrace(VoiceNextEvents.VoiceHandshake, "Selected encryption mode is {0}", selectedEncryptionMode.Key);
var vsp = new VoiceDispatch
{
OpCode = 1,
Payload = new VoiceSelectProtocolPayload
{
Protocol = "udp",
Data = new VoiceSelectProtocolPayloadData
{
Address = this._discoveredEndpoint.Address.ToString(),
Port = (ushort)this._discoveredEndpoint.Port,
Mode = selectedEncryptionMode.Key
}
}
};
var vsj = JsonConvert.SerializeObject(vsp, Formatting.None);
await this.WsSendAsync(vsj).ConfigureAwait(false);
this._senderTokenSource = new CancellationTokenSource();
this._senderTask = Task.Run(this.VoiceSenderTask, this.SENDER_TOKEN);
this._receiverTokenSource = new CancellationTokenSource();
this._receiverTask = Task.Run(this.UdpReceiverTask, this.RECEIVER_TOKEN);
}
///
/// Stage2S .
///
/// The voice session description.
/// A Task.
private async Task Stage2(VoiceSessionDescriptionPayload voiceSessionDescription)
{
this._selectedEncryptionMode = Sodium.SupportedModes[voiceSessionDescription.Mode.ToLowerInvariant()];
this._discord.Logger.LogTrace(VoiceNextEvents.VoiceHandshake, "Discord updated encryption mode - new mode is {0}", this._selectedEncryptionMode);
// start keepalive
this._keepaliveTokenSource = new CancellationTokenSource();
this._keepaliveTask = this.KeepaliveAsync();
// send 3 packets of silence to get things going
var nullpcm = new byte[this.AudioFormat.CalculateSampleSize(20)];
for (var i = 0; i < 3; i++)
{
var nullPcm = new byte[nullpcm.Length];
var nullpacketmem = nullPcm.AsMemory();
await this.EnqueuePacketAsync(new RawVoicePacket(nullpacketmem, 20, true)).ConfigureAwait(false);
}
this._isInitialized = true;
this._readyWait.SetResult(true);
}
///
/// Handles the dispatch.
///
/// The jo.
/// A Task.
private async Task HandleDispatch(JObject jo)
{
var opc = (int)jo["op"];
var opp = jo["d"] as JObject;
switch (opc)
{
case 2: // READY
this._discord.Logger.LogTrace(VoiceNextEvents.VoiceDispatch, "Received READY (OP2)");
var vrp = opp.ToObject();
this._ssrc = vrp.Ssrc;
this.UdpEndpoint = new ConnectionEndpoint(vrp.Address, vrp.Port);
// this is not the valid interval
// oh, discord
//this.HeartbeatInterval = vrp.HeartbeatInterval;
this._heartbeatTask = Task.Run(this.HeartbeatAsync);
await this.Stage1(vrp).ConfigureAwait(false);
break;
case 4: // SESSION_DESCRIPTION
this._discord.Logger.LogTrace(VoiceNextEvents.VoiceDispatch, "Received SESSION_DESCRIPTION (OP4)");
var vsd = opp.ToObject();
this._key = vsd.SecretKey;
this._sodium = new Sodium(this._key.AsMemory());
await this.Stage2(vsd).ConfigureAwait(false);
break;
case 5: // SPEAKING
// Don't spam OP5
// No longer spam, Discord supposedly doesn't send many of these
this._discord.Logger.LogTrace(VoiceNextEvents.VoiceDispatch, "Received SPEAKING (OP5)");
var spd = opp.ToObject();
var foundUserInCache = this._discord.TryGetCachedUserInternal(spd.UserId.Value, out var resolvedUser);
var spk = new UserSpeakingEventArgs(this._discord.ServiceProvider)
{
Speaking = spd.Speaking,
Ssrc = spd.Ssrc.Value,
User = resolvedUser,
};
if (foundUserInCache && this._transmittingSsrCs.TryGetValue(spk.Ssrc, out var txssrc5) && txssrc5.Id == 0)
{
txssrc5.User = spk.User;
}
else
{
var opus = this._opus.CreateDecoder();
var vtx = new AudioSender(spk.Ssrc, opus)
{
User = await this._discord.GetUserAsync(spd.UserId.Value).ConfigureAwait(false)
};
if (!this._transmittingSsrCs.TryAdd(spk.Ssrc, vtx))
this._opus.DestroyDecoder(opus);
}
await this._userSpeaking.InvokeAsync(this, spk).ConfigureAwait(false);
break;
case 6: // HEARTBEAT ACK
var dt = DateTime.Now;
var ping = (int)(dt - this._lastHeartbeat).TotalMilliseconds;
Volatile.Write(ref this._wsPing, ping);
this._discord.Logger.LogTrace(VoiceNextEvents.VoiceDispatch, "Received HEARTBEAT_ACK (OP6, {0}ms)", ping);
this._lastHeartbeat = dt;
break;
case 8: // HELLO
// this sends a heartbeat interval that we need to use for heartbeating
this._discord.Logger.LogTrace(VoiceNextEvents.VoiceDispatch, "Received HELLO (OP8)");
this._heartbeatInterval = opp["heartbeat_interval"].ToObject();
break;
case 9: // RESUMED
this._discord.Logger.LogTrace(VoiceNextEvents.VoiceDispatch, "Received RESUMED (OP9)");
this._heartbeatTask = Task.Run(this.HeartbeatAsync);
break;
case 12: // CLIENT_CONNECTED
this._discord.Logger.LogTrace(VoiceNextEvents.VoiceDispatch, "Received CLIENT_CONNECTED (OP12)");
var ujpd = opp.ToObject();
var usrj = await this._discord.GetUserAsync(ujpd.UserId).ConfigureAwait(false);
{
var opus = this._opus.CreateDecoder();
var vtx = new AudioSender(ujpd.Ssrc, opus)
{
User = usrj
};
if (!this._transmittingSsrCs.TryAdd(vtx.Ssrc, vtx))
this._opus.DestroyDecoder(opus);
}
await this._userJoined.InvokeAsync(this, new VoiceUserJoinEventArgs(this._discord.ServiceProvider) { User = usrj, Ssrc = ujpd.Ssrc }).ConfigureAwait(false);
break;
case 13: // CLIENT_DISCONNECTED
this._discord.Logger.LogTrace(VoiceNextEvents.VoiceDispatch, "Received CLIENT_DISCONNECTED (OP13)");
var ulpd = opp.ToObject();
var txssrc = this._transmittingSsrCs.FirstOrDefault(x => x.Value.Id == ulpd.UserId);
if (this._transmittingSsrCs.ContainsKey(txssrc.Key))
{
this._transmittingSsrCs.TryRemove(txssrc.Key, out var txssrc13);
this._opus.DestroyDecoder(txssrc13.Decoder);
}
var usrl = await this._discord.GetUserAsync(ulpd.UserId).ConfigureAwait(false);
await this._userLeft.InvokeAsync(this, new VoiceUserLeaveEventArgs(this._discord.ServiceProvider)
{
User = usrl,
Ssrc = txssrc.Key
}).ConfigureAwait(false);
break;
default:
this._discord.Logger.LogTrace(VoiceNextEvents.VoiceDispatch, "Received unknown voice opcode (OP{0})", opc);
break;
}
}
///
/// Voices the w s_ socket closed.
///
/// The client.
/// The e.
/// A Task.
private async Task VoiceWS_SocketClosed(IWebSocketClient client, SocketCloseEventArgs e)
{
this._discord.Logger.LogDebug(VoiceNextEvents.VoiceConnectionClose, "Voice WebSocket closed ({0}, '{1}')", e.CloseCode, e.CloseMessage);
// generally this should not be disposed on all disconnects, only on requested ones
// or something
// otherwise problems happen
//this.Dispose();
if (e.CloseCode == 4006 || e.CloseCode == 4009)
this.Resume = false;
if (!this._isDisposed)
{
this._tokenSource.Cancel();
this._tokenSource = new CancellationTokenSource();
this._voiceWs = this._discord.Configuration.WebSocketClientFactory(this._discord.Configuration.Proxy, this._discord.ServiceProvider);
this._voiceWs.Disconnected += this.VoiceWS_SocketClosed;
this._voiceWs.MessageReceived += this.VoiceWS_SocketMessage;
this._voiceWs.Connected += this.VoiceWS_SocketOpened;
if (this.Resume) // emzi you dipshit
await this.ConnectAsync().ConfigureAwait(false);
}
}
///
/// Voices the w s_ socket message.
///
/// The client.
/// The e.
/// A Task.
private Task VoiceWS_SocketMessage(IWebSocketClient client, SocketMessageEventArgs e)
{
if (e is not SocketTextMessageEventArgs et)
{
this._discord.Logger.LogCritical(VoiceNextEvents.VoiceGatewayError, "Discord Voice Gateway sent binary data - unable to process");
return Task.CompletedTask;
}
this._discord.Logger.LogTrace(VoiceNextEvents.VoiceWsRx, et.Message);
return this.HandleDispatch(JObject.Parse(et.Message));
}
///
/// Voices the w s_ socket opened.
///
/// The client.
/// The e.
/// A Task.
private Task VoiceWS_SocketOpened(IWebSocketClient client, SocketEventArgs e)
=> this.StartAsync();
///
/// Voices the ws_ socket exception.
///
/// The client.
/// The e.
/// A Task.
private Task VoiceWs_SocketException(IWebSocketClient client, SocketErrorEventArgs e)
=> this._voiceSocketError.InvokeAsync(this, new SocketErrorEventArgs(this._discord.ServiceProvider) { Exception = e.Exception });
///
/// Ws the send async.
///
/// The payload.
/// A Task.
private async Task WsSendAsync(string payload)
{
this._discord.Logger.LogTrace(VoiceNextEvents.VoiceWsTx, payload);
await this._voiceWs.SendMessageAsync(payload).ConfigureAwait(false);
}
///
/// Gets the unix timestamp.
///
/// The datetime.
private static uint UnixTimestamp(DateTime dt)
{
var ts = dt - s_unixEpoch;
var sd = ts.TotalSeconds;
var si = (uint)sd;
return si;
}
}
}
diff --git a/DisCatSharp.VoiceNext/global.json b/DisCatSharp.VoiceNext/global.json
new file mode 100644
index 000000000..90a51a911
--- /dev/null
+++ b/DisCatSharp.VoiceNext/global.json
@@ -0,0 +1,6 @@
+{
+ "sdk": {
+ "version": "6.0.202",
+ "rollForward": "major"
+ }
+}
diff --git a/DisCatSharp.targets b/DisCatSharp.targets
index b063ad919..bf2cc22bc 100644
--- a/DisCatSharp.targets
+++ b/DisCatSharp.targets
@@ -1,12 +1,28 @@
-
- Lala Sabathil, Lunar, Auros, Geferon, J_M_Lutra, Alice, AITSYS contributors
- Aiko IT Systems
- False
- https://github.com/Aiko-IT-Systems/DisCatSharp
- https://github.com/Aiko-IT-Systems/DisCatSharp
- Git
- https://raw.githubusercontent.com/Aiko-IT-Systems/DisCatSharp/main/DisCatSharp.Logos/logobig.png
-
+
+ Aiko-IT-Systems, Lala Sabathil, J_M_Lutra , Xorog, Badger2-3, aiko, AITSYS Contributors
+ Aiko IT Systems
+ False
+ https://github.com/Aiko-IT-Systems/DisCatSharp
+ https://github.com/Aiko-IT-Systems/DisCatSharp
+ Git
+ logobig.png
+
+ LICENSE.md
+ True
+
+
+
+
+ True
+
+
+
+
+
+ True
+
+
+
diff --git a/DisCatSharp/Clients/DiscordClient.Dispatch.cs b/DisCatSharp/Clients/DiscordClient.Dispatch.cs
index c0f1743b8..fa011cff3 100644
--- a/DisCatSharp/Clients/DiscordClient.Dispatch.cs
+++ b/DisCatSharp/Clients/DiscordClient.Dispatch.cs
@@ -1,3412 +1,3412 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using DisCatSharp.Common;
using DisCatSharp.Entities;
using DisCatSharp.Enums;
using DisCatSharp.EventArgs;
using DisCatSharp.Exceptions;
using DisCatSharp.Net.Abstractions;
using DisCatSharp.Net.Serialization;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
namespace DisCatSharp
{
///
/// Represents a discord client.
///
public sealed partial class DiscordClient
{
#region Private Fields
private string _sessionId;
private bool _guildDownloadCompleted;
private readonly Dictionary> _tempTimers = new();
///
/// Represents a timeout handler.
///
internal class TimeoutHandler
{
///
/// Gets the member.
///
internal readonly DiscordMember Member;
///
/// Gets the guild.
///
internal readonly DiscordGuild Guild;
///
/// Gets the old timeout value.
///
internal DateTime? TimeoutUntilOld;
///
/// Gets the new timeout value.
///
internal DateTime? TimeoutUntilNew;
///
/// Constructs a new .
///
/// The affected member.
/// The affected guild.
/// The old timeout value.
/// The new timeout value.
internal TimeoutHandler(DiscordMember mbr, DiscordGuild guild, DateTime? too, DateTime? ton)
{
this.Guild = guild;
this.Member = mbr;
this.TimeoutUntilOld = too;
this.TimeoutUntilNew = ton;
}
}
#endregion
#region Dispatch Handler
///
/// Handles the dispatch payloads.
///
/// The payload.
internal async Task HandleDispatchAsync(GatewayPayload payload)
{
if (payload.Data is not JObject dat)
{
this.Logger.LogWarning(LoggerEvents.WebSocketReceive, "Invalid payload body (this message is probably safe to ignore); opcode: {0} event: {1}; payload: {2}", payload.OpCode, payload.EventName, payload.Data);
return;
}
await this._payloadReceived.InvokeAsync(this, new PayloadReceivedEventArgs(this.ServiceProvider)
{
EventName = payload.EventName,
PayloadObject = dat
}).ConfigureAwait(false);
#region Default objects
DiscordChannel chn;
ulong gid;
ulong cid;
ulong uid;
DiscordStageInstance stg = default;
DiscordIntegration itg = default;
DiscordThreadChannel trd = default;
DiscordThreadChannelMember trdm = default;
DiscordScheduledEvent gse = default;
TransportUser usr = default;
TransportMember mbr = default;
TransportUser refUsr = default;
TransportMember refMbr = default;
JToken rawMbr = default;
var rawRefMsg = dat["referenced_message"];
#endregion
switch (payload.EventName.ToLowerInvariant())
{
#region Gateway Status
case "ready":
var glds = (JArray)dat["guilds"];
await this.OnReadyEventAsync(dat.ToObject(), glds).ConfigureAwait(false);
break;
case "resumed":
await this.OnResumedAsync().ConfigureAwait(false);
break;
#endregion
#region Channel
case "channel_create":
chn = dat.ToObject();
await this.OnChannelCreateEventAsync(chn).ConfigureAwait(false);
break;
case "channel_update":
await this.OnChannelUpdateEventAsync(dat.ToObject()).ConfigureAwait(false);
break;
case "channel_delete":
chn = dat.ToObject();
await this.OnChannelDeleteEventAsync(chn.IsPrivate ? dat.ToObject() : chn).ConfigureAwait(false);
break;
case "channel_pins_update":
cid = (ulong)dat["channel_id"];
var ts = (string)dat["last_pin_timestamp"];
await this.OnChannelPinsUpdateAsync((ulong?)dat["guild_id"], cid, ts != null ? DateTimeOffset.Parse(ts, CultureInfo.InvariantCulture) : default(DateTimeOffset?)).ConfigureAwait(false);
break;
#endregion
#region Guild
case "guild_create":
await this.OnGuildCreateEventAsync(dat.ToDiscordObject(), (JArray)dat["members"], dat["presences"].ToDiscordObject>()).ConfigureAwait(false);
break;
case "guild_update":
await this.OnGuildUpdateEventAsync(dat.ToDiscordObject(), (JArray)dat["members"]).ConfigureAwait(false);
break;
case "guild_delete":
await this.OnGuildDeleteEventAsync(dat.ToDiscordObject()).ConfigureAwait(false);
break;
case "guild_sync":
gid = (ulong)dat["id"];
await this.OnGuildSyncEventAsync(this.GuildsInternal[gid], (bool)dat["large"], (JArray)dat["members"], dat["presences"].ToDiscordObject>()).ConfigureAwait(false);
break;
case "guild_emojis_update":
gid = (ulong)dat["guild_id"];
var ems = dat["emojis"].ToObject>();
await this.OnGuildEmojisUpdateEventAsync(this.GuildsInternal[gid], ems).ConfigureAwait(false);
break;
case "guild_stickers_update":
gid = (ulong)dat["guild_id"];
var strs = dat["stickers"].ToDiscordObject>();
await this.OnStickersUpdatedAsync(strs, gid).ConfigureAwait(false);
break;
case "guild_integrations_update":
gid = (ulong)dat["guild_id"];
// discord fires this event inconsistently if the current user leaves a guild.
if (!this.GuildsInternal.ContainsKey(gid))
return;
await this.OnGuildIntegrationsUpdateEventAsync(this.GuildsInternal[gid]).ConfigureAwait(false);
break;
case "guild_join_request_create":
break;
case "guild_join_request_update":
break;
case "guild_join_request_delete":
break;
#endregion
#region Guild Ban
case "guild_ban_add":
usr = dat["user"].ToObject();
gid = (ulong)dat["guild_id"];
await this.OnGuildBanAddEventAsync(usr, this.GuildsInternal[gid]).ConfigureAwait(false);
break;
case "guild_ban_remove":
usr = dat["user"].ToObject();
gid = (ulong)dat["guild_id"];
await this.OnGuildBanRemoveEventAsync(usr, this.GuildsInternal[gid]).ConfigureAwait(false);
break;
#endregion
#region Guild Event
case "guild_scheduled_event_create":
gse = dat.ToObject();
gid = (ulong)dat["guild_id"];
await this.OnGuildScheduledEventCreateEventAsync(gse, this.GuildsInternal[gid]).ConfigureAwait(false);
break;
case "guild_scheduled_event_update":
gse = dat.ToObject();
gid = (ulong)dat["guild_id"];
await this.OnGuildScheduledEventUpdateEventAsync(gse, this.GuildsInternal[gid]).ConfigureAwait(false);
break;
case "guild_scheduled_event_delete":
gse = dat.ToObject();
gid = (ulong)dat["guild_id"];
await this.OnGuildScheduledEventDeleteEventAsync(gse, this.GuildsInternal[gid]).ConfigureAwait(false);
break;
case "guild_scheduled_event_user_add":
gid = (ulong)dat["guild_id"];
uid = (ulong)dat["user_id"];
await this.OnGuildScheduledEventUserAddedEventAsync((ulong)dat["guild_scheduled_event_id"], uid, this.GuildsInternal[gid]).ConfigureAwait(false);
break;
case "guild_scheduled_event_user_remove":
gid = (ulong)dat["guild_id"];
uid = (ulong)dat["user_id"];
await this.OnGuildScheduledEventUserRemovedEventAsync((ulong)dat["guild_scheduled_event_id"], uid, this.GuildsInternal[gid]).ConfigureAwait(false);
break;
#endregion
#region Guild Integration
case "integration_create":
gid = (ulong)dat["guild_id"];
itg = dat.ToObject();
// discord fires this event inconsistently if the current user leaves a guild.
if (!this.GuildsInternal.ContainsKey(gid))
return;
await this.OnGuildIntegrationCreateEventAsync(this.GuildsInternal[gid], itg).ConfigureAwait(false);
break;
case "integration_update":
gid = (ulong)dat["guild_id"];
itg = dat.ToObject();
// discord fires this event inconsistently if the current user leaves a guild.
if (!this.GuildsInternal.ContainsKey(gid))
return;
await this.OnGuildIntegrationUpdateEventAsync(this.GuildsInternal[gid], itg).ConfigureAwait(false);
break;
case "integration_delete":
gid = (ulong)dat["guild_id"];
// discord fires this event inconsistently if the current user leaves a guild.
if (!this.GuildsInternal.ContainsKey(gid))
return;
await this.OnGuildIntegrationDeleteEventAsync(this.GuildsInternal[gid], (ulong)dat["id"], (ulong?)dat["application_id"]).ConfigureAwait(false);
break;
#endregion
#region Guild Member
case "guild_member_add":
gid = (ulong)dat["guild_id"];
await this.OnGuildMemberAddEventAsync(dat.ToObject(), this.GuildsInternal[gid]).ConfigureAwait(false);
break;
case "guild_member_remove":
gid = (ulong)dat["guild_id"];
usr = dat["user"].ToObject();
if (!this.GuildsInternal.ContainsKey(gid))
{
// discord fires this event inconsistently if the current user leaves a guild.
if (usr.Id != this.CurrentUser.Id)
this.Logger.LogError(LoggerEvents.WebSocketReceive, "Could not find {0} in guild cache", gid);
return;
}
await this.OnGuildMemberRemoveEventAsync(usr, this.GuildsInternal[gid]).ConfigureAwait(false);
break;
case "guild_member_update":
gid = (ulong)dat["guild_id"];
await this.OnGuildMemberUpdateEventAsync(dat.ToDiscordObject(), this.GuildsInternal[gid], dat["roles"].ToObject>(), (string)dat["nick"], (bool?)dat["pending"]).ConfigureAwait(false);
break;
case "guild_members_chunk":
await this.OnGuildMembersChunkEventAsync(dat).ConfigureAwait(false);
break;
#endregion
#region Guild Role
case "guild_role_create":
gid = (ulong)dat["guild_id"];
await this.OnGuildRoleCreateEventAsync(dat["role"].ToObject(), this.GuildsInternal[gid]).ConfigureAwait(false);
break;
case "guild_role_update":
gid = (ulong)dat["guild_id"];
await this.OnGuildRoleUpdateEventAsync(dat["role"].ToObject(), this.GuildsInternal[gid]).ConfigureAwait(false);
break;
case "guild_role_delete":
gid = (ulong)dat["guild_id"];
await this.OnGuildRoleDeleteEventAsync((ulong)dat["role_id"], this.GuildsInternal[gid]).ConfigureAwait(false);
break;
#endregion
#region Invite
case "invite_create":
gid = (ulong)dat["guild_id"];
cid = (ulong)dat["channel_id"];
await this.OnInviteCreateEventAsync(cid, gid, dat.ToObject()).ConfigureAwait(false);
break;
case "invite_delete":
gid = (ulong)dat["guild_id"];
cid = (ulong)dat["channel_id"];
await this.OnInviteDeleteEventAsync(cid, gid, dat).ConfigureAwait(false);
break;
#endregion
#region Message
case "message_ack":
cid = (ulong)dat["channel_id"];
var mid = (ulong)dat["message_id"];
await this.OnMessageAckEventAsync(this.InternalGetCachedChannel(cid), mid).ConfigureAwait(false);
break;
case "message_create":
rawMbr = dat["member"];
if (rawMbr != null)
mbr = rawMbr.ToObject();
if (rawRefMsg != null && rawRefMsg.HasValues)
{
if (rawRefMsg.SelectToken("author") != null)
{
refUsr = rawRefMsg.SelectToken("author").ToObject();
}
if (rawRefMsg.SelectToken("member") != null)
{
refMbr = rawRefMsg.SelectToken("member").ToObject();
}
}
await this.OnMessageCreateEventAsync(dat.ToDiscordObject(), dat["author"].ToObject(), mbr, refUsr, refMbr).ConfigureAwait(false);
break;
case "message_update":
rawMbr = dat["member"];
if (rawMbr != null)
mbr = rawMbr.ToObject();
if (rawRefMsg != null && rawRefMsg.HasValues)
{
if (rawRefMsg.SelectToken("author") != null)
{
refUsr = rawRefMsg.SelectToken("author").ToObject();
}
if (rawRefMsg.SelectToken("member") != null)
{
refMbr = rawRefMsg.SelectToken("member").ToObject();
}
}
await this.OnMessageUpdateEventAsync(dat.ToDiscordObject(), dat["author"]?.ToObject(), mbr, refUsr, refMbr).ConfigureAwait(false);
break;
// delete event does *not* include message object
case "message_delete":
await this.OnMessageDeleteEventAsync((ulong)dat["id"], (ulong)dat["channel_id"], (ulong?)dat["guild_id"]).ConfigureAwait(false);
break;
case "message_delete_bulk":
await this.OnMessageBulkDeleteEventAsync(dat["ids"].ToObject(), (ulong)dat["channel_id"], (ulong?)dat["guild_id"]).ConfigureAwait(false);
break;
#endregion
#region Message Reaction
case "message_reaction_add":
rawMbr = dat["member"];
if (rawMbr != null)
mbr = rawMbr.ToObject();
await this.OnMessageReactionAddAsync((ulong)dat["user_id"], (ulong)dat["message_id"], (ulong)dat["channel_id"], (ulong?)dat["guild_id"], mbr, dat["emoji"].ToObject()).ConfigureAwait(false);
break;
case "message_reaction_remove":
await this.OnMessageReactionRemoveAsync((ulong)dat["user_id"], (ulong)dat["message_id"], (ulong)dat["channel_id"], (ulong?)dat["guild_id"], dat["emoji"].ToObject()).ConfigureAwait(false);
break;
case "message_reaction_remove_all":
await this.OnMessageReactionRemoveAllAsync((ulong)dat["message_id"], (ulong)dat["channel_id"], (ulong?)dat["guild_id"]).ConfigureAwait(false);
break;
case "message_reaction_remove_emoji":
await this.OnMessageReactionRemoveEmojiAsync((ulong)dat["message_id"], (ulong)dat["channel_id"], (ulong)dat["guild_id"], dat["emoji"]).ConfigureAwait(false);
break;
#endregion
#region Stage Instance
case "stage_instance_create":
stg = dat.ToObject();
await this.OnStageInstanceCreateEventAsync(stg).ConfigureAwait(false);
break;
case "stage_instance_update":
stg = dat.ToObject();
await this.OnStageInstanceUpdateEventAsync(stg).ConfigureAwait(false);
break;
case "stage_instance_delete":
stg = dat.ToObject();
await this.OnStageInstanceDeleteEventAsync(stg).ConfigureAwait(false);
break;
#endregion
#region Thread
case "thread_create":
trd = dat.ToObject();
await this.OnThreadCreateEventAsync(trd).ConfigureAwait(false);
break;
case "thread_update":
trd = dat.ToObject();
await this.OnThreadUpdateEventAsync(trd).ConfigureAwait(false);
break;
case "thread_delete":
trd = dat.ToObject();
await this.OnThreadDeleteEventAsync(trd).ConfigureAwait(false);
break;
case "thread_list_sync":
gid = (ulong)dat["guild_id"]; //get guild
await this.OnThreadListSyncEventAsync(this.GuildsInternal[gid], dat["channel_ids"].ToObject>(), dat["threads"].ToObject>(), dat["members"].ToObject>()).ConfigureAwait(false);
break;
case "thread_member_update":
trdm = dat.ToObject();
await this.OnThreadMemberUpdateEventAsync(trdm).ConfigureAwait(false);
break;
case "thread_members_update":
gid = (ulong)dat["guild_id"];
await this.OnThreadMembersUpdateEventAsync(this.GuildsInternal[gid], (ulong)dat["id"], (JArray)dat["added_members"], (JArray)dat["removed_member_ids"], (int)dat["member_count"]).ConfigureAwait(false);
break;
#endregion
#region Activities
case "embedded_activity_update":
gid = (ulong)dat["guild_id"];
cid = (ulong)dat["channel_id"];
await this.OnEmbeddedActivityUpdateAsync((JObject)dat["embedded_activity"], this.GuildsInternal[gid], cid, (JArray)dat["users"], (ulong)dat["embedded_activity"]["application_id"]).ConfigureAwait(false);
break;
#endregion
#region User/Presence Update
case "presence_update":
await this.OnPresenceUpdateEventAsync(dat, (JObject)dat["user"]).ConfigureAwait(false);
break;
case "user_settings_update":
await this.OnUserSettingsUpdateEventAsync(dat.ToObject()).ConfigureAwait(false);
break;
case "user_update":
await this.OnUserUpdateEventAsync(dat.ToObject()).ConfigureAwait(false);
break;
#endregion
#region Voice
case "voice_state_update":
await this.OnVoiceStateUpdateEventAsync(dat).ConfigureAwait(false);
break;
case "voice_server_update":
gid = (ulong)dat["guild_id"];
await this.OnVoiceServerUpdateEventAsync((string)dat["endpoint"], (string)dat["token"], this.GuildsInternal[gid]).ConfigureAwait(false);
break;
#endregion
#region Interaction/Integration/Application
case "interaction_create":
rawMbr = dat["member"];
if (rawMbr != null)
{
mbr = dat["member"].ToObject();
usr = mbr.User;
}
else
{
usr = dat["user"].ToObject();
}
cid = (ulong)dat["channel_id"];
await this.OnInteractionCreateAsync((ulong?)dat["guild_id"], cid, usr, mbr, dat.ToDiscordObject()).ConfigureAwait(false);
break;
case "application_command_create":
await this.OnApplicationCommandCreateAsync(dat.ToObject(), (ulong?)dat["guild_id"]).ConfigureAwait(false);
break;
case "application_command_update":
await this.OnApplicationCommandUpdateAsync(dat.ToObject(), (ulong?)dat["guild_id"]).ConfigureAwait(false);
break;
case "application_command_delete":
await this.OnApplicationCommandDeleteAsync(dat.ToObject(), (ulong?)dat["guild_id"]).ConfigureAwait(false);
break;
case "guild_application_command_counts_update":
var counts = dat["application_command_counts"];
await this.OnGuildApplicationCommandCountsUpdateAsync((int)counts["1"], (int)counts["2"], (int)counts["3"], (ulong)dat["guild_id"]).ConfigureAwait(false);
break;
case "guild_application_command_index_update":
// TODO: Implement.
break;
case "application_command_permissions_update":
var aid = (ulong)dat["application_id"];
if (aid != this.CurrentApplication.Id)
return;
var pms = dat["permissions"].ToObject>();
gid = (ulong)dat["guild_id"];
await this.OnApplicationCommandPermissionsUpdateAsync(pms, (ulong)dat["id"], gid, aid).ConfigureAwait(false);
break;
#endregion
#region Misc
case "gift_code_update": //Not supposed to be dispatched to bots
break;
case "typing_start":
cid = (ulong)dat["channel_id"];
rawMbr = dat["member"];
if (rawMbr != null)
mbr = rawMbr.ToObject();
await this.OnTypingStartEventAsync((ulong)dat["user_id"], cid, this.InternalGetCachedChannel(cid), (ulong?)dat["guild_id"], Utilities.GetDateTimeOffset((long)dat["timestamp"]), mbr).ConfigureAwait(false);
break;
case "webhooks_update":
gid = (ulong)dat["guild_id"];
cid = (ulong)dat["channel_id"];
await this.OnWebhooksUpdateAsync(this.GuildsInternal[gid].GetChannel(cid), this.GuildsInternal[gid]).ConfigureAwait(false);
break;
default:
await this.OnUnknownEventAsync(payload).ConfigureAwait(false);
this.Logger.LogWarning(LoggerEvents.WebSocketReceive, "Unknown event: {0}\npayload: {1}", payload.EventName, payload.Data);
break;
#endregion
}
}
#endregion
#region Events
#region Gateway
///
/// Handles the ready event.
///
/// The ready payload.
/// The raw guilds.
internal async Task OnReadyEventAsync(ReadyPayload ready, JArray rawGuilds)
{
//ready.CurrentUser.Discord = this;
var rusr = ready.CurrentUser;
this.CurrentUser.Username = rusr.Username;
this.CurrentUser.Discriminator = rusr.Discriminator;
this.CurrentUser.AvatarHash = rusr.AvatarHash;
this.CurrentUser.MfaEnabled = rusr.MfaEnabled;
this.CurrentUser.Verified = rusr.Verified;
this.CurrentUser.IsBot = rusr.IsBot;
this.CurrentUser.Flags = rusr.Flags;
this.GatewayVersion = ready.GatewayVersion;
this._sessionId = ready.SessionId;
var rawGuildIndex = rawGuilds.ToDictionary(xt => (ulong)xt["id"], xt => (JObject)xt);
this.GuildsInternal.Clear();
foreach (var guild in ready.Guilds)
{
guild.Discord = this;
if (guild.ChannelsInternal == null)
guild.ChannelsInternal = new ConcurrentDictionary();
foreach (var xc in guild.Channels.Values)
{
xc.GuildId = guild.Id;
xc.Discord = this;
foreach (var xo in xc.PermissionOverwritesInternal)
{
xo.Discord = this;
xo.ChannelId = xc.Id;
}
}
if (guild.RolesInternal == null)
guild.RolesInternal = new ConcurrentDictionary();
foreach (var xr in guild.Roles.Values)
{
xr.Discord = this;
xr.GuildId = guild.Id;
}
var rawGuild = rawGuildIndex[guild.Id];
var rawMembers = (JArray)rawGuild["members"];
if (guild.MembersInternal != null)
guild.MembersInternal.Clear();
else
guild.MembersInternal = new ConcurrentDictionary();
if (rawMembers != null)
{
foreach (var xj in rawMembers)
{
var xtm = xj.ToObject();
var xu = new DiscordUser(xtm.User) { Discord = this };
xu = this.UserCache.AddOrUpdate(xtm.User.Id, xu, (id, old) =>
{
old.Username = xu.Username;
old.Discriminator = xu.Discriminator;
old.AvatarHash = xu.AvatarHash;
return old;
});
guild.MembersInternal[xtm.User.Id] = new DiscordMember(xtm) { Discord = this, GuildId = guild.Id };
}
}
if (guild.EmojisInternal == null)
guild.EmojisInternal = new ConcurrentDictionary();
foreach (var xe in guild.Emojis.Values)
xe.Discord = this;
if (guild.StickersInternal == null)
guild.StickersInternal = new ConcurrentDictionary();
foreach (var xs in guild.Stickers.Values)
xs.Discord = this;
if (guild.VoiceStatesInternal == null)
guild.VoiceStatesInternal = new ConcurrentDictionary();
foreach (var xvs in guild.VoiceStates.Values)
xvs.Discord = this;
if (guild.ThreadsInternal == null)
guild.ThreadsInternal = new ConcurrentDictionary();
foreach (var xt in guild.ThreadsInternal.Values)
xt.Discord = this;
if (guild.StageInstancesInternal == null)
guild.StageInstancesInternal = new ConcurrentDictionary();
foreach (var xsi in guild.StageInstancesInternal.Values)
xsi.Discord = this;
if (guild.ScheduledEventsInternal == null)
guild.ScheduledEventsInternal = new ConcurrentDictionary();
foreach (var xse in guild.ScheduledEventsInternal.Values)
xse.Discord = this;
this.GuildsInternal[guild.Id] = guild;
}
await this._ready.InvokeAsync(this, new ReadyEventArgs(this.ServiceProvider)).ConfigureAwait(false);
}
///
/// Handles the resumed event.
///
internal Task OnResumedAsync()
{
this.Logger.LogInformation(LoggerEvents.SessionUpdate, "Session resumed");
return this._resumed.InvokeAsync(this, new ReadyEventArgs(this.ServiceProvider));
}
#endregion
#region Channel
///
/// Handles the channel create event.
///
/// The channel.
internal async Task OnChannelCreateEventAsync(DiscordChannel channel)
{
channel.Discord = this;
foreach (var xo in channel.PermissionOverwritesInternal)
{
xo.Discord = this;
xo.ChannelId = channel.Id;
}
this.GuildsInternal[channel.GuildId.Value].ChannelsInternal[channel.Id] = channel;
/*if (this.Configuration.AutoRefreshChannelCache)
{
await this.RefreshChannelsAsync(channel.Guild.Id);
}*/
await this._channelCreated.InvokeAsync(this, new ChannelCreateEventArgs(this.ServiceProvider) { Channel = channel, Guild = channel.Guild }).ConfigureAwait(false);
}
///
/// Handles the channel update event.
///
/// The channel.
internal async Task OnChannelUpdateEventAsync(DiscordChannel channel)
{
if (channel == null)
return;
channel.Discord = this;
var gld = channel.Guild;
var channelNew = this.InternalGetCachedChannel(channel.Id);
DiscordChannel channelOld = null;
if (channelNew != null)
{
channelOld = new DiscordChannel
{
Bitrate = channelNew.Bitrate,
Discord = this,
GuildId = channelNew.GuildId,
Id = channelNew.Id,
LastMessageId = channelNew.LastMessageId,
Name = channelNew.Name,
PermissionOverwritesInternal = new List(channelNew.PermissionOverwritesInternal),
Position = channelNew.Position,
Topic = channelNew.Topic,
Type = channelNew.Type,
UserLimit = channelNew.UserLimit,
ParentId = channelNew.ParentId,
IsNsfw = channelNew.IsNsfw,
PerUserRateLimit = channelNew.PerUserRateLimit,
RtcRegionId = channelNew.RtcRegionId,
QualityMode = channelNew.QualityMode,
DefaultAutoArchiveDuration = channelNew.DefaultAutoArchiveDuration
};
channelNew.Bitrate = channel.Bitrate;
channelNew.Name = channel.Name;
channelNew.Position = channel.Position;
channelNew.Topic = channel.Topic;
channelNew.UserLimit = channel.UserLimit;
channelNew.ParentId = channel.ParentId;
channelNew.IsNsfw = channel.IsNsfw;
channelNew.PerUserRateLimit = channel.PerUserRateLimit;
channelNew.Type = channel.Type;
channelNew.RtcRegionId = channel.RtcRegionId;
channelNew.QualityMode = channel.QualityMode;
channelNew.DefaultAutoArchiveDuration = channel.DefaultAutoArchiveDuration;
channelNew.PermissionOverwritesInternal.Clear();
foreach (var po in channel.PermissionOverwritesInternal)
{
po.Discord = this;
po.ChannelId = channel.Id;
}
channelNew.PermissionOverwritesInternal.AddRange(channel.PermissionOverwritesInternal);
if (this.Configuration.AutoRefreshChannelCache && gld != null)
{
await this.RefreshChannelsAsync(channel.Guild.Id);
}
}
else if (gld != null)
{
gld.ChannelsInternal[channel.Id] = channel;
if (this.Configuration.AutoRefreshChannelCache)
{
await this.RefreshChannelsAsync(channel.Guild.Id);
}
}
await this._channelUpdated.InvokeAsync(this, new ChannelUpdateEventArgs(this.ServiceProvider) { ChannelAfter = channelNew, Guild = gld, ChannelBefore = channelOld }).ConfigureAwait(false);
}
///
/// Handles the channel delete event.
///
/// The channel.
internal async Task OnChannelDeleteEventAsync(DiscordChannel channel)
{
if (channel == null)
return;
channel.Discord = this;
//if (channel.IsPrivate)
if (channel.Type == ChannelType.Group || channel.Type == ChannelType.Private)
{
var dmChannel = channel as DiscordDmChannel;
await this._dmChannelDeleted.InvokeAsync(this, new DmChannelDeleteEventArgs(this.ServiceProvider) { Channel = dmChannel }).ConfigureAwait(false);
}
else
{
var gld = channel.Guild;
if (gld.ChannelsInternal.TryRemove(channel.Id, out var cachedChannel)) channel = cachedChannel;
if (this.Configuration.AutoRefreshChannelCache)
{
await this.RefreshChannelsAsync(channel.Guild.Id);
}
await this._channelDeleted.InvokeAsync(this, new ChannelDeleteEventArgs(this.ServiceProvider) { Channel = channel, Guild = gld }).ConfigureAwait(false);
}
}
///
/// Refreshes the channels.
///
/// The guild id.
internal async Task RefreshChannelsAsync(ulong guildId)
{
var guild = this.InternalGetCachedGuild(guildId);
var channels = await this.ApiClient.GetGuildChannelsAsync(guildId);
guild.ChannelsInternal.Clear();
foreach (var channel in channels.ToList())
{
channel.Discord = this;
foreach (var xo in channel.PermissionOverwritesInternal)
{
xo.Discord = this;
xo.ChannelId = channel.Id;
}
guild.ChannelsInternal[channel.Id] = channel;
}
}
///
/// Handles the channel pins update event.
///
/// The optional guild id.
/// The channel id.
/// The optional last pin timestamp.
internal async Task OnChannelPinsUpdateAsync(ulong? guildId, ulong channelId, DateTimeOffset? lastPinTimestamp)
{
var guild = this.InternalGetCachedGuild(guildId);
var channel = this.InternalGetCachedChannel(channelId) ?? this.InternalGetCachedThread(channelId);
var ea = new ChannelPinsUpdateEventArgs(this.ServiceProvider)
{
Guild = guild,
Channel = channel,
LastPinTimestamp = lastPinTimestamp
};
await this._channelPinsUpdated.InvokeAsync(this, ea).ConfigureAwait(false);
}
#endregion
#region Guild
///
/// Handles the guild create event.
///
/// The guild.
/// The raw members.
/// The presences.
internal async Task OnGuildCreateEventAsync(DiscordGuild guild, JArray rawMembers, IEnumerable presences)
{
if (presences != null)
{
foreach (var xp in presences)
{
xp.Discord = this;
xp.GuildId = guild.Id;
xp.Activity = new DiscordActivity(xp.RawActivity);
if (xp.RawActivities != null)
{
xp.InternalActivities = xp.RawActivities
.Select(x => new DiscordActivity(x)).ToArray();
}
this.PresencesInternal[xp.InternalUser.Id] = xp;
}
}
var exists = this.GuildsInternal.TryGetValue(guild.Id, out var foundGuild);
guild.Discord = this;
guild.IsUnavailable = false;
var eventGuild = guild;
if (exists)
guild = foundGuild;
if (guild.ChannelsInternal == null)
guild.ChannelsInternal = new ConcurrentDictionary();
if (guild.ThreadsInternal == null)
guild.ThreadsInternal = new ConcurrentDictionary();
if (guild.RolesInternal == null)
guild.RolesInternal = new ConcurrentDictionary();
if (guild.ThreadsInternal == null)
guild.ThreadsInternal = new ConcurrentDictionary();
if (guild.StickersInternal == null)
guild.StickersInternal = new ConcurrentDictionary();
if (guild.EmojisInternal == null)
guild.EmojisInternal = new ConcurrentDictionary();
if (guild.VoiceStatesInternal == null)
guild.VoiceStatesInternal = new ConcurrentDictionary();
if (guild.MembersInternal == null)
guild.MembersInternal = new ConcurrentDictionary();
if (guild.ScheduledEventsInternal == null)
guild.ScheduledEventsInternal = new ConcurrentDictionary();
this.UpdateCachedGuild(eventGuild, rawMembers);
guild.JoinedAt = eventGuild.JoinedAt;
guild.IsLarge = eventGuild.IsLarge;
guild.MemberCount = Math.Max(eventGuild.MemberCount, guild.MembersInternal.Count);
guild.IsUnavailable = eventGuild.IsUnavailable;
guild.PremiumSubscriptionCount = eventGuild.PremiumSubscriptionCount;
guild.PremiumTier = eventGuild.PremiumTier;
guild.BannerHash = eventGuild.BannerHash;
guild.VanityUrlCode = eventGuild.VanityUrlCode;
guild.Description = eventGuild.Description;
guild.IsNsfw = eventGuild.IsNsfw;
foreach (var kvp in eventGuild.VoiceStatesInternal) guild.VoiceStatesInternal[kvp.Key] = kvp.Value;
foreach (var kvp in eventGuild.ChannelsInternal) guild.ChannelsInternal[kvp.Key] = kvp.Value;
foreach (var kvp in eventGuild.RolesInternal) guild.RolesInternal[kvp.Key] = kvp.Value;
foreach (var kvp in eventGuild.EmojisInternal) guild.EmojisInternal[kvp.Key] = kvp.Value;
foreach (var kvp in eventGuild.ThreadsInternal) guild.ThreadsInternal[kvp.Key] = kvp.Value;
foreach (var kvp in eventGuild.StickersInternal) guild.StickersInternal[kvp.Key] = kvp.Value;
foreach (var kvp in eventGuild.StageInstancesInternal) guild.StageInstancesInternal[kvp.Key] = kvp.Value;
foreach (var kvp in eventGuild.ScheduledEventsInternal) guild.ScheduledEventsInternal[kvp.Key] = kvp.Value;
foreach (var xc in guild.ChannelsInternal.Values)
{
xc.GuildId = guild.Id;
xc.Discord = this;
foreach (var xo in xc.PermissionOverwritesInternal)
{
xo.Discord = this;
xo.ChannelId = xc.Id;
}
}
foreach (var xt in guild.ThreadsInternal.Values)
{
xt.GuildId = guild.Id;
xt.Discord = this;
}
foreach (var xe in guild.EmojisInternal.Values)
xe.Discord = this;
foreach (var xs in guild.StickersInternal.Values)
xs.Discord = this;
foreach (var xvs in guild.VoiceStatesInternal.Values)
xvs.Discord = this;
foreach (var xsi in guild.StageInstancesInternal.Values)
{
xsi.Discord = this;
xsi.GuildId = guild.Id;
}
foreach (var xr in guild.RolesInternal.Values)
{
xr.Discord = this;
xr.GuildId = guild.Id;
}
foreach (var xse in guild.ScheduledEventsInternal.Values)
{
xse.Discord = this;
xse.GuildId = guild.Id;
if (xse.Creator != null)
xse.Creator.Discord = this;
}
var old = Volatile.Read(ref this._guildDownloadCompleted);
var dcompl = this.GuildsInternal.Values.All(xg => !xg.IsUnavailable);
Volatile.Write(ref this._guildDownloadCompleted, dcompl);
if (exists)
await this._guildAvailable.InvokeAsync(this, new GuildCreateEventArgs(this.ServiceProvider) { Guild = guild }).ConfigureAwait(false);
else
await this._guildCreated.InvokeAsync(this, new GuildCreateEventArgs(this.ServiceProvider) { Guild = guild }).ConfigureAwait(false);
if (dcompl && !old)
await this._guildDownloadCompletedEv.InvokeAsync(this, new GuildDownloadCompletedEventArgs(this.Guilds, this.ServiceProvider)).ConfigureAwait(false);
}
///
/// Handles the guild update event.
///
/// The guild.
/// The raw members.
internal async Task OnGuildUpdateEventAsync(DiscordGuild guild, JArray rawMembers)
{
DiscordGuild oldGuild;
if (!this.GuildsInternal.ContainsKey(guild.Id))
{
this.GuildsInternal[guild.Id] = guild;
oldGuild = null;
}
else
{
var gld = this.GuildsInternal[guild.Id];
oldGuild = new DiscordGuild
{
Discord = gld.Discord,
Name = gld.Name,
AfkChannelId = gld.AfkChannelId,
AfkTimeout = gld.AfkTimeout,
ApplicationId = gld.ApplicationId,
DefaultMessageNotifications = gld.DefaultMessageNotifications,
ExplicitContentFilter = gld.ExplicitContentFilter,
RawFeatures = gld.RawFeatures,
IconHash = gld.IconHash,
Id = gld.Id,
IsLarge = gld.IsLarge,
IsSynced = gld.IsSynced,
IsUnavailable = gld.IsUnavailable,
JoinedAt = gld.JoinedAt,
MemberCount = gld.MemberCount,
MaxMembers = gld.MaxMembers,
MaxPresences = gld.MaxPresences,
ApproximateMemberCount = gld.ApproximateMemberCount,
ApproximatePresenceCount = gld.ApproximatePresenceCount,
MaxVideoChannelUsers = gld.MaxVideoChannelUsers,
DiscoverySplashHash = gld.DiscoverySplashHash,
PreferredLocale = gld.PreferredLocale,
MfaLevel = gld.MfaLevel,
OwnerId = gld.OwnerId,
SplashHash = gld.SplashHash,
SystemChannelId = gld.SystemChannelId,
SystemChannelFlags = gld.SystemChannelFlags,
Description = gld.Description,
WidgetEnabled = gld.WidgetEnabled,
WidgetChannelId = gld.WidgetChannelId,
VerificationLevel = gld.VerificationLevel,
RulesChannelId = gld.RulesChannelId,
PublicUpdatesChannelId = gld.PublicUpdatesChannelId,
VoiceRegionId = gld.VoiceRegionId,
IsNsfw = gld.IsNsfw,
PremiumProgressBarEnabled = gld.PremiumProgressBarEnabled,
PremiumSubscriptionCount = gld.PremiumSubscriptionCount,
PremiumTier = gld.PremiumTier,
ChannelsInternal = new ConcurrentDictionary(),
ThreadsInternal = new ConcurrentDictionary(),
EmojisInternal = new ConcurrentDictionary(),
StickersInternal = new ConcurrentDictionary(),
MembersInternal = new ConcurrentDictionary(),
RolesInternal = new ConcurrentDictionary(),
StageInstancesInternal = new ConcurrentDictionary(),
VoiceStatesInternal = new ConcurrentDictionary(),
ScheduledEventsInternal = new ConcurrentDictionary()
};
foreach (var kvp in gld.ChannelsInternal) oldGuild.ChannelsInternal[kvp.Key] = kvp.Value;
foreach (var kvp in gld.ThreadsInternal) oldGuild.ThreadsInternal[kvp.Key] = kvp.Value;
foreach (var kvp in gld.EmojisInternal) oldGuild.EmojisInternal[kvp.Key] = kvp.Value;
foreach (var kvp in gld.StickersInternal) oldGuild.StickersInternal[kvp.Key] = kvp.Value;
foreach (var kvp in gld.RolesInternal) oldGuild.RolesInternal[kvp.Key] = kvp.Value;
foreach (var kvp in gld.VoiceStatesInternal) oldGuild.VoiceStatesInternal[kvp.Key] = kvp.Value;
foreach (var kvp in gld.MembersInternal) oldGuild.MembersInternal[kvp.Key] = kvp.Value;
foreach (var kvp in gld.StageInstancesInternal) oldGuild.StageInstancesInternal[kvp.Key] = kvp.Value;
foreach (var kvp in gld.ScheduledEventsInternal) oldGuild.ScheduledEventsInternal[kvp.Key] = kvp.Value;
}
guild.Discord = this;
guild.IsUnavailable = false;
var eventGuild = guild;
guild = this.GuildsInternal[eventGuild.Id];
if (guild.ChannelsInternal == null)
guild.ChannelsInternal = new ConcurrentDictionary();
if (guild.ThreadsInternal == null)
guild.ThreadsInternal = new ConcurrentDictionary();
if (guild.RolesInternal == null)
guild.RolesInternal = new ConcurrentDictionary();
if (guild.EmojisInternal == null)
guild.EmojisInternal = new ConcurrentDictionary();
if (guild.StickersInternal == null)
guild.StickersInternal = new ConcurrentDictionary();
if (guild.VoiceStatesInternal == null)
guild.VoiceStatesInternal = new ConcurrentDictionary();
if (guild.StageInstancesInternal == null)
guild.StageInstancesInternal = new ConcurrentDictionary();
if (guild.MembersInternal == null)
guild.MembersInternal = new ConcurrentDictionary();
if (guild.ScheduledEventsInternal == null)
guild.ScheduledEventsInternal = new ConcurrentDictionary();
this.UpdateCachedGuild(eventGuild, rawMembers);
foreach (var xc in guild.ChannelsInternal.Values)
{
xc.GuildId = guild.Id;
xc.Discord = this;
foreach (var xo in xc.PermissionOverwritesInternal)
{
xo.Discord = this;
xo.ChannelId = xc.Id;
}
}
foreach (var xc in guild.ThreadsInternal.Values)
{
xc.GuildId = guild.Id;
xc.Discord = this;
}
foreach (var xe in guild.EmojisInternal.Values)
xe.Discord = this;
foreach (var xs in guild.StickersInternal.Values)
xs.Discord = this;
foreach (var xvs in guild.VoiceStatesInternal.Values)
xvs.Discord = this;
foreach (var xr in guild.RolesInternal.Values)
{
xr.Discord = this;
xr.GuildId = guild.Id;
}
foreach (var xsi in guild.StageInstancesInternal.Values)
{
xsi.Discord = this;
xsi.GuildId = guild.Id;
}
foreach (var xse in guild.ScheduledEventsInternal.Values)
{
xse.Discord = this;
xse.GuildId = guild.Id;
if (xse.Creator != null)
xse.Creator.Discord = this;
}
await this._guildUpdated.InvokeAsync(this, new GuildUpdateEventArgs(this.ServiceProvider) { GuildBefore = oldGuild, GuildAfter = guild }).ConfigureAwait(false);
}
///
/// Handles the guild delete event.
///
/// The guild.
internal async Task OnGuildDeleteEventAsync(DiscordGuild guild)
{
if (guild.IsUnavailable)
{
if (!this.GuildsInternal.TryGetValue(guild.Id, out var gld))
return;
gld.IsUnavailable = true;
await this._guildUnavailable.InvokeAsync(this, new GuildDeleteEventArgs(this.ServiceProvider) { Guild = guild, Unavailable = true }).ConfigureAwait(false);
}
else
{
if (!this.GuildsInternal.TryRemove(guild.Id, out var gld))
return;
await this._guildDeleted.InvokeAsync(this, new GuildDeleteEventArgs(this.ServiceProvider) { Guild = gld }).ConfigureAwait(false);
}
}
///
/// Handles the guild sync event.
///
/// The guild.
/// Whether the guild is a large guild..
/// The raw members.
/// The presences.
internal async Task OnGuildSyncEventAsync(DiscordGuild guild, bool isLarge, JArray rawMembers, IEnumerable presences)
{
presences = presences.Select(xp => { xp.Discord = this; xp.Activity = new DiscordActivity(xp.RawActivity); return xp; });
foreach (var xp in presences)
this.PresencesInternal[xp.InternalUser.Id] = xp;
guild.IsSynced = true;
guild.IsLarge = isLarge;
this.UpdateCachedGuild(guild, rawMembers);
await this._guildAvailable.InvokeAsync(this, new GuildCreateEventArgs(this.ServiceProvider) { Guild = guild }).ConfigureAwait(false);
}
///
/// Handles the guild emojis update event.
///
/// The guild.
/// The new emojis.
internal async Task OnGuildEmojisUpdateEventAsync(DiscordGuild guild, IEnumerable newEmojis)
{
var oldEmojis = new ConcurrentDictionary(guild.EmojisInternal);
guild.EmojisInternal.Clear();
foreach (var emoji in newEmojis)
{
emoji.Discord = this;
guild.EmojisInternal[emoji.Id] = emoji;
}
var ea = new GuildEmojisUpdateEventArgs(this.ServiceProvider)
{
Guild = guild,
EmojisAfter = guild.Emojis,
EmojisBefore = new ReadOnlyConcurrentDictionary(oldEmojis)
};
await this._guildEmojisUpdated.InvokeAsync(this, ea).ConfigureAwait(false);
}
///
/// Handles the stickers updated.
///
/// The new stickers.
/// The guild id.
internal async Task OnStickersUpdatedAsync(IEnumerable newStickers, ulong guildId)
{
var guild = this.InternalGetCachedGuild(guildId);
var oldStickers = new ConcurrentDictionary(guild.StickersInternal);
guild.StickersInternal.Clear();
foreach (var nst in newStickers)
{
if (nst.User is not null)
{
nst.User.Discord = this;
this.UserCache.AddOrUpdate(nst.User.Id, nst.User, (old, @new) => @new);
}
nst.Discord = this;
guild.StickersInternal[nst.Id] = nst;
}
var sea = new GuildStickersUpdateEventArgs(this.ServiceProvider)
{
Guild = guild,
StickersBefore = oldStickers,
StickersAfter = guild.Stickers
};
await this._guildStickersUpdated.InvokeAsync(this, sea).ConfigureAwait(false);
}
#endregion
#region Guild Ban
///
/// Handles the guild ban add event.
///
/// The transport user.
/// The guild.
internal async Task OnGuildBanAddEventAsync(TransportUser user, DiscordGuild guild)
{
var usr = new DiscordUser(user) { Discord = this };
usr = this.UserCache.AddOrUpdate(user.Id, usr, (id, old) =>
{
old.Username = usr.Username;
old.Discriminator = usr.Discriminator;
old.AvatarHash = usr.AvatarHash;
return old;
});
if (!guild.Members.TryGetValue(user.Id, out var mbr))
mbr = new DiscordMember(usr) { Discord = this, GuildId = guild.Id };
var ea = new GuildBanAddEventArgs(this.ServiceProvider)
{
Guild = guild,
Member = mbr
};
await this._guildBanAdded.InvokeAsync(this, ea).ConfigureAwait(false);
}
///
/// Handles the guild ban remove event.
///
/// The transport user.
/// The guild.
internal async Task OnGuildBanRemoveEventAsync(TransportUser user, DiscordGuild guild)
{
var usr = new DiscordUser(user) { Discord = this };
usr = this.UserCache.AddOrUpdate(user.Id, usr, (id, old) =>
{
old.Username = usr.Username;
old.Discriminator = usr.Discriminator;
old.AvatarHash = usr.AvatarHash;
return old;
});
if (!guild.Members.TryGetValue(user.Id, out var mbr))
mbr = new DiscordMember(usr) { Discord = this, GuildId = guild.Id };
var ea = new GuildBanRemoveEventArgs(this.ServiceProvider)
{
Guild = guild,
Member = mbr
};
await this._guildBanRemoved.InvokeAsync(this, ea).ConfigureAwait(false);
}
#endregion
#region Guild Scheduled Event
///
/// Handles the scheduled event create event.
///
/// The created event.
/// The guild.
internal async Task OnGuildScheduledEventCreateEventAsync(DiscordScheduledEvent scheduledEvent, DiscordGuild guild)
{
scheduledEvent.Discord = this;
guild.ScheduledEventsInternal.AddOrUpdate(scheduledEvent.Id, scheduledEvent, (old, newScheduledEvent) => newScheduledEvent);
if (scheduledEvent.Creator != null)
{
scheduledEvent.Creator.Discord = this;
this.UserCache.AddOrUpdate(scheduledEvent.Creator.Id, scheduledEvent.Creator, (id, old) =>
{
old.Username = scheduledEvent.Creator.Username;
old.Discriminator = scheduledEvent.Creator.Discriminator;
old.AvatarHash = scheduledEvent.Creator.AvatarHash;
old.Flags = scheduledEvent.Creator.Flags;
return old;
});
}
await this._guildScheduledEventCreated.InvokeAsync(this, new GuildScheduledEventCreateEventArgs(this.ServiceProvider) { ScheduledEvent = scheduledEvent, Guild = scheduledEvent.Guild }).ConfigureAwait(false);
}
///
/// Handles the scheduled event update event.
///
/// The updated event.
/// The guild.
internal async Task OnGuildScheduledEventUpdateEventAsync(DiscordScheduledEvent scheduledEvent, DiscordGuild guild)
{
if (guild == null)
return;
DiscordScheduledEvent oldEvent;
if (!guild.ScheduledEventsInternal.ContainsKey(scheduledEvent.Id))
{
oldEvent = null;
}
else
{
var ev = guild.ScheduledEventsInternal[scheduledEvent.Id];
oldEvent = new DiscordScheduledEvent
{
Id = ev.Id,
ChannelId = ev.ChannelId,
EntityId = ev.EntityId,
EntityMetadata = ev.EntityMetadata,
CreatorId = ev.CreatorId,
Creator = ev.Creator,
Discord = this,
Description = ev.Description,
EntityType = ev.EntityType,
ScheduledStartTimeRaw = ev.ScheduledStartTimeRaw,
ScheduledEndTimeRaw = ev.ScheduledEndTimeRaw,
GuildId = ev.GuildId,
Status = ev.Status,
Name = ev.Name,
UserCount = ev.UserCount,
CoverImageHash = ev.CoverImageHash
};
}
if (scheduledEvent.Creator != null)
{
scheduledEvent.Creator.Discord = this;
this.UserCache.AddOrUpdate(scheduledEvent.Creator.Id, scheduledEvent.Creator, (id, old) =>
{
old.Username = scheduledEvent.Creator.Username;
old.Discriminator = scheduledEvent.Creator.Discriminator;
old.AvatarHash = scheduledEvent.Creator.AvatarHash;
old.Flags = scheduledEvent.Creator.Flags;
return old;
});
}
if (scheduledEvent.Status == ScheduledEventStatus.Completed)
{
guild.ScheduledEventsInternal.TryRemove(scheduledEvent.Id, out var deletedEvent);
await this._guildScheduledEventDeleted.InvokeAsync(this, new GuildScheduledEventDeleteEventArgs(this.ServiceProvider) { ScheduledEvent = scheduledEvent, Guild = guild, Reason = ScheduledEventStatus.Completed }).ConfigureAwait(false);
}
else if (scheduledEvent.Status == ScheduledEventStatus.Canceled)
{
guild.ScheduledEventsInternal.TryRemove(scheduledEvent.Id, out var deletedEvent);
scheduledEvent.Status = ScheduledEventStatus.Canceled;
await this._guildScheduledEventDeleted.InvokeAsync(this, new GuildScheduledEventDeleteEventArgs(this.ServiceProvider) { ScheduledEvent = scheduledEvent, Guild = guild, Reason = ScheduledEventStatus.Canceled }).ConfigureAwait(false);
}
else
{
this.UpdateScheduledEvent(scheduledEvent, guild);
await this._guildScheduledEventUpdated.InvokeAsync(this, new GuildScheduledEventUpdateEventArgs(this.ServiceProvider) { ScheduledEventBefore = oldEvent, ScheduledEventAfter = scheduledEvent, Guild = guild }).ConfigureAwait(false);
}
}
///
/// Handles the scheduled event delete event.
///
/// The deleted event.
/// The guild.
internal async Task OnGuildScheduledEventDeleteEventAsync(DiscordScheduledEvent scheduledEvent, DiscordGuild guild)
{
scheduledEvent.Discord = this;
if (scheduledEvent.Status == ScheduledEventStatus.Scheduled)
scheduledEvent.Status = ScheduledEventStatus.Canceled;
if (scheduledEvent.Creator != null)
{
scheduledEvent.Creator.Discord = this;
this.UserCache.AddOrUpdate(scheduledEvent.Creator.Id, scheduledEvent.Creator, (id, old) =>
{
old.Username = scheduledEvent.Creator.Username;
old.Discriminator = scheduledEvent.Creator.Discriminator;
old.AvatarHash = scheduledEvent.Creator.AvatarHash;
old.Flags = scheduledEvent.Creator.Flags;
return old;
});
}
await this._guildScheduledEventDeleted.InvokeAsync(this, new GuildScheduledEventDeleteEventArgs(this.ServiceProvider) { ScheduledEvent = scheduledEvent, Guild = scheduledEvent.Guild, Reason = scheduledEvent.Status }).ConfigureAwait(false);
guild.ScheduledEventsInternal.TryRemove(scheduledEvent.Id, out var deletedEvent);
}
///
/// Handles the scheduled event user add event.
/// The event.
/// The added user id.
/// The guild.
///
internal async Task OnGuildScheduledEventUserAddedEventAsync(ulong guildScheduledEventId, ulong userId, DiscordGuild guild)
{
var scheduledEvent = this.InternalGetCachedScheduledEvent(guildScheduledEventId) ?? this.UpdateScheduledEvent(new DiscordScheduledEvent
{
Id = guildScheduledEventId,
GuildId = guild.Id,
Discord = this,
UserCount = 0
}, guild);
scheduledEvent.UserCount++;
scheduledEvent.Discord = this;
guild.Discord = this;
var user = this.GetUserAsync(userId, true).Result;
user.Discord = this;
var member = guild.Members.TryGetValue(userId, out var mem) ? mem : guild.GetMemberAsync(userId).Result;
member.Discord = this;
await this._guildScheduledEventUserAdded.InvokeAsync(this, new GuildScheduledEventUserAddEventArgs(this.ServiceProvider) { ScheduledEvent = scheduledEvent, Guild = guild, User = user, Member = member }).ConfigureAwait(false);
}
///
/// Handles the scheduled event user remove event.
/// The event.
/// The removed user id.
/// The guild.
///
internal async Task OnGuildScheduledEventUserRemovedEventAsync(ulong guildScheduledEventId, ulong userId, DiscordGuild guild)
{
var scheduledEvent = this.InternalGetCachedScheduledEvent(guildScheduledEventId) ?? this.UpdateScheduledEvent(new DiscordScheduledEvent
{
Id = guildScheduledEventId,
GuildId = guild.Id,
Discord = this,
UserCount = 0
}, guild);
scheduledEvent.UserCount = scheduledEvent.UserCount == 0 ? 0 : scheduledEvent.UserCount - 1;
scheduledEvent.Discord = this;
guild.Discord = this;
var user = this.GetUserAsync(userId, true).Result;
user.Discord = this;
var member = guild.Members.TryGetValue(userId, out var mem) ? mem : guild.GetMemberAsync(userId).Result;
member.Discord = this;
await this._guildScheduledEventUserRemoved.InvokeAsync(this, new GuildScheduledEventUserRemoveEventArgs(this.ServiceProvider) { ScheduledEvent = scheduledEvent, Guild = guild, User = user, Member = member }).ConfigureAwait(false);
}
#endregion
#region Guild Integration
///
/// Handles the guild integration create event.
///
/// The guild.
/// The integration.
internal async Task OnGuildIntegrationCreateEventAsync(DiscordGuild guild, DiscordIntegration integration)
{
integration.Discord = this;
await this._guildIntegrationCreated.InvokeAsync(this, new GuildIntegrationCreateEventArgs(this.ServiceProvider) { Integration = integration, Guild = guild }).ConfigureAwait(false);
}
///
/// Handles the guild integration update event.
///
/// The guild.
/// The integration.
internal async Task OnGuildIntegrationUpdateEventAsync(DiscordGuild guild, DiscordIntegration integration)
{
integration.Discord = this;
await this._guildIntegrationUpdated.InvokeAsync(this, new GuildIntegrationUpdateEventArgs(this.ServiceProvider) { Integration = integration, Guild = guild }).ConfigureAwait(false);
}
///
/// Handles the guild integrations update event.
///
/// The guild.
internal async Task OnGuildIntegrationsUpdateEventAsync(DiscordGuild guild)
{
var ea = new GuildIntegrationsUpdateEventArgs(this.ServiceProvider)
{
Guild = guild
};
await this._guildIntegrationsUpdated.InvokeAsync(this, ea).ConfigureAwait(false);
}
///
/// Handles the guild integration delete event.
///
/// The guild.
/// The integration id.
/// The optional application id.
internal async Task OnGuildIntegrationDeleteEventAsync(DiscordGuild guild, ulong integrationId, ulong? applicationId)
=> await this._guildIntegrationDeleted.InvokeAsync(this, new GuildIntegrationDeleteEventArgs(this.ServiceProvider) { Guild = guild, IntegrationId = integrationId, ApplicationId = applicationId }).ConfigureAwait(false);
#endregion
#region Guild Member
///
/// Handles the guild member add event.
///
/// The transport member.
/// The guild.
internal async Task OnGuildMemberAddEventAsync(TransportMember member, DiscordGuild guild)
{
var usr = new DiscordUser(member.User) { Discord = this };
usr = this.UserCache.AddOrUpdate(member.User.Id, usr, (id, old) =>
{
old.Username = usr.Username;
old.Discriminator = usr.Discriminator;
old.AvatarHash = usr.AvatarHash;
return old;
});
var mbr = new DiscordMember(member)
{
Discord = this,
GuildId = guild.Id
};
guild.MembersInternal[mbr.Id] = mbr;
guild.MemberCount++;
var ea = new GuildMemberAddEventArgs(this.ServiceProvider)
{
Guild = guild,
Member = mbr
};
await this._guildMemberAdded.InvokeAsync(this, ea).ConfigureAwait(false);
}
///
/// Handles the guild member remove event.
///
/// The transport user.
/// The guild.
internal async Task OnGuildMemberRemoveEventAsync(TransportUser user, DiscordGuild guild)
{
var usr = new DiscordUser(user);
if (!guild.MembersInternal.TryRemove(user.Id, out var mbr))
mbr = new DiscordMember(usr) { Discord = this, GuildId = guild.Id };
guild.MemberCount--;
_ = this.UserCache.AddOrUpdate(user.Id, usr, (old, @new) => @new);
var ea = new GuildMemberRemoveEventArgs(this.ServiceProvider)
{
Guild = guild,
Member = mbr
};
await this._guildMemberRemoved.InvokeAsync(this, ea).ConfigureAwait(false);
}
///
/// Handles the guild member update event.
///
/// The transport member.
/// The guild.
/// The roles.
/// The nick.
/// Whether the member is pending.
internal async Task OnGuildMemberUpdateEventAsync(TransportMember member, DiscordGuild guild, IEnumerable roles, string nick, bool? pending)
{
var usr = new DiscordUser(member.User) { Discord = this };
usr = this.UserCache.AddOrUpdate(usr.Id, usr, (id, old) =>
{
old.Username = usr.Username;
old.Discriminator = usr.Discriminator;
old.AvatarHash = usr.AvatarHash;
return old;
});
if (!guild.Members.TryGetValue(member.User.Id, out var mbr))
mbr = new DiscordMember(usr) { Discord = this, GuildId = guild.Id };
var old = mbr;
var gAvOld = old.GuildAvatarHash;
var avOld = old.AvatarHash;
var nickOld = mbr.Nickname;
var pendingOld = mbr.IsPending;
var rolesOld = new ReadOnlyCollection(new List(mbr.Roles));
var cduOld = mbr.CommunicationDisabledUntil;
mbr.MemberFlags = member.MemberFlags;
mbr.AvatarHashInternal = member.AvatarHash;
mbr.GuildAvatarHash = member.GuildAvatarHash;
mbr.Nickname = nick;
mbr.IsPending = pending;
mbr.CommunicationDisabledUntil = member.CommunicationDisabledUntil;
mbr.RoleIdsInternal.Clear();
mbr.RoleIdsInternal.AddRange(roles);
guild.MembersInternal.AddOrUpdate(member.User.Id, mbr, (id, oldMbr) => oldMbr);
var timeoutUntil = member.CommunicationDisabledUntil;
/*this.Logger.LogTrace($"Timeout:\nBefore - {cduOld}\nAfter - {timeoutUntil}");
if ((timeoutUntil.HasValue && cduOld.HasValue) || (timeoutUntil == null && cduOld.HasValue) || (timeoutUntil.HasValue && cduOld == null))
{
// We are going to add a scheduled timer to assure that we get a auditlog entry.
var id = $"tt-{mbr.Id}-{guild.Id}-{DateTime.Now.ToLongTimeString()}";
this._tempTimers.Add(
id,
new(
new TimeoutHandler(
mbr,
guild,
cduOld,
timeoutUntil
),
new Timer(
this.TimeoutTimer,
id,
2000,
Timeout.Infinite
)
)
);
this.Logger.LogTrace("Scheduling timeout event.");
return;
}*/
//this.Logger.LogTrace("No timeout detected. Continuing on normal operation.");
var eargs = new GuildMemberUpdateEventArgs(this.ServiceProvider)
{
Guild = guild,
Member = mbr,
NicknameAfter = mbr.Nickname,
RolesAfter = new ReadOnlyCollection(new List(mbr.Roles)),
PendingAfter = mbr.IsPending,
TimeoutAfter = mbr.CommunicationDisabledUntil,
AvatarHashAfter = mbr.AvatarHash,
GuildAvatarHashAfter = mbr.GuildAvatarHash,
NicknameBefore = nickOld,
RolesBefore = rolesOld,
PendingBefore = pendingOld,
TimeoutBefore = cduOld,
AvatarHashBefore = avOld,
GuildAvatarHashBefore = gAvOld
};
await this._guildMemberUpdated.InvokeAsync(this, eargs).ConfigureAwait(false);
}
///
/// Handles timeout events.
///
/// Internally used as uid for the timer data.
private async void TimeoutTimer(object state)
{
var tid = (string)state;
var data = this._tempTimers.First(x=> x.Key == tid).Value.Key;
var timer = this._tempTimers.First(x=> x.Key == tid).Value.Value;
IReadOnlyList auditlog = null;
DiscordAuditLogMemberUpdateEntry filtered = null;
try
{
auditlog = await data.Guild.GetAuditLogsAsync(10, null, AuditLogActionType.MemberUpdate);
var preFiltered = auditlog.Select(x => x as DiscordAuditLogMemberUpdateEntry).Where(x => x.Target.Id == data.Member.Id);
filtered = preFiltered.First();
}
catch (UnauthorizedException) { }
catch (Exception)
{
this.Logger.LogTrace("Failing timeout event.");
await timer.DisposeAsync();
this._tempTimers.Remove(tid);
return;
}
var actor = filtered?.UserResponsible as DiscordMember;
this.Logger.LogTrace("Trying to execute timeout event.");
if (data.TimeoutUntilOld.HasValue && data.TimeoutUntilNew.HasValue)
{
// A timeout was updated.
if (filtered != null && auditlog == null)
{
this.Logger.LogTrace("Re-scheduling timeout event.");
timer.Change(2000, Timeout.Infinite);
return;
}
var ea = new GuildMemberTimeoutUpdateEventArgs(this.ServiceProvider)
{
Guild = data.Guild,
Target = data.Member,
TimeoutBefore = data.TimeoutUntilOld.Value,
TimeoutAfter = data.TimeoutUntilNew.Value,
Actor = actor,
AuditLogId = filtered?.Id,
AuditLogReason = filtered?.Reason
};
await this._guildMemberTimeoutChanged.InvokeAsync(this, ea).ConfigureAwait(false);
}
else if (!data.TimeoutUntilOld.HasValue && data.TimeoutUntilNew.HasValue)
{
// A timeout was added.
if (filtered != null && auditlog == null)
{
this.Logger.LogTrace("Re-scheduling timeout event.");
timer.Change(2000, Timeout.Infinite);
return;
}
var ea = new GuildMemberTimeoutAddEventArgs(this.ServiceProvider)
{
Guild = data.Guild,
Target = data.Member,
Timeout = data.TimeoutUntilNew.Value,
Actor = actor,
AuditLogId = filtered?.Id,
AuditLogReason = filtered?.Reason
};
await this._guildMemberTimeoutAdded.InvokeAsync(this, ea).ConfigureAwait(false);
}
else if (data.TimeoutUntilOld.HasValue && !data.TimeoutUntilNew.HasValue)
{
// A timeout was removed.
if (filtered != null && auditlog == null)
{
this.Logger.LogTrace("Re-scheduling timeout event.");
timer.Change(2000, Timeout.Infinite);
return;
}
var ea = new GuildMemberTimeoutRemoveEventArgs(this.ServiceProvider)
{
Guild = data.Guild,
Target = data.Member,
TimeoutBefore = data.TimeoutUntilOld.Value,
Actor = actor,
AuditLogId = filtered?.Id,
AuditLogReason = filtered?.Reason
};
await this._guildMemberTimeoutRemoved.InvokeAsync(this, ea).ConfigureAwait(false);
}
// Ending timer because it worked.
this.Logger.LogTrace("Removing timeout event.");
await timer.DisposeAsync();
this._tempTimers.Remove(tid);
}
///
/// Handles the guild members chunk event.
///
/// The raw chunk data.
internal async Task OnGuildMembersChunkEventAsync(JObject dat)
{
var guild = this.Guilds[(ulong)dat["guild_id"]];
var chunkIndex = (int)dat["chunk_index"];
var chunkCount = (int)dat["chunk_count"];
var nonce = (string)dat["nonce"];
var mbrs = new HashSet();
var pres = new HashSet();
var members = dat["members"].ToObject();
foreach (var member in members)
{
var mbr = new DiscordMember(member) { Discord = this, GuildId = guild.Id };
if (!this.UserCache.ContainsKey(mbr.Id))
this.UserCache[mbr.Id] = new DiscordUser(member.User) { Discord = this };
guild.MembersInternal[mbr.Id] = mbr;
mbrs.Add(mbr);
}
guild.MemberCount = guild.MembersInternal.Count;
var ea = new GuildMembersChunkEventArgs(this.ServiceProvider)
{
Guild = guild,
Members = new ReadOnlySet(mbrs),
ChunkIndex = chunkIndex,
ChunkCount = chunkCount,
Nonce = nonce,
};
if (dat["presences"] != null)
{
var presences = dat["presences"].ToObject();
var presCount = presences.Length;
foreach (var presence in presences)
{
presence.Discord = this;
presence.Activity = new DiscordActivity(presence.RawActivity);
if (presence.RawActivities != null)
{
presence.InternalActivities = presence.RawActivities
.Select(x => new DiscordActivity(x)).ToArray();
}
pres.Add(presence);
}
ea.Presences = new ReadOnlySet(pres);
}
if (dat["not_found"] != null)
{
var nf = dat["not_found"].ToObject>();
ea.NotFound = new ReadOnlySet(nf);
}
await this._guildMembersChunked.InvokeAsync(this, ea).ConfigureAwait(false);
}
#endregion
#region Guild Role
///
/// Handles the guild role create event.
///
/// The role.
/// The guild.
internal async Task OnGuildRoleCreateEventAsync(DiscordRole role, DiscordGuild guild)
{
role.Discord = this;
role.GuildId = guild.Id;
guild.RolesInternal[role.Id] = role;
var ea = new GuildRoleCreateEventArgs(this.ServiceProvider)
{
Guild = guild,
Role = role
};
await this._guildRoleCreated.InvokeAsync(this, ea).ConfigureAwait(false);
}
///
/// Handles the guild role update event.
///
/// The role.
/// The guild.
internal async Task OnGuildRoleUpdateEventAsync(DiscordRole role, DiscordGuild guild)
{
var newRole = guild.GetRole(role.Id);
var oldRole = new DiscordRole
{
GuildId = guild.Id,
ColorInternal = newRole.ColorInternal,
Discord = this,
IsHoisted = newRole.IsHoisted,
Id = newRole.Id,
IsManaged = newRole.IsManaged,
IsMentionable = newRole.IsMentionable,
Name = newRole.Name,
Permissions = newRole.Permissions,
Position = newRole.Position,
IconHash = newRole.IconHash,
Tags = newRole.Tags ?? null,
UnicodeEmojiString = newRole.UnicodeEmojiString
};
newRole.GuildId = guild.Id;
newRole.ColorInternal = role.ColorInternal;
newRole.IsHoisted = role.IsHoisted;
newRole.IsManaged = role.IsManaged;
newRole.IsMentionable = role.IsMentionable;
newRole.Name = role.Name;
newRole.Permissions = role.Permissions;
newRole.Position = role.Position;
newRole.IconHash = role.IconHash;
newRole.Tags = role.Tags ?? null;
newRole.UnicodeEmojiString = role.UnicodeEmojiString;
var ea = new GuildRoleUpdateEventArgs(this.ServiceProvider)
{
Guild = guild,
RoleAfter = newRole,
RoleBefore = oldRole
};
await this._guildRoleUpdated.InvokeAsync(this, ea).ConfigureAwait(false);
}
///
/// Handles the guild role delete event.
///
/// The role id.
/// The guild.
internal async Task OnGuildRoleDeleteEventAsync(ulong roleId, DiscordGuild guild)
{
if (!guild.RolesInternal.TryRemove(roleId, out var role))
this.Logger.LogWarning($"Attempted to delete a nonexistent role ({roleId}) from guild ({guild}).");
var ea = new GuildRoleDeleteEventArgs(this.ServiceProvider)
{
Guild = guild,
Role = role
};
await this._guildRoleDeleted.InvokeAsync(this, ea).ConfigureAwait(false);
}
#endregion
#region Invite
///
/// Handles the invite create event.
///
/// The channel id.
/// The guild id.
/// The invite.
internal async Task OnInviteCreateEventAsync(ulong channelId, ulong guildId, DiscordInvite invite)
{
var guild = this.InternalGetCachedGuild(guildId);
var channel = this.InternalGetCachedChannel(channelId);
invite.Discord = this;
if (invite.Inviter is not null)
{
invite.Inviter.Discord = this;
this.UserCache.AddOrUpdate(invite.Inviter.Id, invite.Inviter, (old, @new) => @new);
}
guild.Invites[invite.Code] = invite;
var ea = new InviteCreateEventArgs(this.ServiceProvider)
{
Channel = channel,
Guild = guild,
Invite = invite
};
await this._inviteCreated.InvokeAsync(this, ea).ConfigureAwait(false);
}
///
/// Handles the invite delete event.
///
/// The channel id.
/// The guild id.
/// The raw invite.
internal async Task OnInviteDeleteEventAsync(ulong channelId, ulong guildId, JToken dat)
{
var guild = this.InternalGetCachedGuild(guildId);
var channel = this.InternalGetCachedChannel(channelId);
if (!guild.Invites.TryRemove(dat["code"].ToString(), out var invite))
{
invite = dat.ToObject();
invite.Discord = this;
}
invite.IsRevoked = true;
var ea = new InviteDeleteEventArgs(this.ServiceProvider)
{
Channel = channel,
Guild = guild,
Invite = invite
};
await this._inviteDeleted.InvokeAsync(this, ea).ConfigureAwait(false);
}
#endregion
#region Message
///
/// Handles the message acknowledge event.
///
/// The channel.
/// The message id.
internal async Task OnMessageAckEventAsync(DiscordChannel chn, ulong messageId)
{
if (this.MessageCache == null || !this.MessageCache.TryGet(xm => xm.Id == messageId && xm.ChannelId == chn.Id, out var msg))
{
msg = new DiscordMessage
{
Id = messageId,
ChannelId = chn.Id,
Discord = this,
};
}
await this._messageAcknowledged.InvokeAsync(this, new MessageAcknowledgeEventArgs(this.ServiceProvider) { Message = msg }).ConfigureAwait(false);
}
///
/// Handles the message create event.
///
/// The message.
/// The transport user (author).
/// The transport member.
/// The reference transport user (author).
/// The reference transport member.
internal async Task OnMessageCreateEventAsync(DiscordMessage message, TransportUser author, TransportMember member, TransportUser referenceAuthor, TransportMember referenceMember)
{
message.Discord = this;
this.PopulateMessageReactionsAndCache(message, author, member);
message.PopulateMentions();
if (message.Channel == null && message.ChannelId == default)
this.Logger.LogWarning(LoggerEvents.WebSocketReceive, "Channel which the last message belongs to is not in cache - cache state might be invalid!");
if (message.ReferencedMessage != null)
{
message.ReferencedMessage.Discord = this;
this.PopulateMessageReactionsAndCache(message.ReferencedMessage, referenceAuthor, referenceMember);
message.ReferencedMessage.PopulateMentions();
}
foreach (var sticker in message.Stickers)
sticker.Discord = this;
var ea = new MessageCreateEventArgs(this.ServiceProvider)
{
Message = message,
MentionedUsers = new ReadOnlyCollection(message.MentionedUsersInternal),
MentionedRoles = message.MentionedRolesInternal != null ? new ReadOnlyCollection(message.MentionedRolesInternal) : null,
MentionedChannels = message.MentionedChannelsInternal != null ? new ReadOnlyCollection(message.MentionedChannelsInternal) : null
};
await this._messageCreated.InvokeAsync(this, ea).ConfigureAwait(false);
}
///
/// Handles the message update event.
///
/// The message.
/// The transport user (author).
/// The transport member.
/// The reference transport user (author).
/// The reference transport member.
internal async Task OnMessageUpdateEventAsync(DiscordMessage message, TransportUser author, TransportMember member, TransportUser referenceAuthor, TransportMember referenceMember)
{
DiscordGuild guild;
message.Discord = this;
var eventMessage = message;
DiscordMessage oldmsg = null;
if (this.Configuration.MessageCacheSize == 0
|| this.MessageCache == null
|| !this.MessageCache.TryGet(xm => xm.Id == eventMessage.Id && xm.ChannelId == eventMessage.ChannelId, out message))
{
message = eventMessage;
this.PopulateMessageReactionsAndCache(message, author, member);
guild = message.Channel?.Guild;
if (message.ReferencedMessage != null)
{
message.ReferencedMessage.Discord = this;
this.PopulateMessageReactionsAndCache(message.ReferencedMessage, referenceAuthor, referenceMember);
message.ReferencedMessage.PopulateMentions();
}
}
else
{
oldmsg = new DiscordMessage(message);
guild = message.Channel?.Guild;
message.EditedTimestampRaw = eventMessage.EditedTimestampRaw;
if (eventMessage.Content != null)
message.Content = eventMessage.Content;
message.EmbedsInternal.Clear();
message.EmbedsInternal.AddRange(eventMessage.EmbedsInternal);
message.Pinned = eventMessage.Pinned;
message.IsTts = eventMessage.IsTts;
}
message.PopulateMentions();
var ea = new MessageUpdateEventArgs(this.ServiceProvider)
{
Message = message,
MessageBefore = oldmsg,
MentionedUsers = new ReadOnlyCollection(message.MentionedUsersInternal),
MentionedRoles = message.MentionedRolesInternal != null ? new ReadOnlyCollection(message.MentionedRolesInternal) : null,
MentionedChannels = message.MentionedChannelsInternal != null ? new ReadOnlyCollection(message.MentionedChannelsInternal) : null
};
await this._messageUpdated.InvokeAsync(this, ea).ConfigureAwait(false);
}
///
/// Handles the message delete event.
///
/// The message id.
/// The channel id.
/// The optional guild id.
internal async Task OnMessageDeleteEventAsync(ulong messageId, ulong channelId, ulong? guildId)
{
var channel = this.InternalGetCachedChannel(channelId) ?? this.InternalGetCachedThread(channelId);
var guild = this.InternalGetCachedGuild(guildId);
if (channel == null
|| this.Configuration.MessageCacheSize == 0
|| this.MessageCache == null
|| !this.MessageCache.TryGet(xm => xm.Id == messageId && xm.ChannelId == channelId, out var msg))
{
msg = new DiscordMessage
{
Id = messageId,
ChannelId = channelId,
Discord = this,
};
}
if (this.Configuration.MessageCacheSize > 0)
this.MessageCache?.Remove(xm => xm.Id == msg.Id && xm.ChannelId == channelId);
var ea = new MessageDeleteEventArgs(this.ServiceProvider)
{
Channel = channel,
Message = msg,
Guild = guild
};
await this._messageDeleted.InvokeAsync(this, ea).ConfigureAwait(false);
}
///
/// Handles the message bulk delete event.
///
/// The message ids.
/// The channel id.
/// The optional guild id.
internal async Task OnMessageBulkDeleteEventAsync(ulong[] messageIds, ulong channelId, ulong? guildId)
{
var channel = this.InternalGetCachedChannel(channelId) ?? this.InternalGetCachedThread(channelId);
var msgs = new List(messageIds.Length);
foreach (var messageId in messageIds)
{
if (channel == null
|| this.Configuration.MessageCacheSize == 0
|| this.MessageCache == null
|| !this.MessageCache.TryGet(xm => xm.Id == messageId && xm.ChannelId == channelId, out var msg))
{
msg = new DiscordMessage
{
Id = messageId,
ChannelId = channelId,
Discord = this,
};
}
if (this.Configuration.MessageCacheSize > 0)
this.MessageCache?.Remove(xm => xm.Id == msg.Id && xm.ChannelId == channelId);
msgs.Add(msg);
}
var guild = this.InternalGetCachedGuild(guildId);
var ea = new MessageBulkDeleteEventArgs(this.ServiceProvider)
{
Channel = channel,
Messages = new ReadOnlyCollection