Browse Source

Early 1.0 REST Preview

tags/1.0-rc
RogueException 9 years ago
parent
commit
5bdd6a7ff3
100 changed files with 105 additions and 3576 deletions
  1. +19
    -83
      Discord.Net.sln
  2. +8
    -0
      NuGet.config
  3. +1
    -1
      docs/features/commands.rst
  4. +11
    -9
      docs/features/events.rst
  5. +5
    -11
      docs/features/logging.rst
  6. +17
    -23
      docs/features/permissions.rst
  7. +1
    -1
      docs/features/server-management.rst
  8. +2
    -2
      docs/features/user-management.rst
  9. +4
    -171
      docs/features/voice.rst
  10. +3
    -3
      docs/getting_started.rst
  11. +1
    -3
      docs/global.txt
  12. +5
    -7
      docs/index.rst
  13. +7
    -7
      docs/samples/events.cs
  14. +3
    -6
      docs/samples/getting_started.cs
  15. +3
    -2
      docs/samples/logging.cs
  16. +11
    -5
      docs/samples/permissions.cs
  17. +4
    -4
      global.json
  18. +0
    -21
      ref/Discord.Net.xproj
  19. +0
    -75
      ref/DiscordClient.cs
  20. +0
    -65
      ref/DiscordConfig.cs
  21. +0
    -43
      ref/Entities/Channels/IPublicChannel.cs
  22. +0
    -55
      ref/Entities/Channels/PrivateChannel.cs
  23. +0
    -91
      ref/Entities/Channels/TextChannel.cs
  24. +0
    -74
      ref/Entities/Channels/VoiceChannel.cs
  25. +0
    -17
      ref/Entities/Color.cs
  26. +0
    -11
      ref/Entities/IModifiable.cs
  27. +0
    -37
      ref/Entities/Invite/BasicInvite.cs
  28. +0
    -18
      ref/Entities/Invite/Invite.cs
  29. +0
    -68
      ref/Entities/Message.cs
  30. +0
    -53
      ref/Entities/Permissions/ChannelPermissions.cs
  31. +0
    -50
      ref/Entities/Permissions/OverwritePermissions.cs
  32. +0
    -9
      ref/Entities/Permissions/PermissionOverwriteEntry.cs
  33. +0
    -55
      ref/Entities/Permissions/ServerPermissions.cs
  34. +0
    -25
      ref/Entities/Profile.cs
  35. +0
    -11
      ref/Entities/Region.cs
  36. +0
    -29
      ref/Entities/Role.cs
  37. +0
    -67
      ref/Entities/Server.cs
  38. +0
    -19
      ref/Entities/Users/IUser.cs
  39. +0
    -43
      ref/Entities/Users/PrivateUser.cs
  40. +0
    -73
      ref/Entities/Users/ServerUser.cs
  41. +0
    -13
      ref/Enums/ChannelType.cs
  42. +0
    -10
      ref/Enums/ConnectionState.cs
  43. +0
    -18
      ref/Enums/EntityState.cs
  44. +0
    -9
      ref/Enums/ImageType.cs
  45. +0
    -12
      ref/Enums/LogSeverity.cs
  46. +0
    -9
      ref/Enums/PermValue.cs
  47. +0
    -8
      ref/Enums/PermissionTarget.cs
  48. +0
    -8
      ref/Enums/Relative.cs
  49. +0
    -9
      ref/Enums/UserStatus.cs
  50. +0
    -9
      ref/Events/ChannelEventArgs.cs
  51. +0
    -10
      ref/Events/ChannelUpdatedEventArgs.cs
  52. +0
    -10
      ref/Events/DisconnectedEventArgs.cs
  53. +0
    -12
      ref/Events/LogMessageEventArgs.cs
  54. +0
    -11
      ref/Events/MessageEventArgs.cs
  55. +0
    -12
      ref/Events/MessageUpdatedEventArgs.cs
  56. +0
    -10
      ref/Events/ProfileUpdatedEventArgs.cs
  57. +0
    -10
      ref/Events/RoleEventArgs.cs
  58. +0
    -11
      ref/Events/RoleUpdatedEventArgs.cs
  59. +0
    -9
      ref/Events/ServerEventArgs.cs
  60. +0
    -10
      ref/Events/ServerUpdatedEventArgs.cs
  61. +0
    -14
      ref/Events/TypingEventArgs.cs
  62. +0
    -8
      ref/Events/UserEventArgs.cs
  63. +0
    -9
      ref/Events/UserUpdatedEventArgs.cs
  64. +0
    -14
      ref/Format.cs
  65. +0
    -30
      ref/ILogger.cs
  66. +0
    -9
      ref/MessageQueue.cs
  67. +0
    -16
      ref/Net/HttpException.cs
  68. +0
    -17
      ref/Net/Rest/CompletedRequestEventArgs.cs
  69. +0
    -23
      ref/Net/Rest/IRestClient.cs
  70. +0
    -10
      ref/Net/Rest/IRestClientProvider.cs
  71. +0
    -12
      ref/Net/Rest/RequestEventArgs.cs
  72. +0
    -9
      ref/Net/TimeoutException.cs
  73. +0
    -12
      ref/Net/WebSocketException.cs
  74. +0
    -11
      ref/Net/WebSockets/BinaryMessageEventArgs.cs
  75. +0
    -28
      ref/Net/WebSockets/GatewaySocket.cs
  76. +0
    -15
      ref/Net/WebSockets/IWebSocket.cs
  77. +0
    -18
      ref/Net/WebSockets/IWebSocketEngine.cs
  78. +0
    -9
      ref/Net/WebSockets/IWebSocketProvider.cs
  79. +0
    -11
      ref/Net/WebSockets/TextMessageEventArgs.cs
  80. +0
    -11
      ref/Net/WebSockets/WebSocketEventEventArgs.cs
  81. +0
    -81
      ref/project.json
  82. +0
    -283
      src/Discord.Net.Audio/AudioClient.cs
  83. +0
    -26
      src/Discord.Net.Audio/AudioExtensions.cs
  84. +0
    -9
      src/Discord.Net.Audio/AudioMode.cs
  85. +0
    -193
      src/Discord.Net.Audio/AudioService.cs
  86. +0
    -51
      src/Discord.Net.Audio/AudioServiceConfig.cs
  87. +0
    -21
      src/Discord.Net.Audio/Discord.Net.Audio.xproj
  88. +0
    -46
      src/Discord.Net.Audio/IAudioClient.cs
  89. +0
    -22
      src/Discord.Net.Audio/InternalFrameEventArgs.cs
  90. +0
    -14
      src/Discord.Net.Audio/InternalIsSpeakingEventArgs.cs
  91. +0
    -516
      src/Discord.Net.Audio/Net/VoiceSocket.cs
  92. +0
    -111
      src/Discord.Net.Audio/Opus/OpusConverter.cs
  93. +0
    -43
      src/Discord.Net.Audio/Opus/OpusDecoder.cs
  94. +0
    -78
      src/Discord.Net.Audio/Opus/OpusEncoder.cs
  95. +0
    -32
      src/Discord.Net.Audio/Sodium/SecretBox.cs
  96. +0
    -13
      src/Discord.Net.Audio/UserIsTalkingEventArgs.cs
  97. +0
    -39
      src/Discord.Net.Audio/VirtualClient.cs
  98. +0
    -140
      src/Discord.Net.Audio/VoiceBuffer.cs
  99. +0
    -15
      src/Discord.Net.Audio/VoiceDisconnectedEventArgs.cs
  100. BIN
      src/Discord.Net.Audio/libsodium.dll

+ 19
- 83
Discord.Net.sln View File

@@ -1,17 +1,13 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.24720.0
VisualStudioVersion = 14.0.25123.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8D7989F0-66CE-4DBB-8230-D8C811E9B1D7}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "netplatform", "netplatform", "{EA68EBE2-51C8-4440-9EF7-D633C90A5D35}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net", "src\Discord.Net\Discord.Net.xproj", "{ACFB060B-EC8A-4926-B293-04C01E17EE23}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Commands", "src\Discord.Net.Commands\Discord.Net.Commands.xproj", "{19793545-EF89-48F4-8100-3EBAAD0A9141}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "net45", "net45", "{DF03D4E8-38F6-4FE1-BC52-E38124BE8AFD}"
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net", "src\Discord.Net\Discord.Net.xproj", "{2C91BDD7-621D-460F-B768-EAD106D9BA62}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{6317A2E6-8E36-4C3E-949B-3F10EC888AB9}"
EndProject
@@ -22,104 +18,44 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
global.json = global.json
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net", "src\Discord.Net.Net45\Discord.Net.csproj", "{8D71A857-879A-4A10-859E-5FF824ED6688}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net.Commands", "src\Discord.Net.Commands.Net45\Discord.Net.Commands.csproj", "{1B5603B4-6F8F-4289-B945-7BAAE523D740}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Modules", "src\Discord.Net.Modules\Discord.Net.Modules.xproj", "{01584E8A-78DA-486F-9EF9-A894E435841B}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "net45", "net45", "{628A40F4-2D06-4BCE-82EF-0EE70DD5C1CA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net.Modules", "src\Discord.Net.Modules.Net45\Discord.Net.Modules.csproj", "{3091164F-66AE-4543-A63D-167C1116241D}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Audio", "src\Discord.Net.Audio\Discord.Net.Audio.xproj", "{DFF7AFE3-CA77-4109-BADE-B4B49A4F6648}"
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Discord.Net.Shared", "src\Discord.Net.Shared\Discord.Net.Shared.shproj", "{2875DEB5-F248-4105-8EA2-5141E3DE8025}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net.Audio", "src\Discord.Net.Audio.Net45\Discord.Net.Audio.csproj", "{7BFEF748-B934-4621-9B11-6302E3A9F6B3}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net.Net45", "src\Discord.Net\Discord.Net.Net45.csproj", "{C6A50D24-CBD3-4E76-852C-4DCA60BBD608}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Discord.Net.Shared\Discord.Net.Shared.projitems*{2875deb5-f248-4105-8ea2-5141e3de8025}*SharedItemsImports = 13
src\Discord.Net.Shared\Discord.Net.Shared.projitems*{7bfef748-b934-4621-9b11-6302e3a9f6b3}*SharedItemsImports = 4
src\Discord.Net.Shared\Discord.Net.Shared.projitems*{1b5603b4-6f8f-4289-b945-7baae523d740}*SharedItemsImports = 4
src\Discord.Net.Shared\Discord.Net.Shared.projitems*{3091164f-66ae-4543-a63d-167c1116241d}*SharedItemsImports = 4
src\Discord.Net.Shared\Discord.Net.Shared.projitems*{8d71a857-879a-4a10-859e-5ff824ed6688}*SharedItemsImports = 4
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
FullDebug|Any CPU = FullDebug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{ACFB060B-EC8A-4926-B293-04C01E17EE23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ACFB060B-EC8A-4926-B293-04C01E17EE23}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ACFB060B-EC8A-4926-B293-04C01E17EE23}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU
{ACFB060B-EC8A-4926-B293-04C01E17EE23}.FullDebug|Any CPU.Build.0 = Debug|Any CPU
{ACFB060B-EC8A-4926-B293-04C01E17EE23}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ACFB060B-EC8A-4926-B293-04C01E17EE23}.Release|Any CPU.Build.0 = Release|Any CPU
{19793545-EF89-48F4-8100-3EBAAD0A9141}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{19793545-EF89-48F4-8100-3EBAAD0A9141}.Debug|Any CPU.Build.0 = Debug|Any CPU
{19793545-EF89-48F4-8100-3EBAAD0A9141}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU
{19793545-EF89-48F4-8100-3EBAAD0A9141}.FullDebug|Any CPU.Build.0 = Debug|Any CPU
{19793545-EF89-48F4-8100-3EBAAD0A9141}.Release|Any CPU.ActiveCfg = Release|Any CPU
{19793545-EF89-48F4-8100-3EBAAD0A9141}.Release|Any CPU.Build.0 = Release|Any CPU
{2C91BDD7-621D-460F-B768-EAD106D9BA62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2C91BDD7-621D-460F-B768-EAD106D9BA62}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2C91BDD7-621D-460F-B768-EAD106D9BA62}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU
{2C91BDD7-621D-460F-B768-EAD106D9BA62}.FullDebug|Any CPU.Build.0 = Debug|Any CPU
{2C91BDD7-621D-460F-B768-EAD106D9BA62}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2C91BDD7-621D-460F-B768-EAD106D9BA62}.Release|Any CPU.Build.0 = Release|Any CPU
{855D6B1D-847B-42DA-BE6A-23683EA89511}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{855D6B1D-847B-42DA-BE6A-23683EA89511}.Debug|Any CPU.Build.0 = Debug|Any CPU
{855D6B1D-847B-42DA-BE6A-23683EA89511}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU
{855D6B1D-847B-42DA-BE6A-23683EA89511}.FullDebug|Any CPU.Build.0 = Debug|Any CPU
{855D6B1D-847B-42DA-BE6A-23683EA89511}.Release|Any CPU.ActiveCfg = Release|Any CPU
{855D6B1D-847B-42DA-BE6A-23683EA89511}.Release|Any CPU.Build.0 = Release|Any CPU
{8D71A857-879A-4A10-859E-5FF824ED6688}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8D71A857-879A-4A10-859E-5FF824ED6688}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8D71A857-879A-4A10-859E-5FF824ED6688}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU
{8D71A857-879A-4A10-859E-5FF824ED6688}.FullDebug|Any CPU.Build.0 = Debug|Any CPU
{8D71A857-879A-4A10-859E-5FF824ED6688}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8D71A857-879A-4A10-859E-5FF824ED6688}.Release|Any CPU.Build.0 = Release|Any CPU
{1B5603B4-6F8F-4289-B945-7BAAE523D740}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1B5603B4-6F8F-4289-B945-7BAAE523D740}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1B5603B4-6F8F-4289-B945-7BAAE523D740}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU
{1B5603B4-6F8F-4289-B945-7BAAE523D740}.FullDebug|Any CPU.Build.0 = Debug|Any CPU
{1B5603B4-6F8F-4289-B945-7BAAE523D740}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1B5603B4-6F8F-4289-B945-7BAAE523D740}.Release|Any CPU.Build.0 = Release|Any CPU
{01584E8A-78DA-486F-9EF9-A894E435841B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{01584E8A-78DA-486F-9EF9-A894E435841B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{01584E8A-78DA-486F-9EF9-A894E435841B}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU
{01584E8A-78DA-486F-9EF9-A894E435841B}.FullDebug|Any CPU.Build.0 = Debug|Any CPU
{01584E8A-78DA-486F-9EF9-A894E435841B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{01584E8A-78DA-486F-9EF9-A894E435841B}.Release|Any CPU.Build.0 = Release|Any CPU
{3091164F-66AE-4543-A63D-167C1116241D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3091164F-66AE-4543-A63D-167C1116241D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3091164F-66AE-4543-A63D-167C1116241D}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU
{3091164F-66AE-4543-A63D-167C1116241D}.FullDebug|Any CPU.Build.0 = Debug|Any CPU
{3091164F-66AE-4543-A63D-167C1116241D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3091164F-66AE-4543-A63D-167C1116241D}.Release|Any CPU.Build.0 = Release|Any CPU
{DFF7AFE3-CA77-4109-BADE-B4B49A4F6648}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DFF7AFE3-CA77-4109-BADE-B4B49A4F6648}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DFF7AFE3-CA77-4109-BADE-B4B49A4F6648}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU
{DFF7AFE3-CA77-4109-BADE-B4B49A4F6648}.FullDebug|Any CPU.Build.0 = Debug|Any CPU
{DFF7AFE3-CA77-4109-BADE-B4B49A4F6648}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DFF7AFE3-CA77-4109-BADE-B4B49A4F6648}.Release|Any CPU.Build.0 = Release|Any CPU
{7BFEF748-B934-4621-9B11-6302E3A9F6B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7BFEF748-B934-4621-9B11-6302E3A9F6B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7BFEF748-B934-4621-9B11-6302E3A9F6B3}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU
{7BFEF748-B934-4621-9B11-6302E3A9F6B3}.FullDebug|Any CPU.Build.0 = Debug|Any CPU
{7BFEF748-B934-4621-9B11-6302E3A9F6B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7BFEF748-B934-4621-9B11-6302E3A9F6B3}.Release|Any CPU.Build.0 = Release|Any CPU
{C6A50D24-CBD3-4E76-852C-4DCA60BBD608}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C6A50D24-CBD3-4E76-852C-4DCA60BBD608}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C6A50D24-CBD3-4E76-852C-4DCA60BBD608}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU
{C6A50D24-CBD3-4E76-852C-4DCA60BBD608}.FullDebug|Any CPU.Build.0 = Debug|Any CPU
{C6A50D24-CBD3-4E76-852C-4DCA60BBD608}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C6A50D24-CBD3-4E76-852C-4DCA60BBD608}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{EA68EBE2-51C8-4440-9EF7-D633C90A5D35} = {8D7989F0-66CE-4DBB-8230-D8C811E9B1D7}
{ACFB060B-EC8A-4926-B293-04C01E17EE23} = {EA68EBE2-51C8-4440-9EF7-D633C90A5D35}
{19793545-EF89-48F4-8100-3EBAAD0A9141} = {EA68EBE2-51C8-4440-9EF7-D633C90A5D35}
{DF03D4E8-38F6-4FE1-BC52-E38124BE8AFD} = {8D7989F0-66CE-4DBB-8230-D8C811E9B1D7}
{2C91BDD7-621D-460F-B768-EAD106D9BA62} = {EA68EBE2-51C8-4440-9EF7-D633C90A5D35}
{855D6B1D-847B-42DA-BE6A-23683EA89511} = {6317A2E6-8E36-4C3E-949B-3F10EC888AB9}
{8D71A857-879A-4A10-859E-5FF824ED6688} = {DF03D4E8-38F6-4FE1-BC52-E38124BE8AFD}
{1B5603B4-6F8F-4289-B945-7BAAE523D740} = {DF03D4E8-38F6-4FE1-BC52-E38124BE8AFD}
{01584E8A-78DA-486F-9EF9-A894E435841B} = {EA68EBE2-51C8-4440-9EF7-D633C90A5D35}
{3091164F-66AE-4543-A63D-167C1116241D} = {DF03D4E8-38F6-4FE1-BC52-E38124BE8AFD}
{DFF7AFE3-CA77-4109-BADE-B4B49A4F6648} = {EA68EBE2-51C8-4440-9EF7-D633C90A5D35}
{2875DEB5-F248-4105-8EA2-5141E3DE8025} = {DF03D4E8-38F6-4FE1-BC52-E38124BE8AFD}
{7BFEF748-B934-4621-9B11-6302E3A9F6B3} = {DF03D4E8-38F6-4FE1-BC52-E38124BE8AFD}
{628A40F4-2D06-4BCE-82EF-0EE70DD5C1CA} = {8D7989F0-66CE-4DBB-8230-D8C811E9B1D7}
{C6A50D24-CBD3-4E76-852C-4DCA60BBD608} = {628A40F4-2D06-4BCE-82EF-0EE70DD5C1CA}
EndGlobalSection
EndGlobal

+ 8
- 0
NuGet.config View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="AspNetCiDev" value="https://www.myget.org/F/aspnetcidev/api/v3/index.json" />
<add key="NuGet" value="https://api.nuget.org/v3/index.json" />
<add key="aspnet-contrib" value="https://www.myget.org/F/aspnet-contrib/api/v3/index.json" />
</packageSources>
</configuration>

+ 1
- 1
docs/features/commands.rst View File

@@ -1,7 +1,7 @@
|stub| Commands
===============

The `Discord.Net.Commands`_ package extends DiscordClient with a built-in Commands Handler.
The `Discord.Net.Commands`_ package DiscordBotClient extends DiscordClient with support for commands.

.. _Discord.Net.Commands: https://www.nuget.org/packages/Discord.Net.Commands



+ 11
- 9
docs/features/events.rst View File

@@ -3,19 +3,21 @@ Events

Usage
-----
Messages from the Discord server are exposed via events on the DiscordClient class and follow the standard EventHandler<EventArgs> C# pattern.
Messages from the Discord server are exposed via events on the DiscordClient class and follow the standard EventHandler<EventArgs> C# pattern.

.. warning::
Note that all synchronous code in an event handler will run on the gateway socket's thread and should be handled as quickly as possible.
Note that all synchronous code in an event handler will run on the gateway socket's thread and should be handled as quickly as possible.
Using the async-await pattern to let the thread continue immediately is recommended and is demonstrated in the examples below.

Ready
-----

The Ready Event is raised only once, when your client finishes processing the READY packet from Discord.
Connection State
----------------

This has replaced the previous "Connected" event, and indicates that it is safe to begin retrieving users, channels, or servers from the cache.
Connection Events will be raised when the Connection State of your client changes.

.. warning::
You should not use DiscordClient.Connected to run code when your client first connects to Discord.
If you lose connection and automatically reconnect, this code will be ran again, which may lead to unexpected behavior.
Messages
--------

@@ -24,7 +26,7 @@ Messages

Example of MessageReceived:

.. code-block:: csharp6
.. code-block:: c#

// (Preface: Echo Bots are discouraged, make sure your bot is not running in a public server if you use them)

@@ -54,7 +56,7 @@ There are several user events:

Examples:

.. code-block:: csharp6
.. code-block:: c#

// Register a Hook into the UserBanned event using a Lambda
_client.UserBanned += async (s, e) => {


+ 5
- 11
docs/features/logging.rst View File

@@ -2,12 +2,12 @@ Logging
=======

Discord.Net will log all of its events/exceptions using a built-in LogManager.
This LogManager can be accessed through ``DiscordClient.Log``
This LogManager can be accessed through DiscordClient.Log

Usage
-----

To handle Log Messages through Discord.Net's Logger, you must hook into the ``Log.Message<LogMessageEventArgs>`` Event.
To handle Log Messages through Discord.Net's Logger, you must hook into the Log.Message<LogMessageEventArgs> Event.

The LogManager does not provide a string-based result for the message, you must put your own message format together using the data provided through LogMessageEventArgs
See the Example for a snippet of logging.
@@ -17,25 +17,19 @@ Logging Your Own Data

The LogManager included in Discord.Net can also be used to log your own messages.

You can use ``DiscordClient.Log.Log(LogSeverity, Source, Message, [Exception])``, or one of the shortcut helpers, to log data.
You can use DiscordClient.Log.Log(LogSeverity, Source, Message, Exception), or one of the shortcut helpers, to log data.

Example:

.. code-block:: csharp6
.. code-block:: c#

_client.MessageReceived += async (s, e) {
// Log a new Message with Severity Info, Sourced from 'MessageReceived', with the Message Contents.
_client.Log.Info("MessageReceived", e.Message.Text, null);
};


.. warning::

Starting in Discord.Net 1.0, you will not be able to log your own messages. You will need to create your own Logging manager, or use a pre-existing one.

Example
-------

.. literalinclude:: /samples/logging.cs
:language: csharp6
:language: c#
:tab-width: 2

+ 17
- 23
docs/features/permissions.rst View File

@@ -1,21 +1,8 @@
Permissions
===========

|outdated|

There are two types of permissions: *Channel Permissions* and *Server Permissions*.

Permission Overrides
--------------------

Channel Permissions are expressed using an enum, ``PermValue``.

The three states are fairly straightforward -

``PermValue.Allow``: Allow the user to perform a permission.
``PermValue.Deny``: Deny the user to perform a permission.
``PermValue.Inherit``: The user will inherit the permission from its role.

Channel Permissions
-------------------
Channel Permissions are controlled using a set of flags:
@@ -42,24 +29,27 @@ Speak Voice Speak in a voice channel.
UseVoiceActivation Voice Use Voice Activation in a text channel (for large channels where PTT is preferred)
======================= ======= ==============

Each flag is a PermValue; see the section above.
If a user has a permission, the value is true. Otherwise, it must be null.

Setting Channel Permissions
---------------------------
Dual Channel Permissions
------------------------
You may also access a user's permissions in a channel with the DualChannelPermissions class.
Unlike normal ChannelPermissions, DualChannelPermissions hold three values:

To set channel permissions, create a new ``ChannelPermissionOverrides``, and specify the flags/values that you want to override.
If a user has a permission, the value is true. If a user is denied a permission, it will be false. If the permission is not set, the value will return null.

Then, update the user, by doing ``Channel.AddPermissionsRule(_user, _overwrites);``
Setting Channel Permissions
---------------------------

Roles
-----
To set channel permissions, you may use either two ChannelPermissions, or one DualChannelPermissions.

Accessing/modifying permissions for roles is done the same way as user permissions, just using the overload for a Role. See above sections.
In the case of using two Channel Permissions, you must create one list of allowed permissions, and one list of denied permissions.
Otherwise, you can use a single DualChannelPermissions.

Server Permissions
------------------

Server Permissions can be viewed with ``User.ServerPermissions``, but **at the time of this writing** cannot be set.
Server Permissions can be accessed by ``Server.GetPermissions(User)``, and updated with ``Server.UpdatePermissions(User, ServerPermissions)``

A user's server permissions also contain the default values for it's channel permissions, so the channel permissions listed above are also valid flags for Server Permissions. There are also a few extra Server Permissions:

@@ -71,7 +61,11 @@ KickMembers Server Kick users from the server. They can still rejoi
ManageRoles Server Manage roles on the server, and their permissions.
ManageChannels Server Manage channels that exist on the server (add, remove them)
ManageServer Server Manage the server settings.
======================= ======= ==============

Roles
-----

Managing permissions for roles is much easier than for users in channels. For roles, just access the flag under `Role.Permissions`.

Example
-------


+ 1
- 1
docs/features/server-management.rst View File

@@ -10,7 +10,7 @@ You can create Channels, Invites, and Roles on a server using the CreateChannel,

You may also edit a server's name, icon, and region.

.. code-block:: csharp6
.. code-block:: c#

// Create a Channel and retrieve the Channel object
var _channel = await _server.CreateChannel("announcements", ChannelType.Text);


+ 2
- 2
docs/features/user-management.rst View File

@@ -6,7 +6,7 @@ Banning

To ban a user, invoke the Ban function on a Server object.

.. code-block:: csharp6
.. code-block:: c#

_server.Ban(_user, 30);

@@ -17,6 +17,6 @@ Kicking

To kick a user, invoke the Kick function on the User.

.. code-block:: csharp6
.. code-block:: c#

_user.Kick();

+ 4
- 171
docs/features/voice.rst View File

@@ -1,180 +1,13 @@
Voice
=====
|stub| Voice
=================

Installation
------------

Before setting up the AudioService, you must first install the package `from NuGet`_ or `GitHub`_.

Add the package to your solution, and then import the namespace ``Discord.Audio``.

.. _from NuGet: https://www.nuget.org/packages/Discord.Net.Audio/0.9.0-rc3
.. _GitHub: https://github.com/RogueException/Discord.Net/tree/master/src/Discord.Net.Audio

Setup
-----

To use audio, you must install the AudioService to your DiscordClient.

.. code-block:: csharp6
var _client = new DiscordClient();

_client.UsingAudio(x => // Opens an AudioConfigBuilder so we can configure our AudioService
{
x.Mode = AudioMode.Outgoing; // Tells the AudioService that we will only be sending audio
});

Joining a Channel
-----------------

Joining Voice Channels is pretty straight-forward, and is required to send Audio. This will also allow us to get an IAudioClient, which we will later use to send Audio.

.. code-block:: csharp6
var voiceChannel = _client.FindServers("Music Bot Server").FirstOrDefault().VoiceChannels.FirstOrDefault(); // Finds the first VoiceChannel on the server 'Music Bot Server'

var _vClient = await _client.GetService<AudioService>() // We use GetService to find the AudioService that we installed earlier. In previous versions, this was equivelent to _client.Audio()
.Join(VoiceChannel); // Join the Voice Channel, and return the IAudioClient.

The client will sustain a connection to this channel until it is kicked, disconnected from Discord, or told to Disconnect.

The IAudioClient
----------------

The IAudioClient is used to connect/disconnect to/from a Voice Channel, and to send audio to that Voice Channel.

.. function:: IAudioClient.Disconnect();
Disconnects the IAudioClient from the Voice Server.


.. function:: IAudioClient.Join(Channel);
Moves the IAudioClient to another channel on the Voice Server, or starts a connection if one has already been terminated.

.. note::

Because versions previous to 0.9 do not discretely differentiate between Text and Voice Channels, you may want to ensure that users cannot request the audio client to join a text channel, as this will throw an exception, leading to potentially unexpected behavior
.. function:: IAudioClient.Wait();
Blocks the current thread until the sending audio buffer has cleared out.

.. function:: IAudioClient.Clear();
Clears the sending audio buffer.

.. function:: IAudioClient.Send(byte[] data, int offset, int count);
Adds a stream of data to the Audio Client's internal buffer, to be sent to Discord. Follows the standard c# Stream.Send() format.
|stub-desc|

Broadcasting
------------

There are multiple approaches to broadcasting audio. Discord.Net will convert your audio packets into Opus format, so the only work you need to do is converting your audio into a format that Discord will accept. The format Discord takes is 16-bit 48000Hz PCM.

Broadcasting with NAudio
------------------------

`NAudio`_ is one of the easiest approaches to sending audio, although it is not multi-platform compatible. The following example will show you how to read an mp3 file, and send it to Discord.
You can `download NAudio from NuGet`_.

.. code-block:: csharp6

using NAudio;
using NAudio.Wave;
using NAudio.CoreAudioApi;
public void SendAudio(string filePath)
{
var channelCount = _client.GetService<AudioService>().Config.Channels; // Get the number of AudioChannels our AudioService has been configured to use.
var OutFormat = new WaveFormat(48000, 16, channelCount); // Create a new Output Format, using the spec that Discord will accept, and with the number of channels that our client supports.
using (var MP3Reader = new Mp3FileReader(filePath)) // Create a new Disposable MP3FileReader, to read audio from the filePath parameter
using (var resampler = new MediaFoundationResampler(MP3Reader, OutFormat)) // Create a Disposable Resampler, which will convert the read MP3 data to PCM, using our Output Format
{
resampler.ResamplerQuality = 60; // Set the quality of the resampler to 60, the highest quality
int blockSize = outFormat.AverageBytesPerSecond / 50; // Establish the size of our AudioBuffer
byte[] buffer = new byte[blockSize];
int byteCount;

while((byteCount = resampler.Read(buffer, 0, blockSize)) > 0) // Read audio into our buffer, and keep a loop open while data is present
{
if (byteCount < blockSize)
{
// Incomplete Frame
for (int i = byteCount; i < blockSize; i++)
buffer[i] = 0;
}
_vClient.Send(buffer, 0, blockSize); // Send the buffer to Discord
}
}

}

.. _NAudio: https://naudio.codeplex.com/
.. _download NAudio from NuGet: https://www.nuget.org/packages/NAudio/

Broadcasting with FFmpeg
------------------------

`FFmpeg`_ allows for a more advanced approach to sending audio, although it is multiplatform safe. The following example will show you how to stream a file to Discord.

.. code-block:: csharp6

public void SendAudio(string pathOrUrl)
{
var process = Process.Start(new ProcessStartInfo { // FFmpeg requires us to spawn a process and hook into its stdout, so we will create a Process
FileName = "ffmpeg",
Arguments = $"-i {pathOrUrl}" + // Here we provide a list of arguments to feed into FFmpeg. -i means the location of the file/URL it will read from
"-f s16le -ar 48000 -ac 2 pipe:1", // Next, we tell it to output 16-bit 48000Hz PCM, over 2 channels, to stdout.
UseShellExecute = false,
RedirectStandardOutput = true // Capture the stdout of the process
});
Thread.Sleep(2000); // Sleep for a few seconds to FFmpeg can prebuffer.
int blockSize = 3840; // The size of bytes to read per frame; 1920 for mono
byte[] buffer = new byte[blockSize];
int byteCount;

while (true) // Loop forever, so data will always be read
{
byteCount = process.StandardOutput.BaseStream // Access the underlying MemoryStream from the stdout of FFmpeg
.Read(buffer, 0, blockSize); // Read stdout into the buffer

if (byteCount == 0) // FFmpeg did not output anything
break; // Break out of the while(true) loop, since there was nothing to read.

_vClient.Send(buffer, 0, byteCount); // Send our data to Discord
}
_vClient.Wait(); // Wait for the Voice Client to finish sending data, as ffMPEG may have already finished buffering out a song, and it is unsafe to return now.
}

.. _FFmpeg: https://ffmpeg.org/

.. note::
The code-block above assumes that your client is configured to stream 2-channel audio. It also may prematurely end a song. FFmpeg can — especially when streaming from a URL — stop to buffer data from a source, and cause your output stream to read empty data. Because the snippet above does not safely track for failed attempts, or buffers, an empty buffer will cause playback to stop. This is also not 'memory-friendly'.

Multi-Server Broadcasting
-------------------------

.. warning:: Multi-Server broadcasting is not supported by Discord, will cause performance issues for you, and is not encouraged. Proceed with caution.

To prepare for Multi-Server Broadcasting, you must first enable it in your config.

.. code-block::csharp6
_client.UsingAudio(x =>
{
x.Mode = AudioMode.Outgoing;
x.EnableMultiserver = true; // Enable Multiserver
});

From here on, it is as easy as creating an IAudioClient for each server you want to join. See the sections on broadcasting to proceed.


Receiving
---------

**Receiving is not implemented in the latest version of Discord.Net**
---------

+ 3
- 3
docs/getting_started.rst View File

@@ -22,12 +22,12 @@ You can get Discord.Net from NuGet:

If you have trouble installing from NuGet, try installing dependencies manually.

You can also pull the latest source from `GitHub`_
You can also pull the latest source from `GitHub`_

.. _Discord.Net: https://www.nuget.org/packages/Discord.Net
.. _Discord.Net.Commands: https://www.nuget.org/packages/Discord.Net.Commands
.. _Discord.Net.Modules: https://www.nuget.org/packages/Discord.Net.Modules
.. _Discord.Net.Audio: https://www.nuget.org/packages/Discord.Net.Audio
.. _Discord.Net.Modules: https://www.nuget.org/packages/Discord.Net.Audio
.. _GitHub: https://github.com/RogueException/Discord.Net/

Async
@@ -42,7 +42,7 @@ For more information, go to `MSDN's Await-Async section`_.

Example
-------
.. literalinclude:: samples/getting_started.cs
:language: csharp6
:tab-width: 2

+ 1
- 3
docs/global.txt View File

@@ -1,4 +1,2 @@
.. |stub| unicode:: U+1F527
.. |stub-desc| replace:: This page is a placeholder and has not been written yet. It should be coming soon!
.. |outdated| replace:: **This page is currently out-of-date. The information below may be inaccurate.**
.. |incomplete| replace:: **This page is incomplete. While the information below is accurate, it should be noted that it is not thorough.**
.. |stub-desc| replace:: This page is a placeholder and has not been written yet. It should be coming soon!

+ 5
- 7
docs/index.rst View File

@@ -9,13 +9,13 @@ Feel free to join us in the `Discord API chat`_.
.. _Discord chat service: https://discordapp.com
.. _Discord API chat: https://discord.gg/0SBTUU1wZTVjAMPx

.. warning::
.. warn::

This is a beta!
This is a beta!

This library has been built thanks to a community effort reverse engineering the Discord client.
As the API is still unofficial, it may change at any time without notice, breaking this library as well.
Discord.Net itself is still in development (and is currently undergoing a rewrite) and you may encounter breaking changes throughout development until the official Discord API is released.
This library has been built thanks to a community effort reverse engineering the Discord client.
As the API is still unofficial, it may change at any time without notice, breaking this library as well.
Discord.Net itself is still in development (and is currently undergoing a rewrite) and you may encounter breaking changes throughout development until the official Discord API is released.

It is highly recommended that you always use the latest version and please report any bugs you find to our `Discord chat`_.

@@ -23,8 +23,6 @@ It is highly recommended that you always use the latest version and please repor

This Documentation is **currently undergoing a rewrite**. Some pages (marked with a wrench) are not updated, or are not completed yet.

**The documentation is currently being written to reflect ``0.9-rc4``, which can be accessed via the latest git-master.**

.. toctree::
:caption: Documentation
:maxdepth: 2


+ 7
- 7
docs/samples/events.cs View File

@@ -1,20 +1,20 @@
class Program
{
private static DiscordClient _client;
private static DiscordBotClient _client;
static void Main(string[] args)
{
_client = new DiscordClient();
var client = new DiscordClient();

// Handle Events using Lambdas
_client.MessageReceived += (s, e) =>
client.MessageCreated += (s, e) =>
{
if (!e.Message.IsAuthor)
await e.Channel.SendMessage("foo");
await client.SendMessage(e.Message.ChannelId, "foo");
}

// Handle Events using Event Handlers
EventHandler<MessageEventArgs> handler = new EventHandler<MessageEventArgs>(HandleMessageCreated);
client.MessageReceived += handler;
client.MessageCreated += handler;
}


@@ -22,6 +22,6 @@ class Program
static void HandleMessageCreated(object sender, EventArgs e)
{
if (!e.Message.IsAuthor)
await e.Channel.SendMessage("bar");
await client.SendMessage(e.Message.ChannelId, "foo");
}
}
}

+ 3
- 6
docs/samples/getting_started.cs View File

@@ -2,13 +2,10 @@ class Program
{
static void Main(string[] args)
{
var client = new DiscordClient(x =>
{
LogLevel = LogSeverity.Info
});
var client = new DiscordClient();

//Display all log messages in the console
client.Log.Message += (s, e) => Console.WriteLine($"[{e.Severity}] {e.Source}: {e.Message}");
client.LogMessage += (s, e) => Console.WriteLine($"[{e.Severity}] {e.Source}: {e.Message}");

//Echo back any message received, provided it didn't come from the bot itself
client.MessageReceived += async (s, e) =>
@@ -25,7 +22,7 @@ class Program

//If we are not a member of any server, use our invite code (made beforehand in the official Discord Client)
if (!client.Servers.Any())
await (client.GetInvite("aaabbbcccdddeee")).Accept();
await client.AcceptInvite(client.GetInvite("aaabbbcccdddeee"));
});
}
}

+ 3
- 2
docs/samples/logging.cs View File

@@ -1,5 +1,6 @@
class Program
{
private static DiscordBotClient _client;
static void Main(string[] args)
{
var client = new DiscordClient(x =>
@@ -7,13 +8,13 @@ class Program
LogLevel = LogSeverity.Info
});

client.Log.Message += (s, e) => Console.WriteLine($"[{e.Severity}] {e.Source}: {e.Message}");
_client.Log.Message += (s, e) => Console.WriteLine($"[{e.Severity}] {e.Source}: {e.Message}");

client.ExecuteAndWait(async () =>
{
await client.Connect("discordtest@email.com", "Password123");
if (!client.Servers.Any())
await (client.GetInvite("aaabbbcccdddeee")).Accept();
await client.AcceptInvite("aaabbbcccdddeee");
});
}
}

+ 11
- 5
docs/samples/permissions.cs View File

@@ -1,8 +1,14 @@

// Find a User's Channel Permissions
var UserPerms = _channel.GetPermissionsRule(_user);
var userChannelPermissions = user.GetPermissions(channel);

// Find a User's Server Permissions
var userServerPermissions = user.ServerPermissions();
var userServerPermissions = server.GetPermissions(user);

// Set a User's Channel Permissions
// Set a User's Channel Permissions (using DualChannelPermissions)

var NewOverwrites = new ChannelPermissionOverrides(sendMessages: PermValue.Deny);
await channel.AddPermissionsRule(_user, NewOverwrites);
var userPerms = user.GetPermissions(channel);
userPerms.ReadMessageHistory = false;
userPerms.AttachFiles = null;
channel.AddPermissionsRule(user, userPerms);
}

+ 4
- 4
global.json View File

@@ -1,6 +1,6 @@
{
"projects": [ "src" ],
"sdk": {
"version": "1.0.0-rc1-update1"
}
"projects": [ "src" ],
"sdk": {
"version": "1.0.0-rc2-20221"
}
}

+ 0
- 21
ref/Discord.Net.xproj View File

@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>5b2afee6-fff6-4ba2-be12-61b283b72ac0</ProjectGuid>
<RootNamespace>Discord</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<ProduceOutputsOnBuild>True</ProduceOutputsOnBuild>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

+ 0
- 75
ref/DiscordClient.cs View File

@@ -1,75 +0,0 @@
using Discord.Net.Rest;
using Discord.Net.WebSockets;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace Discord
{
/// <summary> Provides a connection to the DiscordApp service. </summary>
public class DiscordClient : IDisposable
{
public event EventHandler<LogMessageEventArgs> Log = delegate { };

public event EventHandler LoggedIn = delegate { };
public event EventHandler LoggedOut = delegate { };
public event EventHandler Connected = delegate { };
public event EventHandler Disconnected = delegate { };
public event EventHandler<ServerEventArgs> VoiceConnected = delegate { };
public event EventHandler<ServerEventArgs> VoiceDisconnected = delegate { };

public event EventHandler<ChannelEventArgs> ChannelCreated = delegate { };
public event EventHandler<ChannelUpdatedEventArgs> ChannelUpdated = delegate { };
public event EventHandler<ChannelEventArgs> ChannelDestroyed = delegate { };
public event EventHandler<MessageEventArgs> MessageAcknowledged = delegate { };
public event EventHandler<MessageEventArgs> MessageDeleted = delegate { };
public event EventHandler<MessageEventArgs> MessageReceived = delegate { };
public event EventHandler<MessageEventArgs> MessageSent = delegate { };
public event EventHandler<MessageUpdatedEventArgs> MessageUpdated = delegate { };
public event EventHandler<ProfileUpdatedEventArgs> ProfileUpdated = delegate { };
public event EventHandler<RoleEventArgs> RoleCreated = delegate { };
public event EventHandler<RoleUpdatedEventArgs> RoleUpdated = delegate { };
public event EventHandler<RoleEventArgs> RoleDeleted = delegate { };
public event EventHandler<ServerEventArgs> JoinedServer = delegate { };
public event EventHandler<ServerEventArgs> LeftServer = delegate { };
public event EventHandler<ServerEventArgs> ServerAvailable = delegate { };
public event EventHandler<ServerUpdatedEventArgs> ServerUpdated = delegate { };
public event EventHandler<ServerEventArgs> ServerUnavailable = delegate { };
public event EventHandler<UserEventArgs> UserBanned = delegate { };
public event EventHandler<TypingEventArgs> UserIsTyping = delegate { };
public event EventHandler<UserEventArgs> UserJoined = delegate { };
public event EventHandler<UserEventArgs> UserLeft = delegate { };
public event EventHandler<UserUpdatedEventArgs> UserUpdated = delegate { };
public event EventHandler<UserEventArgs> UserUnbanned = delegate { };

public MessageQueue MessageQueue { get; }
public IRestClient RestClient { get; }
public GatewaySocket GatewaySocket { get; }
public Profile CurrentUser { get; }

public DiscordClient() { }
public DiscordClient(DiscordConfig config) { }
public Task Login(string token) => null;
public Task Logout() => null;

public Task Connect() => null;
public Task Connect(int connectionId, int totalConnections) => null;
public Task Disconnect() => null;

public Task<IEnumerable<PrivateChannel>> GetPrivateChannels() => null;
public Task<PrivateChannel> GetPrivateChannel(ulong userId) => null;
public Task<Invite> GetInvite(string inviteIdOrXkcd) => null;
public Task<IReadOnlyList<Region>> GetRegions() => null;
public Task<Region> GetRegion(string id) => null;
public Task<IEnumerable<Server>> GetServers() => null;
public Task<Server> GetServer(ulong id) => null;

public Task<PrivateChannel> CreatePrivateChannel(ulong userId) => null;
public Task<Server> CreateServer(string name, Region region, ImageType iconType = ImageType.None, Stream icon = null) => null;

public void Dispose() { }
}
}

+ 0
- 65
ref/DiscordConfig.cs View File

@@ -1,65 +0,0 @@
using Discord.Net.Rest;
using Discord.Net.WebSockets;
using System.Reflection;

namespace Discord
{
public class DiscordConfig
{
public const int MaxMessageSize = 2000;
public const int MaxMessagesPerBatch = 100;

public const string LibName = "Discord.Net";
public static string LibVersion => typeof(DiscordConfig).GetTypeInfo().Assembly?.GetName().Version.ToString(3) ?? "Unknown";
public const string LibUrl = "https://github.com/RogueException/Discord.Net";

public const string ClientAPIUrl = "https://discordapp.com/api/";
public const string CDNUrl = "https://cdn.discordapp.com/";
public const string InviteUrl = "https://discord.gg/";

/// <summary> Gets or sets name of your application, used in the user agent. </summary>
public string AppName { get; set; } = null;
/// <summary> Gets or sets url to your application, used in the user agent. </summary>
public string AppUrl { get; set; } = null;
/// <summary> Gets or sets the version of your application, used in the user agent. </summary>
public string AppVersion { get; set; } = null;

/// <summary> Gets or sets the minimum log level severity that will be sent to the LogMessage event. </summary>
public LogSeverity LogLevel { get; set; } = LogSeverity.Info;

/// <summary> Gets or sets the time (in milliseconds) to wait for the websocket to connect and initialize. </summary>
public int ConnectionTimeout { get; set; } = 30000;
/// <summary> Gets or sets the time (in milliseconds) to wait after an unexpected disconnect before reconnecting. </summary>
public int ReconnectDelay { get; set; } = 1000;
/// <summary> Gets or sets the time (in milliseconds) to wait after an reconnect fails before retrying. </summary>
public int FailedReconnectDelay { get; set; } = 15000;

//Performance

/// <summary> Gets or sets the number of messages per channel that should be kept in cache. Setting this to zero disables the message cache entirely. </summary>
public int MessageCacheSize { get; set; } = 100;
/// <summary>
/// Gets or sets whether the permissions cache should be used.
/// This makes operations such as User.GetPermissions(Channel), User.ServerPermissions, Channel.GetUser, and Channel.Members much faster while increasing memory usage.
/// </summary>
public bool UsePermissionsCache { get; set; } = true;
/// <summary> Gets or sets whether the a copy of a model is generated on an update event to allow you to check which properties changed. </summary>
public bool EnablePreUpdateEvents { get; set; } = true;
/// <summary>
/// Gets or sets the max number of users a server may have for offline users to be included in the READY packet. Max is 250.
/// Decreasing this may reduce CPU usage while increasing login time and network usage.
/// </summary>
public int LargeThreshold { get; set; } = 250;

//Engines

/// <summary> Gets or sets the REST engine to use.. Defaults to DefaultRestClientProvider, which uses .Net's HttpClient class. </summary>
public IRestClientProvider RestClientProvider { get; set; } = null;
/// <summary>
/// Gets or sets the WebSocket engine to use. Defaults to DefaultWebSocketProvider, which uses .Net's WebSocketClient class.
/// WebSockets are only used if DiscordClient.Connect() is called.
/// </summary>
public IWebSocketProvider WebSocketProvider { get; set; } = null;
}
}


+ 0
- 43
ref/Entities/Channels/IPublicChannel.cs View File

@@ -1,43 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Discord
{
public interface IPublicChannel : IChannel
{
/// <summary> Gets the server this channel is a member of. </summary>
Server Server { get; }
/// <summary> Gets a collection of permission overwrites for this channel. </summary>
IEnumerable<PermissionOverwriteEntry> PermissionOverwrites { get; }
/// <summary> Gets the position of this public channel relative to others of the same type. </summary>
int Position { get; }

/// <summary> Gets a user in this channel with the given id. </summary>
new Task<ServerUser> GetUser(ulong id);
/// <summary> Gets a collection of all users in this channel. </summary>
new Task<IEnumerable<ServerUser>> GetUsers();

/// <summary> Gets the permission overwrite for a specific user, or null if one does not exist. </summary>
OverwritePermissions? GetPermissionOverwrite(ServerUser user);
/// <summary> Gets the permission overwrite for a specific role, or null if one does not exist. </summary>
OverwritePermissions? GetPermissionOverwrite(Role role);
/// <summary> Downloads a collection of all invites to this server. </summary>
Task<IEnumerable<Invite>> GetInvites();

/// <summary> Adds or updates the permission overwrite for the given user. </summary>
Task UpdatePermissionOverwrite(ServerUser user, OverwritePermissions permissions);
/// <summary> Adds or updates the permission overwrite for the given role. </summary>
Task UpdatePermissionOverwrite(Role role, OverwritePermissions permissions);
/// <summary> Removes the permission overwrite for the given user, if one exists. </summary>
Task RemovePermissionOverwrite(ServerUser user);
/// <summary> Removes the permission overwrite for the given role, if one exists. </summary>
Task RemovePermissionOverwrite(Role role);

/// <summary> Creates a new invite to this channel. </summary>
/// <param name="maxAge"> Time (in seconds) until the invite expires. Set to null to never expire. </param>
/// <param name="maxUses"> The max amount of times this invite may be used. Set to null to have unlimited uses. </param>
/// <param name="tempMembership"> If true, a user accepting this invite will be kicked from the server after closing their client. </param>
/// <param name="withXkcd"> If true, creates a human-readable link. Not supported if maxAge is set to null. </param>
Task<Invite> CreateInvite(int? maxAge = 1800, int? maxUses = null, bool tempMembership = false, bool withXkcd = false);
}
}

+ 0
- 55
ref/Entities/Channels/PrivateChannel.cs View File

@@ -1,55 +0,0 @@
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;

namespace Discord
{
public class PrivateChannel : ITextChannel, IChannel
{
/// <inheritdoc />
public DiscordClient Discord { get; }
/// <inheritdoc />
public EntityState State { get; }
/// <inheritdoc />
public ulong Id { get; }
/// <inheritdoc />
public PrivateUser Recipient { get; }
/// <inheritdoc />
public PrivateUser CurrentUser { get; }

/// <inheritdoc />
ChannelType IChannel.Type => ChannelType.Private | ChannelType.Text;
/// <inheritdoc />
public string Name { get; }

/// <inheritdoc />
public Task<PrivateUser> GetUser(ulong id) => null;
/// <inheritdoc />
Task<IUser> IChannel.GetUser(ulong id) => null;
/// <inheritdoc />
public Task<IEnumerable<PrivateUser>> GetUsers() => null;
/// <inheritdoc />
Task<IEnumerable<IUser>> IChannel.GetUsers() => null;
/// <inheritdoc />
public Task<Message> GetMessage(ulong id) => null;
/// <inheritdoc />
public Task<IEnumerable<Message>> GetMessages(int limit = 100) => null;
/// <inheritdoc />
public Task<IEnumerable<Message>> GetMessages(int limit = 100, ulong? relativeMessageId = null, Relative relativeDir = Relative.Before) => null;

/// <inheritdoc />
public Task<Message> SendMessage(string text, bool isTTS = false) => null;
/// <inheritdoc />
public Task<Message> SendFile(string filePath, string text = null, bool isTTS = false) => null;
/// <inheritdoc />
public Task<Message> SendFile(Stream stream, string filename, string text = null, bool isTTS = false) => null;

/// <inheritdoc />
public Task SendIsTyping() => null;

/// <inheritdoc />
public Task Update() => null;
/// <inheritdoc />
public Task Delete() => null;
}
}

+ 0
- 91
ref/Entities/Channels/TextChannel.cs View File

@@ -1,91 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;

namespace Discord
{
public class TextChannel : ITextChannel, IMentionable, IModifiable<TextChannel.Properties>
{
public sealed class Properties
{
public string Name { get; }
public string Topic { get; }
public int Position { get; }
}

/// <inheritdoc />
public EntityState State { get; }
/// <inheritdoc />
public ulong Id { get; }
/// <inheritdoc />
public Server Server { get; }

/// <inheritdoc />
public DiscordClient Discord { get; }
/// <inheritdoc />
public ChannelType Type => ChannelType.Public | ChannelType.Text;

/// <inheritdoc />
public string Name { get; }
/// <inheritdoc />
public string Topic { get; }
/// <inheritdoc />
public int Position { get; }

/// <inheritdoc />
public string Mention { get; }
/// <inheritdoc />
public IEnumerable<PermissionOverwriteEntry> PermissionOverwrites { get; }

/// <inheritdoc />
public OverwritePermissions? GetPermissionOverwrite(ServerUser user) => null;
/// <inheritdoc />
public OverwritePermissions? GetPermissionOverwrite(Role role) => null;
/// <inheritdoc />
public Task<ServerUser> GetUser(ulong id) => null;
/// <inheritdoc />
Task<IUser> IChannel.GetUser(ulong id) => null;
/// <inheritdoc />
public Task<IEnumerable<ServerUser>> GetUsers() => null;
/// <inheritdoc />
Task<IEnumerable<IUser>> IChannel.GetUsers() => null;
/// <inheritdoc />
public Task<Message> GetMessage(ulong id) => null;
/// <inheritdoc />
public Task<IEnumerable<Message>> GetMessages(int limit = 100) => null;
/// <inheritdoc />
public Task<IEnumerable<Message>> GetMessages(int limit = 100, ulong? relativeMessageId = null, Relative relativeDir = Relative.Before) => null;
/// <inheritdoc />
public Task<IEnumerable<Invite>> GetInvites() => null;
/// <inheritdoc />
public Task UpdatePermissionOverwrite(ServerUser user, OverwritePermissions permissions) => null;
/// <inheritdoc />
public Task UpdatePermissionOverwrite(Role role, OverwritePermissions permissions) => null;
/// <inheritdoc />
public Task RemovePermissionOverwrite(ServerUser user) => null;
/// <inheritdoc />
public Task RemovePermissionOverwrite(Role role) => null;

/// <inheritdoc />
public Task<Message> SendMessage(string text, bool isTTS = false) => null;
/// <inheritdoc />
public Task<Message> SendFile(string filePath, string text = null, bool isTTS = false) => null;
/// <inheritdoc />
public Task<Message> SendFile(Stream stream, string filename, string text = null, bool isTTS = false) => null;

/// <inheritdoc />
public Task SendIsTyping() => null;

/// <inheritdoc />
public Task<Invite> CreateInvite(int? maxAge = 1800, int? maxUses = null, bool tempMembership = false, bool withXkcd = false) => null;

/// <inheritdoc />
public Task Update() => null;
/// <inheritdoc />
public Task Modify(Action<Properties> func) => null;
/// <inheritdoc />
public Task Delete() => null;
}
}

+ 0
- 74
ref/Entities/Channels/VoiceChannel.cs View File

@@ -1,74 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Discord
{
public class VoiceChannel : IPublicChannel, IModifiable<VoiceChannel.Properties>
{
public sealed class Properties
{
public string Name { get; }
public int Bitrate { get; set; }
public int Position { get; }
}

/// <inheritdoc />
public ulong Id { get; }
/// <inheritdoc />
public EntityState State { get; }
/// <inheritdoc />
public Server Server { get; }

/// <inheritdoc />
public DiscordClient Discord { get; }
/// <inheritdoc />
ChannelType IChannel.Type => ChannelType.Public | ChannelType.Voice;

/// <inheritdoc />
public string Name { get; }
/// <inheritdoc />
public int Position { get; }
/// <inheritdoc />
public int Bitrate { get; }

/// <inheritdoc />
public string Mention { get; }
/// <inheritdoc />
public IEnumerable<PermissionOverwriteEntry> PermissionOverwrites { get; }

/// <inheritdoc />
public OverwritePermissions? GetPermissionOverwrite(ServerUser user) => null;
/// <inheritdoc />
public OverwritePermissions? GetPermissionOverwrite(Role role) => null;
/// <inheritdoc />
public Task<ServerUser> GetUser(ulong id) => null;
/// <inheritdoc />
Task<IUser> IChannel.GetUser(ulong id) => null;
/// <inheritdoc />
public Task<IEnumerable<ServerUser>> GetUsers() => null;
/// <inheritdoc />
Task<IEnumerable<IUser>> IChannel.GetUsers() => null;
/// <inheritdoc />
public Task<IEnumerable<Invite>> GetInvites() => null;
/// <inheritdoc />
public Task UpdatePermissionOverwrite(ServerUser user, OverwritePermissions permissions) => null;
/// <inheritdoc />
public Task UpdatePermissionOverwrite(Role role, OverwritePermissions permissions) => null;
/// <inheritdoc />
public Task RemovePermissionOverwrite(ServerUser user) => null;
/// <inheritdoc />
public Task RemovePermissionOverwrite(Role role) => null;

/// <inheritdoc />
public Task<Invite> CreateInvite(int? maxAge = 1800, int? maxUses = null, bool tempMembership = false, bool withXkcd = false) => null;

/// <inheritdoc />
public Task Update() => null;
/// <inheritdoc />
public Task Modify(Action<Properties> func) => null;
/// <inheritdoc />
public Task Delete() => null;
}
}

+ 0
- 17
ref/Entities/Color.cs View File

@@ -1,17 +0,0 @@
namespace Discord
{
public class Color
{
public static readonly Color Default = new Color(0);
public uint RawValue { get; }
public Color(uint rawValue) { }
public Color(byte r, byte g, byte b) { }
public Color(float r, float g, float b) { }
public byte R { get; }
public byte G { get; }
public byte B { get; }
}
}

+ 0
- 11
ref/Entities/IModifiable.cs View File

@@ -1,11 +0,0 @@
using System;
using System.Threading.Tasks;

namespace Discord
{
public interface IModifiable<TModel>
{
/// <summary> Modifies one or more of the properties of this object. </summary>
Task Modify(Action<TModel> func);
}
}

+ 0
- 37
ref/Entities/Invite/BasicInvite.cs View File

@@ -1,37 +0,0 @@
using System.Threading.Tasks;

namespace Discord
{
public class BasicInvite : IEntity<string>
{
public class TargetInfo
{
public ulong Id { get; }
public string Name { get; }
}
public class InviterInfo
{
public ulong Id { get; }
public string Name { get; }
public ushort Discriminator { get; }
public string AvatarId { get; }
public string AvatarUrl { get; }
}

string IEntity<string>.Id => Code;
public DiscordClient Discord { get; }
public EntityState State { get; }

public string Code { get; }
public string XkcdCode { get; }

public TargetInfo Server { get; }
public TargetInfo Channel { get; }

public string Url { get; }

public Task Accept() => null;

public virtual Task Update() => null;
}
}

+ 0
- 18
ref/Entities/Invite/Invite.cs View File

@@ -1,18 +0,0 @@
using System;
using System.Threading.Tasks;

namespace Discord
{
public class Invite : BasicInvite
{
public int? MaxAge { get; }
public int Uses { get; }
public int? MaxUses { get; }
public bool IsRevoked { get; }
public bool IsTemporary { get; }
public DateTime CreatedAt { get; }

public override Task Update() => null;
public Task Delete() => null;
}
}

+ 0
- 68
ref/Entities/Message.cs View File

@@ -1,68 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Discord
{
public class Message : IEntity<ulong>
{
public class Attachment : File
{
public string Id { get; }
public int Size { get; }
public string Filename { get; }
}

public class Embed
{
public string Url { get; }
public string Type { get; }
public string Title { get; }
public string Description { get; }
public EmbedLink Author { get; }
public EmbedLink Provider { get; }
public File Thumbnail { get; }
public File Video { get; }
}

public class EmbedLink
{
public string Url { get; }
public string Name { get; }
}

public class File
{
public string Url { get; }
public string ProxyUrl { get; }
public int? Width { get; }
public int? Height { get; }
}
public ulong Id { get; }
public DiscordClient Discord { get; }
public EntityState State { get; }

public ITextChannel Channel { get; }
public IUser User { get; }
public bool IsTTS { get; }
public string RawText { get; }
public string Text { get; }
public DateTime Timestamp { get; }
public DateTime? EditedTimestamp { get; }
public Attachment[] Attachments { get; }
public Embed[] Embeds { get; }
public IReadOnlyList<ServerUser> MentionedUsers { get; }
public IReadOnlyList<IPublicChannel> MentionedChannels { get; }
public IReadOnlyList<Role> MentionedRoles { get; }
public Server Server => null;
public bool IsAuthor => false;
public bool IsMentioningMe(bool includeRoles = false) => false;

public Task Update() => null;
public Task Delete() => null;
}
}

+ 0
- 53
ref/Entities/Permissions/ChannelPermissions.cs View File

@@ -1,53 +0,0 @@
namespace Discord
{
public struct ChannelPermissions
{
public static ChannelPermissions None { get; }
public static ChannelPermissions TextOnly { get; }
public static ChannelPermissions PrivateOnly { get; }
public static ChannelPermissions VoiceOnly { get; }
public static ChannelPermissions All(ChannelType channelType) => default(ChannelPermissions);

public uint RawValue { get; }

public bool CreateInstantInvite { get; }
public bool ManagePermission { get; }
public bool ManageChannel { get; }

public bool ReadMessages { get; }
public bool SendMessages { get; }
public bool SendTTSMessages { get; }
public bool ManageMessages { get; }
public bool EmbedLinks { get; }
public bool AttachFiles { get; }
public bool ReadMessageHistory { get; }
public bool MentionEveryone { get; }

public bool Connect { get; }
public bool Speak { get; }
public bool MuteMembers { get; }
public bool DeafenMembers { get; }
public bool MoveMembers { get; }
public bool UseVoiceActivation { get; }

public ChannelPermissions(bool? createInstantInvite = null, bool? managePermissions = null,
bool? manageChannel = null, bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null,
bool? manageMessages = null, bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null,
bool? mentionEveryone = null, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null,
bool? moveMembers = null, bool? useVoiceActivation = null)
: this()
{
}
public ChannelPermissions(uint rawValue)
: this()
{
}

public ChannelPermissions Modify(bool? createInstantInvite = null, bool? managePermissions = null,
bool? manageChannel = null, bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null,
bool? manageMessages = null, bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null,
bool? mentionEveryone = null, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null,
bool? moveMembers = null, bool? useVoiceActivation = null)
=> default(ChannelPermissions);
}
}

+ 0
- 50
ref/Entities/Permissions/OverwritePermissions.cs View File

@@ -1,50 +0,0 @@
namespace Discord
{
public struct OverwritePermissions
{
public static OverwritePermissions InheritAll { get; }

public uint AllowValue { get; }
public uint DenyValue { get; }
public PermValue CreateInstantInvite { get; }
public PermValue ManagePermissions { get; }
public PermValue ManageChannel { get; }
public PermValue ReadMessages { get; }
public PermValue SendMessages { get; }
public PermValue SendTTSMessages { get; }
public PermValue ManageMessages { get; }
public PermValue EmbedLinks { get; }
public PermValue AttachFiles { get; }
public PermValue ReadMessageHistory { get; }
public PermValue MentionEveryone { get; }

public PermValue Connect { get; }
public PermValue Speak { get; }
public PermValue MuteMembers { get; }
public PermValue DeafenMembers { get; }
public PermValue MoveMembers { get; }
public PermValue UseVoiceActivation { get; }

public OverwritePermissions(PermValue? createInstantInvite = null, PermValue? managePermissions = null,
PermValue? manageChannel = null, PermValue? readMessages = null, PermValue? sendMessages = null, PermValue? sendTTSMessages = null,
PermValue? manageMessages = null, PermValue? embedLinks = null, PermValue? attachFiles = null, PermValue? readMessageHistory = null,
PermValue? mentionEveryone = null, PermValue? connect = null, PermValue? speak = null, PermValue? muteMembers = null, PermValue? deafenMembers = null,
PermValue? moveMembers = null, PermValue? useVoiceActivation = null)
: this()
{
}

public OverwritePermissions(uint allow = 0, uint deny = 0)
: this()
{
}

public OverwritePermissions Modify(PermValue? createInstantInvite = null, PermValue? managePermissions = null,
PermValue? manageChannel = null, PermValue? readMessages = null, PermValue? sendMessages = null, PermValue? sendTTSMessages = null,
PermValue? manageMessages = null, PermValue? embedLinks = null, PermValue? attachFiles = null, PermValue? readMessageHistory = null,
PermValue? mentionEveryone = null, PermValue? connect = null, PermValue? speak = null, PermValue? muteMembers = null, PermValue? deafenMembers = null,
PermValue? moveMembers = null, PermValue? useVoiceActivation = null)
=> default(OverwritePermissions);
}
}

+ 0
- 9
ref/Entities/Permissions/PermissionOverwriteEntry.cs View File

@@ -1,9 +0,0 @@
namespace Discord
{
public struct PermissionOverwriteEntry
{
public PermissionTarget TargetType { get; }
public ulong TargetId { get; }
public OverwritePermissions Permissions { get; }
}
}

+ 0
- 55
ref/Entities/Permissions/ServerPermissions.cs View File

@@ -1,55 +0,0 @@
namespace Discord
{
public struct ServerPermissions
{
public static ServerPermissions None { get; }
public static ServerPermissions All { get; }

public uint RawValue { get; }

public bool CreateInstantInvite { get; }
public bool BanMembers { get; }
public bool KickMembers { get; }
public bool ManageRoles { get; }
public bool ManageChannels { get; }
public bool ManageServer { get; }

public bool ReadMessages { get; }
public bool SendMessages { get; }
public bool SendTTSMessages { get; }
public bool ManageMessages { get; }
public bool EmbedLinks { get; }
public bool AttachFiles { get; }
public bool ReadMessageHistory { get; }
public bool MentionEveryone { get; }

public bool Connect { get; }
public bool Speak { get; }
public bool MuteMembers { get; }
public bool DeafenMembers { get; }
public bool MoveMembers { get; }
public bool UseVoiceActivation { get; }

public ServerPermissions(bool? createInstantInvite = null, bool? manageRoles = null,
bool? kickMembers = null, bool? banMembers = null, bool? manageChannel = null, bool? manageServer = null,
bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null,
bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null,
bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null,
bool? moveMembers = null, bool? useVoiceActivation = null)
: this()
{
}
public ServerPermissions(uint rawValue)
: this()
{
}

public ServerPermissions Modify(bool? createInstantInvite = null, bool? manageRoles = null,
bool? kickMembers = null, bool? banMembers = null, bool? manageChannel = null, bool? manageServer = null,
bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null,
bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null,
bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null,
bool? moveMembers = null, bool? useVoiceActivation = null)
=> default(ServerPermissions);
}
}

+ 0
- 25
ref/Entities/Profile.cs View File

@@ -1,25 +0,0 @@
using System.Threading.Tasks;

namespace Discord
{
public class Profile : IEntity<ulong>
{
public ulong Id { get; }
public DiscordClient Discord { get; }
public EntityState State { get; }

public string AvatarId { get; }
public string AvatarUrl { get; }
public ushort Discriminator { get; }
public string CurrentGame { get; }
public UserStatus Status { get; }
public string Mention { get; }
public string Email { get; }
public bool? IsVerified { get; }

public string Name { get; set; }

public Task Update() => null;
public Task Delete() => null;
}
}

+ 0
- 11
ref/Entities/Region.cs View File

@@ -1,11 +0,0 @@
namespace Discord
{
public class Region
{
public string Id { get; }
public string Name { get; }
public string Hostname { get; }
public int Port { get; }
public bool Vip { get; }
}
}

+ 0
- 29
ref/Entities/Role.cs View File

@@ -1,29 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Discord
{
public class Role : IEntity<ulong>, IMentionable
{
public ulong Id { get; }
public DiscordClient Discord { get; }
public EntityState State { get; }

public Server Server { get; }
public string Name { get; }
public bool IsHoisted { get; }
public int Position { get; }
public bool IsManaged { get; }
public ServerPermissions Permissions { get; }
public Color Color { get; }
public bool IsEveryone { get; }
public IEnumerable<ServerUser> Members { get; }
public string Mention { get; }

public Task Update() => null;
public Task Delete() => null;
}
}

+ 0
- 67
ref/Entities/Server.cs View File

@@ -1,67 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Discord
{
public class Server : IEntity<ulong>
{
public class Emoji
{
public string Id { get; }
public string Name { get; }
public bool IsManaged { get; }
public bool RequireColons { get; }
public IEnumerable<Role> Roles { get; }
}
public ulong Id { get; }
public DiscordClient Discord { get; }
public EntityState State { get; }

public ServerUser CurrentUser { get; }
public string IconId { get; }
public string SplashId { get; }
public string IconUrl { get; }
public string SplashUrl { get; }
public int ChannelCount { get; }
public int UserCount { get; }
public int RoleCount { get; }
public TextChannel DefaultChannel { get; }
public Role EveryoneRole { get; }
public IEnumerable<string> Features { get; }
public IEnumerable<Emoji> CustomEmojis { get; }
public IEnumerable<IChannel> Channels { get; }
public IEnumerable<TextChannel> TextChannels { get; }
public IEnumerable<VoiceChannel> VoiceChannels { get; }
public IEnumerable<ServerUser> Users { get; }
public IEnumerable<Role> Roles { get; }

public string Name { get; set; }
public Region Region { get; set; }
public int AFKTimeout { get; set; }
public DateTime JoinedAt { get; set; }
public ServerUser Owner { get; set; }
public VoiceChannel AFKChannel { get; set; }
public Task<IPublicChannel> GetChannel(ulong id) => null;
public Task<IPublicChannel> GetChannel(string mention) => null;
public Task<Role> GetRole(ulong id) => null;
public Task<ServerUser> GetUser(ulong id) => null;
public Task<ServerUser> GetUser(string name, ushort discriminator) => null;
public Task<ServerUser> GetUser(string mention) => null;
public Task<IEnumerable<ServerUser>> GetBans() => null;
public Task<IEnumerable<Invite>> GetInvites() => null;

public Task<TextChannel> CreateTextChannel(string name) => null;
public Task<VoiceChannel> CreateVoiceChannel(string name) => null;
public Task<Invite> CreateInvite(int? maxAge = 1800, int? maxUses = null, bool tempMembership = false, bool withXkcd = false) => null;
public Task<Role> CreateRole(string name, ServerPermissions? permissions = null, Color color = null, bool isHoisted = false) => null;
public Task<int> PruneUsers(int days = 30, bool simulate = false) => null;

public Task Update() => null;
public Task Leave() => null;
public Task Delete() => null;
}
}

+ 0
- 19
ref/Entities/Users/IUser.cs View File

@@ -1,19 +0,0 @@
using System.Threading.Tasks;

namespace Discord
{
public interface IUser : IEntity<ulong>, IMentionable
{
bool IsPrivate { get; }

string Name { get; }
ushort Discriminator { get; }
bool IsBot { get; }
string AvatarId { get; }
string AvatarUrl { get; }
string CurrentGame { get; }
UserStatus Status { get; }

Task<PrivateChannel> GetPrivateChannel();
}
}

+ 0
- 43
ref/Entities/Users/PrivateUser.cs View File

@@ -1,43 +0,0 @@
using System.Threading.Tasks;

namespace Discord
{
//TODO: Should this be linked directly to the Profile when it represents us, instead of maintaining a cache of values?
public class PrivateUser : IUser
{
/// <inheritdoc />
public EntityState State { get; internal set; }
/// <inheritdoc />
public ulong Id { get; }
/// <summary> Returns the private channel for this user. </summary>
public PrivateChannel Channel { get; }

/// <inheritdoc />
bool IUser.IsPrivate => true;

/// <inheritdoc />
public string Name { get; }
/// <inheritdoc />
public ushort Discriminator { get; }
/// <inheritdoc />
public bool IsBot { get; }
/// <inheritdoc />
public string AvatarId { get; }
/// <inheritdoc />
public string CurrentGame { get; }
/// <inheritdoc />
public UserStatus Status { get; }

/// <inheritdoc />
public DiscordClient Discord => Channel.Discord;
/// <inheritdoc />
public string AvatarUrl { get; }
/// <inheritdoc />
public string Mention { get; }

/// <inheritdoc />
Task<PrivateChannel> IUser.GetPrivateChannel() => Task.FromResult(Channel);

public Task Update() => null;
}
}

+ 0
- 73
ref/Entities/Users/ServerUser.cs View File

@@ -1,73 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Discord
{
public class ServerUser : IUser
{
/// <inheritdoc />
public EntityState State { get; }
/// <inheritdoc />
public ulong Id { get; }
/// <summary> Returns the private channel for this user. </summary>
public Server Server { get; }

/// <inheritdoc />
bool IUser.IsPrivate => false;

/// <inheritdoc />
public string Name { get; }
/// <inheritdoc />
public ushort Discriminator { get; }
/// <inheritdoc />
public bool IsBot { get; }
/// <inheritdoc />
public string AvatarId { get; }
/// <inheritdoc />
public string CurrentGame { get; }
/// <inheritdoc />
public UserStatus Status { get; }
/// <inheritdoc />
public DateTime JoinedAt { get; }
/// <inheritdoc />
public IReadOnlyList<Role> Roles { get; }

/// <summary> Returns true if this user has marked themselves as muted. </summary>
public bool IsSelfMuted { get; }
/// <summary> Returns true if this user has marked themselves as deafened. </summary>
public bool IsSelfDeafened { get; }
/// <summary> Returns true if the server is blocking audio from this user. </summary>
public bool IsServerMuted { get; }
/// <summary> Returns true if the server is blocking audio to this user. </summary>
public bool IsServerDeafened { get; }
/// <summary> Returns true if the server is temporarily blocking audio to/from this user. </summary>
public bool IsServerSuppressed { get; }
/// <summary> Gets this user's current voice channel. </summary>
public VoiceChannel VoiceChannel { get; }

/// <inheritdoc />
public DiscordClient Discord { get; }
/// <inheritdoc />
public string AvatarUrl { get; }
/// <inheritdoc />
public string Mention { get; }
public ServerPermissions ServerPermissions { get; }

public ChannelPermissions GetPermissions(IPublicChannel channel) => default(ChannelPermissions);
/// <inheritdoc />
public Task<PrivateChannel> GetPrivateChannel() => null;
public Task<IEnumerable<IPublicChannel>> GetChannels() => null;

public bool HasRole(Role role) => false;

public Task AddRoles(params Role[] roles) => null;
public Task RemoveRoles(params Role[] roles) => null;

public Task Update() => null;
public Task Kick() => null;
public Task Ban(int pruneDays = 0) => null;
public Task Unban() => null;
}
}

+ 0
- 13
ref/Enums/ChannelType.cs View File

@@ -1,13 +0,0 @@
using System;

namespace Discord
{
[Flags]
public enum ChannelType : byte
{
Public = 0x01,
Private = 0x02,
Text = 0x10,
Voice = 0x20
}
}

+ 0
- 10
ref/Enums/ConnectionState.cs View File

@@ -1,10 +0,0 @@
namespace Discord
{
public enum ConnectionState
{
Disconnected,
Connecting,
Connected,
Disconnecting
}
}

+ 0
- 18
ref/Enums/EntityState.cs View File

@@ -1,18 +0,0 @@
namespace Discord
{
public enum EntityState : byte
{
/// <summary> Object is not attached to a cache manager nor receiving live updates. </summary>
Detached = 0,
/// <summary> Object is attached to a cache manager and receiving live updates. </summary>
Attached,
/// <summary> Object was deleted. </summary>
Deleted,
/// <summary> Object is currently waiting to be created. </summary>
Queued,
/// <summary> Object's creation was aborted. </summary>
Aborted,
/// <summary> Object's creation failed. </summary>
Failed
}
}

+ 0
- 9
ref/Enums/ImageType.cs View File

@@ -1,9 +0,0 @@
namespace Discord
{
public enum ImageType
{
None,
Jpeg,
Png
}
}

+ 0
- 12
ref/Enums/LogSeverity.cs View File

@@ -1,12 +0,0 @@
namespace Discord
{
public enum LogSeverity
{
Critical = 0,
Error = 1,
Warning = 2,
Info = 3,
Verbose = 4,
Debug = 5
}
}

+ 0
- 9
ref/Enums/PermValue.cs View File

@@ -1,9 +0,0 @@
namespace Discord
{
public enum PermValue
{
Allow,
Deny,
Inherit
}
}

+ 0
- 8
ref/Enums/PermissionTarget.cs View File

@@ -1,8 +0,0 @@
namespace Discord
{
public enum PermissionTarget
{
Role,
User
}
}

+ 0
- 8
ref/Enums/Relative.cs View File

@@ -1,8 +0,0 @@
namespace Discord
{
public enum Relative
{
Before,
After
}
}

+ 0
- 9
ref/Enums/UserStatus.cs View File

@@ -1,9 +0,0 @@
namespace Discord
{
public enum UserStatus
{
Online,
Idle,
Offline
}
}

+ 0
- 9
ref/Events/ChannelEventArgs.cs View File

@@ -1,9 +0,0 @@
using System;

namespace Discord
{
public class ChannelEventArgs : EventArgs
{
public IChannel Channel => null;
}
}

+ 0
- 10
ref/Events/ChannelUpdatedEventArgs.cs View File

@@ -1,10 +0,0 @@
using System;

namespace Discord
{
public class ChannelUpdatedEventArgs : EventArgs
{
public IChannel Before => null;
public IChannel After => null;
}
}

+ 0
- 10
ref/Events/DisconnectedEventArgs.cs View File

@@ -1,10 +0,0 @@
using System;

namespace Discord
{
public class DisconnectedEventArgs : EventArgs
{
public bool WasUnexpected => false;
public Exception Exception => null;
}
}

+ 0
- 12
ref/Events/LogMessageEventArgs.cs View File

@@ -1,12 +0,0 @@
using System;

namespace Discord
{
public class LogMessageEventArgs : EventArgs
{
public LogSeverity Severity => default(LogSeverity);
public string Source => null;
public string Message => null;
public Exception Exception => null;
}
}

+ 0
- 11
ref/Events/MessageEventArgs.cs View File

@@ -1,11 +0,0 @@
using System;

namespace Discord
{
public class MessageEventArgs : EventArgs
{
public Message Message => null;
public IUser User => null;
public ITextChannel Channel => null;
}
}

+ 0
- 12
ref/Events/MessageUpdatedEventArgs.cs View File

@@ -1,12 +0,0 @@
using System;

namespace Discord
{
public class MessageUpdatedEventArgs : EventArgs
{
public Message Before => null;
public Message After => null;
public IUser User => null;
public ITextChannel Channel => null;
}
}

+ 0
- 10
ref/Events/ProfileUpdatedEventArgs.cs View File

@@ -1,10 +0,0 @@
using System;

namespace Discord
{
public class ProfileUpdatedEventArgs : EventArgs
{
public Profile Before => null;
public Profile After => null;
}
}

+ 0
- 10
ref/Events/RoleEventArgs.cs View File

@@ -1,10 +0,0 @@
using System;

namespace Discord
{
public class RoleEventArgs : EventArgs
{
public Role Role => null;
public Server Server => null;
}
}

+ 0
- 11
ref/Events/RoleUpdatedEventArgs.cs View File

@@ -1,11 +0,0 @@
using System;

namespace Discord
{
public class RoleUpdatedEventArgs : EventArgs
{
public Role Before => null;
public Role After => null;
public Server Server => null;
}
}

+ 0
- 9
ref/Events/ServerEventArgs.cs View File

@@ -1,9 +0,0 @@
using System;

namespace Discord
{
public class ServerEventArgs : EventArgs
{
public Server Server => null;
}
}

+ 0
- 10
ref/Events/ServerUpdatedEventArgs.cs View File

@@ -1,10 +0,0 @@
using System;

namespace Discord
{
public class ServerUpdatedEventArgs : EventArgs
{
public Server Before => null;
public Server After => null;
}
}

+ 0
- 14
ref/Events/TypingEventArgs.cs View File

@@ -1,14 +0,0 @@
namespace Discord
{
public class TypingEventArgs
{
public ITextChannel Channel { get; }
public IUser User { get; }

public TypingEventArgs(ITextChannel channel, IUser user)
{
Channel = channel;
User = user;
}
}
}

+ 0
- 8
ref/Events/UserEventArgs.cs View File

@@ -1,8 +0,0 @@
using System;
namespace Discord
{
public class UserEventArgs : EventArgs
{
public IUser User => null;
}
}

+ 0
- 9
ref/Events/UserUpdatedEventArgs.cs View File

@@ -1,9 +0,0 @@
using System;
namespace Discord
{
public class UserUpdatedEventArgs : EventArgs
{
public IUser Before => null;
public IUser After => null;
}
}

+ 0
- 14
ref/Format.cs View File

@@ -1,14 +0,0 @@
namespace Discord
{
public static class Format
{
public static string Escape(string text) => null;
public static string Bold(string text, bool escape = true) => null;
public static string Italics(string text, bool escape = true) => null;
public static string Underline(string text, bool escape = true) => null;
public static string Strikeout(string text, bool escape = true) => null;

public static string Code(string text, string language = null) => null;
}
}

+ 0
- 30
ref/ILogger.cs View File

@@ -1,30 +0,0 @@
using System;

namespace Discord.Logging
{
public interface ILogger
{
LogSeverity Level { get; }

void Log(LogSeverity severity, string message, Exception exception = null);
void Error(string message, Exception exception = null);
void Error(Exception exception);
void Warning(string message, Exception exception = null);
void Warning(Exception exception);
void Info(string message, Exception exception = null);
void Info(Exception exception);
void Verbose(string message, Exception exception = null);
void Verbose(Exception exception);
void Debug(string message, Exception exception = null);
void Debug(Exception exception);

#if DOTNET5_4
void Log(LogSeverity severity, FormattableString message, Exception exception = null);
void Error(FormattableString message, Exception exception = null);
void Warning(FormattableString message, Exception exception = null);
void Info(FormattableString message, Exception exception = null);
void Verbose(FormattableString message, Exception exception = null);
void Debug(FormattableString message, Exception exception = null);
#endif
}
}

+ 0
- 9
ref/MessageQueue.cs View File

@@ -1,9 +0,0 @@
namespace Discord
{
public class MessageQueue
{
public int Count { get; }

public void Clear() { }
}
}

+ 0
- 16
ref/Net/HttpException.cs View File

@@ -1,16 +0,0 @@
using System;
using System.Net;

namespace Discord.Net
{
public class HttpException : Exception
{
public HttpStatusCode StatusCode { get; }

public HttpException(HttpStatusCode statusCode)
: base($"The server responded with error {(int)statusCode} ({statusCode})")
{
StatusCode = statusCode;
}
}
}

+ 0
- 17
ref/Net/Rest/CompletedRequestEventArgs.cs View File

@@ -1,17 +0,0 @@
namespace Discord.Net.Rest
{
public class CompletedRequestEventArgs : RequestEventArgs
{
public object Response { get; set; }
public string ResponseJson { get; set; }
public double Milliseconds { get; set; }

public CompletedRequestEventArgs(IRestRequest request, object response, string responseJson, double milliseconds)
: base(request)
{
Response = response;
ResponseJson = responseJson;
Milliseconds = milliseconds;
}
}
}

+ 0
- 23
ref/Net/Rest/IRestClient.cs View File

@@ -1,23 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;

namespace Discord.Net.Rest
{
public interface IRestClient
{
event EventHandler<RequestEventArgs> SendingRequest;
event EventHandler<CompletedRequestEventArgs> SentRequest;

CancellationToken CancelToken { get; }
string Token { get; }

Task<ResponseT> Send<ResponseT>(IRestRequest<ResponseT> request)
where ResponseT : class;
Task Send(IRestRequest request);

Task<ResponseT> Send<ResponseT>(IRestFileRequest<ResponseT> request)
where ResponseT : class;
Task Send(IRestFileRequest request);
}
}

+ 0
- 10
ref/Net/Rest/IRestClientProvider.cs View File

@@ -1,10 +0,0 @@
using System.Collections.Generic;
using System.Threading;

namespace Discord.Net.Rest
{
public interface IRestClientProvider
{
IRestClient Create(string baseUrl, CancellationToken cancelToken);
}
}

+ 0
- 12
ref/Net/Rest/RequestEventArgs.cs View File

@@ -1,12 +0,0 @@
using System;

namespace Discord.Net.Rest
{
public class RequestEventArgs : EventArgs
{
public IRestRequest Request { get; set; }
public bool Cancel { get; set; }

public RequestEventArgs(IRestRequest request) { }
}
}

+ 0
- 9
ref/Net/TimeoutException.cs View File

@@ -1,9 +0,0 @@
using System;

namespace Discord.Net
{
public class TimeoutException : OperationCanceledException
{
public TimeoutException() { }
}
}

+ 0
- 12
ref/Net/WebSocketException.cs View File

@@ -1,12 +0,0 @@
using System;

namespace Discord.Net
{
public class WebSocketException : Exception
{
public int Code { get; }
public string Reason { get; }

public WebSocketException(int code, string reason) { }
}
}

+ 0
- 11
ref/Net/WebSockets/BinaryMessageEventArgs.cs View File

@@ -1,11 +0,0 @@
using System;

namespace Discord.Net.WebSockets
{
public class BinaryMessageEventArgs : EventArgs
{
public byte[] Data { get; }

public BinaryMessageEventArgs(byte[] data) { }
}
}

+ 0
- 28
ref/Net/WebSockets/GatewaySocket.cs View File

@@ -1,28 +0,0 @@
using Discord.Net.Rest;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace Discord.Net.WebSockets
{
public class GatewaySocket
{
public string SessionId { get; }

public event EventHandler<WebSocketEventEventArgs> ReceivedDispatch = delegate { };

public Task Connect(IRestClient rest, CancellationToken parentCancelToken) => null;
public Task Disconnect() => null;

public void SendIdentify(string token) { }

public void SendResume() { }
public void SendHeartbeat() { }
public void SendUpdateStatus(long? idleSince, string gameName) { }
public void SendUpdateVoice(ulong? serverId, ulong? channelId, bool isSelfMuted, bool isSelfDeafened) { }
public void SendRequestMembers(IEnumerable<ulong> serverId, string query, int limit) { }
public void WaitForConnection(CancellationToken cancelToken) { }
}
}

+ 0
- 15
ref/Net/WebSockets/IWebSocket.cs View File

@@ -1,15 +0,0 @@
using System;
using System.Threading;

namespace Discord.Net.WebSockets
{
public interface IWebSocket
{
CancellationToken CancelToken { get; }
ConnectionState State { get; }
string Host { get; set; }

event EventHandler Connected;
event EventHandler<DisconnectedEventArgs> Disconnected;
}
}

+ 0
- 18
ref/Net/WebSockets/IWebSocketEngine.cs View File

@@ -1,18 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace Discord.Net.WebSockets
{
public interface IWebSocketEngine
{
event EventHandler<BinaryMessageEventArgs> BinaryMessage;
event EventHandler<TextMessageEventArgs> TextMessage;

Task Connect(string host, CancellationToken cancelToken);
Task Disconnect();
void QueueMessage(string message);
IEnumerable<Task> GetTasks(CancellationToken cancelToken);
}
}

+ 0
- 9
ref/Net/WebSockets/IWebSocketProvider.cs View File

@@ -1,9 +0,0 @@
using System.Threading;

namespace Discord.Net.WebSockets
{
public interface IWebSocketProvider
{
IWebSocket Create(CancellationToken cancelToken);
}
}

+ 0
- 11
ref/Net/WebSockets/TextMessageEventArgs.cs View File

@@ -1,11 +0,0 @@
using System;

namespace Discord.Net.WebSockets
{
public class TextMessageEventArgs : EventArgs
{
public string Message { get; }

public TextMessageEventArgs(string msg) { Message = msg; }
}
}

+ 0
- 11
ref/Net/WebSockets/WebSocketEventEventArgs.cs View File

@@ -1,11 +0,0 @@
using Newtonsoft.Json.Linq;
using System;

namespace Discord.Net.WebSockets
{
public class WebSocketEventEventArgs : EventArgs
{
public string Type { get; }
public JToken Payload { get; }
}
}

+ 0
- 81
ref/project.json View File

@@ -1,81 +0,0 @@
{
"version": "0.9.0-rc3-3",
"description": "An unofficial .Net API wrapper for the Discord client.",
"authors": [
"RogueException"
],
"tags": [
"discord",
"discordapp"
],
"projectUrl": "https://github.com/RogueException/Discord.Net",
"licenseUrl": "http://opensource.org/licenses/MIT",
"repository": {
"type": "git",
"url": "git://github.com/RogueException/Discord.Net"
},
"compile": [ "**/*.cs", "../Discord.Net.Shared/*.cs" ],

"compilationOptions": {
"allowUnsafe": true,
"warningsAsErrors": true
},

"configurations": {
"TestResponses": {
"compilationOptions": {
"define": [
"DEBUG",
"TRACE",
"TEST_RESPONSES"
]
}
}
},

"dependencies": {
"Newtonsoft.Json": "8.0.1",
"Nito.AsyncEx": "3.0.1"
},

"frameworks": {
"dotnet5.4": {
"dependencies": {
"System.Collections": "4.0.11-beta-23516",
"System.Collections.Concurrent": "4.0.11-beta-23516",
"System.Dynamic.Runtime": "4.0.11-beta-23516",
"System.IO.FileSystem": "4.0.1-beta-23516",
"System.IO.Compression": "4.1.0-beta-23516",
"System.Linq": "4.0.1-beta-23516",
"System.Net.Http": "4.0.1-beta-23516",
"System.Net.NameResolution": "4.0.0-beta-23516",
"System.Net.Sockets": "4.1.0-beta-23409",
"System.Net.Requests": "4.0.11-beta-23516",
"System.Net.WebSockets.Client": "4.0.0-beta-23516",
"System.Reflection": "4.1.0-beta-23516",
"System.Reflection.Emit.Lightweight": "4.0.1-beta-23516",
"System.Runtime.InteropServices": "4.0.21-beta-23516",
"System.Runtime.Serialization.Primitives": "4.1.0-beta-23516",
"System.Security.Cryptography.Algorithms": "4.0.0-beta-23516",
"System.Text.RegularExpressions": "4.0.11-beta-23516",
"System.Threading": "4.0.11-beta-23516"
}
},
"net45": {
"frameworkAssemblies": {
"System.Runtime": {
"type": "build",
"version": ""
},
"System.Threading.Tasks": {
"type": "build",
"version": ""
}
},
"dependencies": {
"WebSocket4Net": "0.14.1",
"RestSharp": "105.2.3"
}
}
}
}

+ 0
- 283
src/Discord.Net.Audio/AudioClient.cs View File

@@ -1,283 +0,0 @@
using Discord.API.Client.GatewaySocket;
using Discord.API.Client.Rest;
using Discord.Logging;
using Discord.Net.Rest;
using Discord.Net.WebSockets;
using Newtonsoft.Json;
using Nito.AsyncEx;
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

namespace Discord.Audio
{
internal class AudioClient : IAudioClient
{
private readonly DiscordConfig _config;
private readonly AsyncLock _connectionLock;
private readonly TaskManager _taskManager;
private ConnectionState _gatewayState;

internal Logger Logger { get; }
public int Id { get; }
public AudioService Service { get; }
public AudioServiceConfig Config { get; }
public RestClient ClientAPI { get; }
public GatewaySocket GatewaySocket { get; }
public VoiceSocket VoiceSocket { get; }
public JsonSerializer Serializer { get; }
public CancellationToken CancelToken { get; private set; }
public string SessionId => GatewaySocket.SessionId;

public ConnectionState State => VoiceSocket.State;
public Server Server => VoiceSocket.Server;
public VoiceChannel Channel => VoiceSocket.Channel;

public AudioClient(DiscordClient client, Server server, int id)
{
Id = id;
Service = client.GetService<AudioService>();
Config = Service.Config;
Serializer = client.Serializer;
_gatewayState = (int)ConnectionState.Disconnected;

//Logging
Logger = client.Log.CreateLogger($"AudioClient #{id}");

//Async
_taskManager = new TaskManager(Cleanup, false);
_connectionLock = new AsyncLock();
CancelToken = new CancellationToken(true);

//Networking
if (Config.EnableMultiserver)
{
//TODO: We can remove this hack when official API launches
var baseConfig = client.Config;
var builder = new DiscordConfigBuilder
{
AppName = baseConfig.AppName,
AppUrl = baseConfig.AppUrl,
AppVersion = baseConfig.AppVersion,
CacheToken = baseConfig.CacheDir != null,
ConnectionTimeout = baseConfig.ConnectionTimeout,
EnablePreUpdateEvents = false,
FailedReconnectDelay = baseConfig.FailedReconnectDelay,
LargeThreshold = 1,
LogLevel = baseConfig.LogLevel,
MessageCacheSize = 0,
ReconnectDelay = baseConfig.ReconnectDelay,
UsePermissionsCache = false
};
_config = builder.Build();

ClientAPI = new JsonRestClient(_config, DiscordConfig.ClientAPIUrl, client.Log.CreateLogger($"ClientAPI #{id}"));
GatewaySocket = new GatewaySocket(_config, client.Serializer, client.Log.CreateLogger($"Gateway #{id}"));
GatewaySocket.Connected += (s, e) =>
{
if (_gatewayState == ConnectionState.Connecting)
EndGatewayConnect();
};
}
else
{
_config = client.Config;
GatewaySocket = client.GatewaySocket;
}
GatewaySocket.ReceivedDispatch += (s, e) => OnReceivedEvent(e);
VoiceSocket = new VoiceSocket(_config, Config, client.Serializer, client.Log.CreateLogger($"Voice #{id}"));
VoiceSocket.Server = server;
}

public async Task Connect()
{
if (Config.EnableMultiserver)
await BeginGatewayConnect().ConfigureAwait(false);
else
{
var cancelSource = new CancellationTokenSource();
CancelToken = cancelSource.Token;
await _taskManager.Start(new Task[0], cancelSource).ConfigureAwait(false);
}
}
private async Task BeginGatewayConnect()
{
try
{
using (await _connectionLock.LockAsync().ConfigureAwait(false))
{
await Disconnect().ConfigureAwait(false);
_taskManager.ClearException();

ClientAPI.Token = Service.Client.ClientAPI.Token;

Stopwatch stopwatch = null;
if (_config.LogLevel >= LogSeverity.Verbose)
stopwatch = Stopwatch.StartNew();
_gatewayState = ConnectionState.Connecting;

var cancelSource = new CancellationTokenSource();
CancelToken = cancelSource.Token;
ClientAPI.CancelToken = CancelToken;
await GatewaySocket.Connect(ClientAPI, CancelToken).ConfigureAwait(false);

await _taskManager.Start(new Task[0], cancelSource).ConfigureAwait(false);
GatewaySocket.WaitForConnection(CancelToken);

if (_config.LogLevel >= LogSeverity.Verbose)
{
stopwatch.Stop();
double seconds = Math.Round(stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerSecond, 2);
Logger.Verbose($"Connection took {seconds} sec");
}
}
}
catch (Exception ex)
{
await _taskManager.SignalError(ex).ConfigureAwait(false);
throw;
}
}
private void EndGatewayConnect()
{
_gatewayState = ConnectionState.Connected;
}
public async Task Disconnect()
{
await _taskManager.Stop(true).ConfigureAwait(false);
if (Config.EnableMultiserver)
ClientAPI.Token = null;
}
private async Task Cleanup()
{
var oldState = _gatewayState;
_gatewayState = ConnectionState.Disconnecting;

if (Config.EnableMultiserver)
{
if (oldState == ConnectionState.Connected)
{
try { await ClientAPI.Send(new LogoutRequest()).ConfigureAwait(false); }
catch (OperationCanceledException) { }
}

await GatewaySocket.Disconnect().ConfigureAwait(false);
ClientAPI.Token = null;
}

var server = VoiceSocket.Server;
VoiceSocket.Server = null;
VoiceSocket.Channel = null;
if (Config.EnableMultiserver)
await Service.RemoveClient(server, this).ConfigureAwait(false);
SendVoiceUpdate(server.Id, null);

await VoiceSocket.Disconnect().ConfigureAwait(false);
if (Config.EnableMultiserver)
await GatewaySocket.Disconnect().ConfigureAwait(false);

_gatewayState = (int)ConnectionState.Disconnected;
}

public async Task Join(VoiceChannel channel)
{
if (channel == null) throw new ArgumentNullException(nameof(channel));
if (channel.Type != ChannelType.Voice)
throw new ArgumentException("Channel must be a voice channel.", nameof(channel));
if (channel == VoiceSocket.Channel) return;
var server = channel.Server;
if (server != VoiceSocket.Server)
throw new ArgumentException("This is channel is not part of the current server.", nameof(channel));
if (VoiceSocket.Server == null)
throw new InvalidOperationException("This client has been closed.");

SendVoiceUpdate(channel.Server.Id, channel.Id);
using (await _connectionLock.LockAsync().ConfigureAwait(false))
await Task.Run(() => VoiceSocket.WaitForConnection(CancelToken)).ConfigureAwait(false);
}

private async void OnReceivedEvent(WebSocketEventEventArgs e)
{
try
{
switch (e.Type)
{
case "VOICE_STATE_UPDATE":
{
var data = e.Payload.ToObject<VoiceStateUpdateEvent>(Serializer);
if (data.GuildId == VoiceSocket.Server?.Id && data.UserId == Service.Client.CurrentUser?.Id)
{
if (data.ChannelId == null)
await Disconnect().ConfigureAwait(false);
else
{
var channel = Service.Client.GetChannel(data.ChannelId.Value) as VoiceChannel;
if (channel != null)
VoiceSocket.Channel = channel;
else
{
Logger.Warning("VOICE_STATE_UPDATE referenced an unknown channel, disconnecting.");
await Disconnect().ConfigureAwait(false);
}
}
}
}
break;
case "VOICE_SERVER_UPDATE":
{
var data = e.Payload.ToObject<VoiceServerUpdateEvent>(Serializer);
if (data.GuildId == VoiceSocket.Server?.Id)
{
var client = Service.Client;
var id = client.CurrentUser?.Id;
if (id != null)
{
var host = "wss://" + e.Payload.Value<string>("endpoint").Split(':')[0];
await VoiceSocket.Connect(host, data.Token, id.Value, GatewaySocket.SessionId, CancelToken).ConfigureAwait(false);
}
}
}
break;
}
}
catch (Exception ex)
{
Logger.Error($"Error handling {e.Type} event", ex);
}
}

public void Send(byte[] data, int offset, int count)
{
if (data == null) throw new ArgumentException(nameof(data));
if (count < 0) throw new ArgumentOutOfRangeException(nameof(count));
if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset));
if (VoiceSocket.Server == null) return; //Has been closed
if (count == 0) return;

VoiceSocket.SendPCMFrames(data, offset, count);
}

public void Clear()
{
if (VoiceSocket.Server == null) return; //Has been closed
VoiceSocket.ClearPCMFrames();
}
public void Wait()
{
if (VoiceSocket.Server == null) return; //Has been closed
VoiceSocket.WaitForQueue();
}

public void SendVoiceUpdate(ulong? serverId, ulong? channelId)
{
GatewaySocket.SendUpdateVoice(serverId, channelId,
(Service.Config.Mode | AudioMode.Outgoing) == 0,
(Service.Config.Mode | AudioMode.Incoming) == 0);
}
}
}

+ 0
- 26
src/Discord.Net.Audio/AudioExtensions.cs View File

@@ -1,26 +0,0 @@
using System;
using System.Threading.Tasks;

namespace Discord.Audio
{
public static class AudioExtensions
{
public static DiscordClient UsingAudio(this DiscordClient client, AudioServiceConfig config = null)
{
client.AddService(new AudioService(config));
return client;
}
public static DiscordClient UsingAudio(this DiscordClient client, Action<AudioServiceConfigBuilder> configFunc = null)
{
var builder = new AudioServiceConfigBuilder();
configFunc(builder);
client.AddService(new AudioService(builder));
return client;
}

public static Task<IAudioClient> JoinAudio(this VoiceChannel channel) => channel.Client.GetService<AudioService>().Join(channel);
public static Task LeaveAudio(this VoiceChannel channel) => channel.Client.GetService<AudioService>().Leave(channel);
public static Task LeaveAudio(this Server server) => server.Client.GetService<AudioService>().Leave(server);
public static IAudioClient GetAudioClient(this Server server) => server.Client.GetService<AudioService>().GetClient(server);
}
}

+ 0
- 9
src/Discord.Net.Audio/AudioMode.cs View File

@@ -1,9 +0,0 @@
namespace Discord.Audio
{
public enum AudioMode : byte
{
Outgoing = 1,
Incoming = 2,
Both = Outgoing | Incoming
}
}

+ 0
- 193
src/Discord.Net.Audio/AudioService.cs View File

@@ -1,193 +0,0 @@
using Nito.AsyncEx;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;

namespace Discord.Audio
{
public class AudioService : IService
{
private readonly AsyncLock _asyncLock;
private AudioClient _defaultClient; //Only used for single server
private VirtualClient _currentClient; //Only used for single server
private ConcurrentDictionary<ulong, AudioClient> _voiceClients;
private ConcurrentDictionary<User, bool> _talkingUsers;
private int _nextClientId;

public DiscordClient Client { get; private set; }
public AudioServiceConfig Config { get; }

public event EventHandler Connected = delegate { };
public event EventHandler<VoiceDisconnectedEventArgs> Disconnected = delegate { };
public event EventHandler<UserIsSpeakingEventArgs> UserIsSpeakingUpdated = delegate { };

private void OnConnected()
=> Connected(this, EventArgs.Empty);
private void OnDisconnected(ulong serverId, bool wasUnexpected, Exception ex)
=> Disconnected(this, new VoiceDisconnectedEventArgs(serverId, wasUnexpected, ex));
private void OnUserIsSpeakingUpdated(User user, bool isSpeaking)
=> UserIsSpeakingUpdated(this, new UserIsSpeakingEventArgs(user, isSpeaking));

public AudioService()
: this(new AudioServiceConfigBuilder())
{
}
public AudioService(AudioServiceConfigBuilder builder)
: this(builder.Build())
{
}
public AudioService(AudioServiceConfig config)
{
Config = config;
_asyncLock = new AsyncLock();

}
void IService.Install(DiscordClient client)
{
Client = client;

if (Config.EnableMultiserver)
_voiceClients = new ConcurrentDictionary<ulong, AudioClient>();
else
{
var logger = Client.Log.CreateLogger("Voice");
_defaultClient = new AudioClient(Client, null, 0);
}
_talkingUsers = new ConcurrentDictionary<User, bool>();

client.GatewaySocket.Disconnected += async (s, e) =>
{
if (Config.EnableMultiserver)
{
var tasks = _voiceClients
.Select(x =>
{
var val = x.Value;
if (val != null)
return x.Value.Disconnect();
else
return TaskHelper.CompletedTask;
})
.ToArray();
await Task.WhenAll(tasks).ConfigureAwait(false);
_voiceClients.Clear();
}
foreach (var member in _talkingUsers)
{
bool ignored;
if (_talkingUsers.TryRemove(member.Key, out ignored))
OnUserIsSpeakingUpdated(member.Key, false);
}
};
}

public IAudioClient GetClient(Server server)
{
if (server == null) throw new ArgumentNullException(nameof(server));

if (Config.EnableMultiserver)
{
AudioClient client;
if (_voiceClients.TryGetValue(server.Id, out client))
return client;
else
return null;
}
else
{
if (server == _currentClient.Server)
return _currentClient;
else
return null;
}
}
//Called from AudioClient.Disconnect
internal async Task RemoveClient(Server server, AudioClient client)
{
using (await _asyncLock.LockAsync().ConfigureAwait(false))
{
if (_voiceClients.TryUpdate(server.Id, null, client))
_voiceClients.TryRemove(server.Id, out client);
}
}

public async Task<IAudioClient> Join(VoiceChannel channel)
{
if (channel == null) throw new ArgumentNullException(nameof(channel));
var server = channel.Server;
using (await _asyncLock.LockAsync().ConfigureAwait(false))
{
if (Config.EnableMultiserver)
{
AudioClient client;
if (!_voiceClients.TryGetValue(server.Id, out client))
{
client = new AudioClient(Client, server, unchecked(++_nextClientId));
_voiceClients[server.Id] = client;

await client.Connect().ConfigureAwait(false);

/*voiceClient.VoiceSocket.FrameReceived += (s, e) =>
{
OnFrameReceieved(e);
};
voiceClient.VoiceSocket.UserIsSpeaking += (s, e) =>
{
var user = server.GetUser(e.UserId);
OnUserIsSpeakingUpdated(user, e.IsSpeaking);
};*/
}

await client.Join(channel).ConfigureAwait(false);
return client;
}
else
{
if (_defaultClient.Server != server)
{
await _defaultClient.Disconnect().ConfigureAwait(false);
_defaultClient.VoiceSocket.Server = server;
await _defaultClient.Connect().ConfigureAwait(false);
}
var client = new VirtualClient(_defaultClient, server);
_currentClient = client;

await client.Join(channel).ConfigureAwait(false);
return client;
}

}
}

public Task Leave(Server server) => Leave(server, null);
public Task Leave(VoiceChannel channel) => Leave(channel.Server, channel);
private async Task Leave(Server server, VoiceChannel channel)
{
if (server == null) throw new ArgumentNullException(nameof(server));

if (Config.EnableMultiserver)
{
AudioClient client;
//Potential race condition if changing channels during this call, but that's acceptable
if (channel == null || (_voiceClients.TryGetValue(server.Id, out client) && client.Channel == channel))
{
if (_voiceClients.TryRemove(server.Id, out client))
await client.Disconnect().ConfigureAwait(false);
}
}
else
{
using (await _asyncLock.LockAsync().ConfigureAwait(false))
{
var client = GetClient(server) as VirtualClient;
if (client != null && client.Channel == channel)
await _defaultClient.Disconnect().ConfigureAwait(false);
}
}

}
}
}

+ 0
- 51
src/Discord.Net.Audio/AudioServiceConfig.cs View File

@@ -1,51 +0,0 @@
namespace Discord.Audio
{
public class AudioServiceConfigBuilder
{
/// <summary> Enables the voice websocket and UDP client and specifies how it will be used. </summary>
public AudioMode Mode { get; set; } = AudioMode.Outgoing;

/// <summary> Enables the voice websocket and UDP client. This option requires the libsodium .dll or .so be in the local or system folder. </summary>
public bool EnableEncryption { get; set; } = true;
/// <summary>
/// Enables the client to be simultaneously connected to multiple channels at once (Discord still limits you to one channel per server).
/// This option uses a lot of CPU power and network bandwidth, as a new gateway connection needs to be spun up per server. Use sparingly.
/// </summary>
public bool EnableMultiserver { get; set; } = false;

/// <summary> Gets or sets the buffer length (in milliseconds) for outgoing voice packets. </summary>
public int BufferLength { get; set; } = 1000;
/// <summary> Gets or sets the bitrate used (in kbit/s, between 1 and MaxBitrate inclusively) for outgoing voice packets. A null value will use default Opus settings. </summary>
public int? Bitrate { get; set; } = null;
/// <summary> Gets or sets the number of channels (1 or 2) used in both input provided to IAudioClient and output send to Discord. Defaults to 2 (stereo). </summary>
public int Channels { get; set; } = 2;

public AudioServiceConfig Build() => new AudioServiceConfig(this);
}

public class AudioServiceConfig
{
public const int MaxBitrate = 128;

public AudioMode Mode { get; }

public bool EnableEncryption { get; }
public bool EnableMultiserver { get; }

public int BufferLength { get; }
public int? Bitrate { get; }
public int Channels { get; }

internal AudioServiceConfig(AudioServiceConfigBuilder builder)
{
Mode = builder.Mode;

EnableEncryption = builder.EnableEncryption;
EnableMultiserver = builder.EnableMultiserver;

BufferLength = builder.BufferLength;
Bitrate = builder.Bitrate;
Channels = builder.Channels;
}
}
}

+ 0
- 21
src/Discord.Net.Audio/Discord.Net.Audio.xproj View File

@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>dff7afe3-ca77-4109-bade-b4b49a4f6648</ProjectGuid>
<RootNamespace>Discord.Audio</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<ProduceOutputsOnBuild>True</ProduceOutputsOnBuild>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

+ 0
- 46
src/Discord.Net.Audio/IAudioClient.cs View File

@@ -1,46 +0,0 @@
using Discord.Net.Rest;
using Discord.Net.WebSockets;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace Discord.Audio
{
public interface IAudioClient
{
/// <summary> Gets the unique identifier for this client. </summary>
int Id { get; }
/// <summary> Gets the session id for the current connection. </summary>
string SessionId { get; }
/// <summary> Gets the current state of this client. </summary>
ConnectionState State { get; }
/// <summary> Gets the channel this client is currently a member of. </summary>
VoiceChannel Channel { get; }
/// <summary> Gets the server this client is bound to. </summary>
Server Server { get; }
/// <summary> Gets a cancellation token that triggers when the client is manually disconnected. </summary>
CancellationToken CancelToken { get; }

/// <summary> Gets the internal RestClient for the Client API endpoint. </summary>
RestClient ClientAPI { get; }
/// <summary> Gets the internal WebSocket for the Gateway event stream. </summary>
GatewaySocket GatewaySocket { get; }
/// <summary> Gets the internal WebSocket for the Voice control stream. </summary>
VoiceSocket VoiceSocket { get; }

/// <summary> Moves the client to another channel on the same server. </summary>
Task Join(VoiceChannel channel);
/// <summary> Disconnects from the Discord server, canceling any pending requests. </summary>
Task Disconnect();

/// <summary> Sends a PCM frame to the voice server. Will block until space frees up in the outgoing buffer. </summary>
/// <param name="data">PCM frame to send. This must be a single or collection of uncompressed 48Kz monochannel 20ms PCM frames. </param>
/// <param name="offset">Offset . </param>
/// <param name="count">Number of bytes in this frame. </param>
void Send(byte[] data, int offset, int count);
/// <summary> Clears the PCM buffer. </summary>
void Clear();
/// <summary> Blocks until the voice output buffer is empty. </summary>
void Wait();
}
}

+ 0
- 22
src/Discord.Net.Audio/InternalFrameEventArgs.cs View File

@@ -1,22 +0,0 @@
using System;

namespace Discord
{
internal class InternalFrameEventArgs : EventArgs
{
public ulong UserId { get; }
public ulong ChannelId { get; }
public byte[] Buffer { get; }
public int Offset { get; }
public int Count { get; }

public InternalFrameEventArgs(ulong userId, ulong channelId, byte[] buffer, int offset, int count)
{
UserId = userId;
ChannelId = channelId;
Buffer = buffer;
Offset = offset;
Count = count;
}
}
}

+ 0
- 14
src/Discord.Net.Audio/InternalIsSpeakingEventArgs.cs View File

@@ -1,14 +0,0 @@
namespace Discord.Audio
{
internal class InternalIsSpeakingEventArgs
{
public ulong UserId { get; }
public bool IsSpeaking { get; }

public InternalIsSpeakingEventArgs(ulong userId, bool isSpeaking)
{
UserId = userId;
IsSpeaking = isSpeaking;
}
}
}

+ 0
- 516
src/Discord.Net.Audio/Net/VoiceSocket.cs View File

@@ -1,516 +0,0 @@
using Discord.API.Client;
using Discord.API.Client.VoiceSocket;
using Discord.Audio;
using Discord.Audio.Opus;
using Discord.Audio.Sodium;
using Discord.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Discord.Net.WebSockets
{
public partial class VoiceSocket : WebSocket
{
private const int MaxOpusSize = 4000;
private const string EncryptedMode = "xsalsa20_poly1305";
private const string UnencryptedMode = "plain";

private readonly int _targetAudioBufferLength;
private readonly ConcurrentDictionary<uint, OpusDecoder> _decoders;
private readonly AudioServiceConfig _audioConfig;
private Task _sendTask, _receiveTask;
private VoiceBuffer _sendBuffer;
private OpusEncoder _encoder;
private uint _ssrc;
private ConcurrentDictionary<uint, ulong> _ssrcMapping;
private UdpClient _udp;
private IPEndPoint _endpoint;
private bool _isEncrypted;
private byte[] _secretKey, _encodingBuffer;
private ushort _sequence;
private string _encryptionMode;
private int _ping;
private ulong? _userId;
private string _sessionId;

public string Token { get; internal set; }
public Server Server { get; internal set; }
public VoiceChannel Channel { get; internal set; }

public int Ping => _ping;
internal VoiceBuffer OutputBuffer => _sendBuffer;

internal event EventHandler<InternalIsSpeakingEventArgs> UserIsSpeaking = delegate { };
internal event EventHandler<InternalFrameEventArgs> FrameReceived = delegate { };

private void OnUserIsSpeaking(ulong userId, bool isSpeaking)
=> UserIsSpeaking(this, new InternalIsSpeakingEventArgs(userId, isSpeaking));
internal void OnFrameReceived(ulong userId, ulong channelId, byte[] buffer, int offset, int count)
=> FrameReceived(this, new InternalFrameEventArgs(userId, channelId, buffer, offset, count));

internal VoiceSocket(DiscordConfig config, AudioServiceConfig audioConfig, JsonSerializer serializer, Logger logger)
: base(config, serializer, logger)
{
_audioConfig = audioConfig;
_decoders = new ConcurrentDictionary<uint, OpusDecoder>();
_targetAudioBufferLength = _audioConfig.BufferLength / 20; //20 ms frames
_encodingBuffer = new byte[MaxOpusSize];
_ssrcMapping = new ConcurrentDictionary<uint, ulong>();
_encoder = new OpusEncoder(48000, _audioConfig.Channels, 20, _audioConfig.Bitrate, OpusApplication.MusicOrMixed);
_sendBuffer = new VoiceBuffer((int)Math.Ceiling(_audioConfig.BufferLength / (double)_encoder.FrameLength), _encoder.FrameSize);
}

public Task Connect(string host, string token, ulong userId, string sessionId, CancellationToken parentCancelToken)
{
Host = host;
Token = token;
_userId = userId;
_sessionId = sessionId;
return BeginConnect(parentCancelToken);
}
private async Task Reconnect()
{
try
{
var cancelToken = _parentCancelToken;
await Task.Delay(_config.ReconnectDelay, cancelToken).ConfigureAwait(false);
while (!cancelToken.IsCancellationRequested)
{
try
{
await BeginConnect(_parentCancelToken).ConfigureAwait(false);
break;
}
catch (OperationCanceledException) { throw; }
catch (Exception ex)
{
Logger.Error("Reconnect failed", ex);
//Net is down? We can keep trying to reconnect until the user runs Disconnect()
await Task.Delay(_config.FailedReconnectDelay, cancelToken).ConfigureAwait(false);
}
}
}
catch (OperationCanceledException) { }
}
public async Task Disconnect()
{
await _taskManager.Stop(true).ConfigureAwait(false);
_userId = null;
}

protected override async Task Run()
{
_udp = new UdpClient(new IPEndPoint(IPAddress.Any, 0));

List<Task> tasks = new List<Task>();
if (_audioConfig.Mode.HasFlag(AudioMode.Outgoing))
_sendTask = Task.Run(() => SendVoiceAsync(CancelToken));
_receiveTask = Task.Run(() => ReceiveVoiceAsync(CancelToken));

SendIdentify(_userId.Value, _sessionId);

#if !DOTNET5_4
tasks.Add(WatcherAsync());
#endif
tasks.AddRange(_engine.GetTasks(CancelToken));
tasks.Add(HeartbeatAsync(CancelToken));
await _taskManager.Start(tasks, _cancelSource).ConfigureAwait(false);
}
protected override async Task Cleanup()
{
var sendThread = _sendTask;
if (sendThread != null)
{
try { await sendThread.ConfigureAwait(false); }
catch (Exception) { } //Ignore any errors during cleanup
}
_sendTask = null;

var receiveThread = _receiveTask;
if (receiveThread != null)
{
try { await receiveThread.ConfigureAwait(false); }
catch (Exception) { } //Ignore any errors during cleanup
}
_receiveTask = null;

OpusDecoder decoder;
foreach (var pair in _decoders)
{
if (_decoders.TryRemove(pair.Key, out decoder))
decoder.Dispose();
}

ClearPCMFrames();
_udp = null;

await base.Cleanup().ConfigureAwait(false);
}

private async Task ReceiveVoiceAsync(CancellationToken cancelToken)
{
var closeTask = cancelToken.Wait();
try
{
byte[] packet, decodingBuffer = null, nonce = null, result;
int packetLength, resultOffset, resultLength;
IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, 0);

if ((_audioConfig.Mode & AudioMode.Incoming) != 0)
{
decodingBuffer = new byte[MaxOpusSize];
nonce = new byte[24];
}

while (!cancelToken.IsCancellationRequested)
{
await Task.Delay(1).ConfigureAwait(false);
if (_udp.Available > 0)
{
#if !DOTNET5_4
packet = _udp.Receive(ref endpoint);
#else
//TODO: Is this really the only way to end a Receive call in DOTNET5_4?
var receiveTask = _udp.ReceiveAsync();
var task = Task.WhenAny(closeTask, receiveTask).Result;
if (task == closeTask)
break;
var udpPacket = receiveTask.Result;
packet = udpPacket.Buffer;
endpoint = udpPacket.RemoteEndPoint;
#endif
packetLength = packet.Length;

if (packetLength > 0 && endpoint.Equals(_endpoint))
{
if (State != ConnectionState.Connected)
{
if (packetLength != 70)
return;

string ip = Encoding.UTF8.GetString(packet, 4, 70 - 6).TrimEnd('\0');
int port = packet[68] | packet[69] << 8;

SendSelectProtocol(ip, port);
if ((_audioConfig.Mode & AudioMode.Incoming) == 0)
return; //We dont need this thread anymore
}
else
{
//Parse RTP Data
if (packetLength < 12) return;
if (packet[0] != 0x80) return; //Flags
if (packet[1] != 0x78) return; //Payload Type

ushort sequenceNumber = (ushort)((packet[2] << 8) |
packet[3] << 0);
uint timestamp = (uint)((packet[4] << 24) |
(packet[5] << 16) |
(packet[6] << 8) |
(packet[7] << 0));
uint ssrc = (uint)((packet[8] << 24) |
(packet[9] << 16) |
(packet[10] << 8) |
(packet[11] << 0));

//Decrypt
if (_isEncrypted)
{
if (packetLength < 28) //12 + 16 (RTP + Poly1305 MAC)
return;

Buffer.BlockCopy(packet, 0, nonce, 0, 12);
int ret = SecretBox.Decrypt(packet, 12, packetLength - 12, decodingBuffer, nonce, _secretKey);
if (ret != 0)
continue;
result = decodingBuffer;
resultOffset = 0;
resultLength = packetLength - 28;
}
else //Plain
{
result = packet;
resultOffset = 12;
resultLength = packetLength - 12;
}

/*if (_logLevel >= LogMessageSeverity.Debug)
RaiseOnLog(LogMessageSeverity.Debug, $"Received {buffer.Length - 12} bytes.");*/

ulong userId;
if (_ssrcMapping.TryGetValue(ssrc, out userId))
OnFrameReceived(userId, Channel.Id, result, resultOffset, resultLength);
}
}
}
}
}
catch (OperationCanceledException) { }
catch (InvalidOperationException) { } //Includes ObjectDisposedException
}

private async Task SendVoiceAsync(CancellationToken cancelToken)
{
try
{
while (!cancelToken.IsCancellationRequested && State != ConnectionState.Connected)
await Task.Delay(1).ConfigureAwait(false);

if (cancelToken.IsCancellationRequested)
return;

byte[] frame = new byte[_encoder.FrameSize];
byte[] encodedFrame = new byte[MaxOpusSize];
byte[] voicePacket, pingPacket, nonce = null;
uint timestamp = 0;
double nextTicks = 0.0, nextPingTicks = 0.0;
long ticksPerSeconds = Stopwatch.Frequency;
double ticksPerMillisecond = Stopwatch.Frequency / 1000.0;
double ticksPerFrame = ticksPerMillisecond * _encoder.FrameLength;
double spinLockThreshold = 3 * ticksPerMillisecond;
uint samplesPerFrame = (uint)_encoder.SamplesPerFrame;
Stopwatch sw = Stopwatch.StartNew();

if (_isEncrypted)
{
nonce = new byte[24];
voicePacket = new byte[MaxOpusSize + 12 + 16];
}
else
voicePacket = new byte[MaxOpusSize + 12];

pingPacket = new byte[8];

int rtpPacketLength = 0;
voicePacket[0] = 0x80; //Flags;
voicePacket[1] = 0x78; //Payload Type
voicePacket[8] = (byte)(_ssrc >> 24);
voicePacket[9] = (byte)(_ssrc >> 16);
voicePacket[10] = (byte)(_ssrc >> 8);
voicePacket[11] = (byte)(_ssrc >> 0);

if (_isEncrypted)
Buffer.BlockCopy(voicePacket, 0, nonce, 0, 12);

bool hasFrame = false;
while (!cancelToken.IsCancellationRequested)
{
if (!hasFrame && _sendBuffer.Pop(frame))
{
ushort sequence = unchecked(_sequence++);
voicePacket[2] = (byte)(sequence >> 8);
voicePacket[3] = (byte)(sequence >> 0);
voicePacket[4] = (byte)(timestamp >> 24);
voicePacket[5] = (byte)(timestamp >> 16);
voicePacket[6] = (byte)(timestamp >> 8);
voicePacket[7] = (byte)(timestamp >> 0);

//Encode
int encodedLength = _encoder.EncodeFrame(frame, 0, encodedFrame);

//Encrypt
if (_isEncrypted)
{
Buffer.BlockCopy(voicePacket, 2, nonce, 2, 6); //Update nonce
int ret = SecretBox.Encrypt(encodedFrame, encodedLength, voicePacket, 12, nonce, _secretKey);
if (ret != 0)
continue;
rtpPacketLength = encodedLength + 12 + 16;
}
else
{
Buffer.BlockCopy(encodedFrame, 0, voicePacket, 12, encodedLength);
rtpPacketLength = encodedLength + 12;
}

timestamp = unchecked(timestamp + samplesPerFrame);
hasFrame = true;
}

long currentTicks = sw.ElapsedTicks;
double ticksToNextFrame = nextTicks - currentTicks;
if (ticksToNextFrame <= 0.0)
{
if (hasFrame)
{
try
{
_udp.Send(voicePacket, rtpPacketLength);
}
catch (SocketException ex)
{
Logger.Error("Failed to send UDP packet.", ex);
}
hasFrame = false;
}
nextTicks += ticksPerFrame;

//Is it time to send out another ping?
if (currentTicks > nextPingTicks)
{
//Increment in LE
for (int i = 0; i < 8; i++)
{
var b = pingPacket[i];
if (b == byte.MaxValue)
pingPacket[i] = 0;
else
{
pingPacket[i] = (byte)(b + 1);
break;
}
}
await _udp.SendAsync(pingPacket, pingPacket.Length).ConfigureAwait(false);
nextPingTicks = currentTicks + 5 * ticksPerSeconds;
}
}
else
{
if (hasFrame)
{
int time = (int)Math.Floor(ticksToNextFrame / ticksPerMillisecond);
if (time > 0)
await Task.Delay(time).ConfigureAwait(false);
}
else
await Task.Delay(1).ConfigureAwait(false); //Give as much time to the encrypter as possible
}
}
}
catch (OperationCanceledException) { }
catch (InvalidOperationException) { } //Includes ObjectDisposedException
}
#if !DOTNET5_4
//Closes the UDP socket when _disconnectToken is triggered, since UDPClient doesn't allow passing a canceltoken
private async Task WatcherAsync()
{
await CancelToken.Wait().ConfigureAwait(false);
_udp.Close();
}
#endif

protected override async Task ProcessMessage(string json)
{
await base.ProcessMessage(json).ConfigureAwait(false);

WebSocketMessage msg;
using (var reader = new JsonTextReader(new StringReader(json)))
msg = _serializer.Deserialize(reader, typeof(WebSocketMessage)) as WebSocketMessage;
var opCode = (OpCodes)msg.Operation;
switch (opCode)
{
case OpCodes.Ready:
{
if (State != ConnectionState.Connected)
{
var payload = (msg.Payload as JToken).ToObject<ReadyEvent>(_serializer);
_heartbeatInterval = payload.HeartbeatInterval;
_ssrc = payload.SSRC;
var address = (await Dns.GetHostAddressesAsync(Host.Replace("wss://", "")).ConfigureAwait(false)).FirstOrDefault();
_endpoint = new IPEndPoint(address, payload.Port);

if (_audioConfig.EnableEncryption)
{
if (payload.Modes.Contains(EncryptedMode))
{
_encryptionMode = EncryptedMode;
_isEncrypted = true;
}
else
throw new InvalidOperationException("Unexpected encryption format.");
}
else
{
_encryptionMode = UnencryptedMode;
_isEncrypted = false;
}
_udp.Connect(_endpoint);

_sequence = 0;// (ushort)_rand.Next(0, ushort.MaxValue);
//No thread issue here because SendAsync doesn't start until _isReady is true
byte[] packet = new byte[70];
packet[0] = (byte)(_ssrc >> 24);
packet[1] = (byte)(_ssrc >> 16);
packet[2] = (byte)(_ssrc >> 8);
packet[3] = (byte)(_ssrc >> 0);
await _udp.SendAsync(packet, 70).ConfigureAwait(false);
}
}
break;
case OpCodes.Heartbeat:
{
long time = EpochTime.GetMilliseconds();
var payload = (long)msg.Payload;
_ping = (int)(payload - time);
//TODO: Use this to estimate latency
}
break;
case OpCodes.SessionDescription:
{
var payload = (msg.Payload as JToken).ToObject<SessionDescriptionEvent>(_serializer);
_secretKey = payload.SecretKey;
SendSetSpeaking(true);
await EndConnect().ConfigureAwait(false);
}
break;
case OpCodes.Speaking:
{
var payload = (msg.Payload as JToken).ToObject<SpeakingEvent>(_serializer);
OnUserIsSpeaking(payload.UserId, payload.IsSpeaking);
}
break;
default:
Logger.Warning($"Unknown Opcode: {opCode}");
break;
}
}

public void SendPCMFrames(byte[] data, int offset, int count)
{
_sendBuffer.Push(data, offset, count, CancelToken);
}
public void ClearPCMFrames()
{
_sendBuffer.Clear(CancelToken);
}

public void WaitForQueue()
{
_sendBuffer.Wait(CancelToken);
}

public override void SendHeartbeat()
=> QueueMessage(new HeartbeatCommand());
public void SendIdentify(ulong id, string sessionId)
=> QueueMessage(new IdentifyCommand
{
GuildId = Server.Id,
UserId = id,
SessionId = sessionId,
Token = Token
});
public void SendSelectProtocol(string externalAddress, int externalPort)
=> QueueMessage(new SelectProtocolCommand
{
Protocol = "udp",
ExternalAddress = externalAddress,
ExternalPort = externalPort,
EncryptionMode = _encryptionMode
});
public void SendSetSpeaking(bool value)
=> QueueMessage(new SetSpeakingCommand { IsSpeaking = value, Delay = 0 });

}
}

+ 0
- 111
src/Discord.Net.Audio/Opus/OpusConverter.cs View File

@@ -1,111 +0,0 @@
using System;
using System.Runtime.InteropServices;
#if NET45
using System.Security;
#endif

namespace Discord.Audio.Opus
{
internal enum OpusApplication : int
{
Voice = 2048,
MusicOrMixed = 2049,
LowLatency = 2051
}
internal enum OpusError : int
{
OK = 0,
BadArg = -1,
BufferToSmall = -2,
InternalError = -3,
InvalidPacket = -4,
Unimplemented = -5,
InvalidState = -6,
AllocFail = -7
}

internal abstract class OpusConverter : IDisposable
{
protected enum Ctl : int
{
SetBitrateRequest = 4002,
GetBitrateRequest = 4003,
SetInbandFECRequest = 4012,
GetInbandFECRequest = 4013
}

#if NET45
[SuppressUnmanagedCodeSecurity]
#endif
protected unsafe static class UnsafeNativeMethods
{
[DllImport("opus", EntryPoint = "opus_encoder_create", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr CreateEncoder(int Fs, int channels, int application, out OpusError error);
[DllImport("opus", EntryPoint = "opus_encoder_destroy", CallingConvention = CallingConvention.Cdecl)]
public static extern void DestroyEncoder(IntPtr encoder);
[DllImport("opus", EntryPoint = "opus_encode", CallingConvention = CallingConvention.Cdecl)]
public static extern int Encode(IntPtr st, byte* pcm, int frame_size, byte[] data, int max_data_bytes);
[DllImport("opus", EntryPoint = "opus_encoder_ctl", CallingConvention = CallingConvention.Cdecl)]
public static extern int EncoderCtl(IntPtr st, Ctl request, int value);

[DllImport("opus", EntryPoint = "opus_decoder_create", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr CreateDecoder(int Fs, int channels, out OpusError error);
[DllImport("opus", EntryPoint = "opus_decoder_destroy", CallingConvention = CallingConvention.Cdecl)]
public static extern void DestroyDecoder(IntPtr decoder);
[DllImport("opus", EntryPoint = "opus_decode", CallingConvention = CallingConvention.Cdecl)]
public static extern int Decode(IntPtr st, byte* data, int len, byte[] pcm, int frame_size, int decode_fec);
}

protected IntPtr _ptr;

/// <summary> Gets the bit rate of this converter. </summary>
public const int BitsPerSample = 16;
/// <summary> Gets the input sampling rate of this converter. </summary>
public int InputSamplingRate { get; }
/// <summary> Gets the number of channels of this converter. </summary>
public int InputChannels { get; }
/// <summary> Gets the milliseconds per frame. </summary>
public int FrameLength { get; }
/// <summary> Gets the number of samples per frame. </summary>
public int SamplesPerFrame { get; }
/// <summary> Gets the bytes per frame. </summary>
public int FrameSize { get; }
/// <summary> Gets the bytes per sample. </summary>
public int SampleSize { get; }

protected OpusConverter(int samplingRate, int channels, int frameLength)
{
if (samplingRate != 8000 && samplingRate != 12000 &&
samplingRate != 16000 && samplingRate != 24000 &&
samplingRate != 48000)
throw new ArgumentOutOfRangeException(nameof(samplingRate));
if (channels != 1 && channels != 2)
throw new ArgumentOutOfRangeException(nameof(channels));

InputSamplingRate = samplingRate;
InputChannels = channels;
FrameLength = frameLength;
SampleSize = (BitsPerSample / 8) * channels;
SamplesPerFrame = samplingRate / 1000 * FrameLength;
FrameSize = SamplesPerFrame * SampleSize;
}

#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls

protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
disposedValue = true;
}
~OpusConverter() {
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}

+ 0
- 43
src/Discord.Net.Audio/Opus/OpusDecoder.cs View File

@@ -1,43 +0,0 @@
using System;

namespace Discord.Audio.Opus
{
internal class OpusDecoder : OpusConverter
{
/// <summary> Creates a new Opus decoder. </summary>
/// <param name="samplingRate">Sampling rate of the input PCM (in Hz). Supported Values: 8000, 12000, 16000, 24000, or 48000</param>
/// <param name="frameLength">Length, in milliseconds, of each frame. Supported Values: 2.5, 5, 10, 20, 40, or 60</param>
public OpusDecoder(int samplingRate, int channels, int frameLength)
: base(samplingRate, channels, frameLength)
{
OpusError error;
_ptr = UnsafeNativeMethods.CreateDecoder(samplingRate, channels, out error);
if (error != OpusError.OK)
throw new InvalidOperationException($"Error occured while creating decoder: {error}");
}

/// <summary> Produces PCM samples from Opus-encoded audio. </summary>
/// <param name="input">PCM samples to decode.</param>
/// <param name="inputOffset">Offset of the frame in input.</param>
/// <param name="output">Buffer to store the decoded frame.</param>
public unsafe int DecodeFrame(byte[] input, int inputOffset, int inputCount, byte[] output)
{
int result = 0;
fixed (byte* inPtr = input)
result = UnsafeNativeMethods.Decode(_ptr, inPtr + inputOffset, inputCount, output, SamplesPerFrame, 0);

if (result < 0)
throw new Exception(((OpusError)result).ToString());
return result;
}

protected override void Dispose(bool disposing)
{
if (_ptr != IntPtr.Zero)
{
UnsafeNativeMethods.DestroyDecoder(_ptr);
_ptr = IntPtr.Zero;
}
}
}
}

+ 0
- 78
src/Discord.Net.Audio/Opus/OpusEncoder.cs View File

@@ -1,78 +0,0 @@
using System;

namespace Discord.Audio.Opus
{
internal class OpusEncoder : OpusConverter
{
/// <summary> Gets the bit rate in kbit/s. </summary>
public int? BitRate { get; }
/// <summary> Gets the coding mode of the encoder. </summary>
public OpusApplication Application { get; }

/// <summary> Creates a new Opus encoder. </summary>
/// <param name="samplingRate">Sampling rate of the input signal (Hz). Supported Values: 8000, 12000, 16000, 24000, or 48000</param>
/// <param name="channels">Number of channels in input signal. Supported Values: 1 or 2</param>
/// <param name="frameLength">Length, in milliseconds, that each frame takes. Supported Values: 2.5, 5, 10, 20, 40, 60</param>
/// <param name="bitrate">Bitrate (kbit/s) used for this encoder. Supported Values: 1-512. Null will use the recommended bitrate. </param>
/// <param name="application">Coding mode.</param>
public OpusEncoder(int samplingRate, int channels, int frameLength, int? bitrate, OpusApplication application)
: base(samplingRate, channels, frameLength)
{
if (bitrate != null && (bitrate < 1 || bitrate > AudioServiceConfig.MaxBitrate))
throw new ArgumentOutOfRangeException(nameof(bitrate));

BitRate = bitrate;
Application = application;

OpusError error;
_ptr = UnsafeNativeMethods.CreateEncoder(samplingRate, channels, (int)application, out error);
if (error != OpusError.OK)
throw new InvalidOperationException($"Error occured while creating encoder: {error}");

SetForwardErrorCorrection(true);
if (bitrate != null)
SetBitrate(bitrate.Value);
}

/// <summary> Produces Opus encoded audio from PCM samples. </summary>
/// <param name="input">PCM samples to encode.</param>
/// <param name="inputOffset">Offset of the frame in pcmSamples.</param>
/// <param name="output">Buffer to store the encoded frame.</param>
/// <returns>Length of the frame contained in outputBuffer.</returns>
public unsafe int EncodeFrame(byte[] input, int inputOffset, byte[] output)
{
int result = 0;
fixed (byte* inPtr = input)
result = UnsafeNativeMethods.Encode(_ptr, inPtr + inputOffset, SamplesPerFrame, output, output.Length);

if (result < 0)
throw new Exception(((OpusError)result).ToString());
return result;
}

/// <summary> Gets or sets whether Forward Error Correction is enabled. </summary>
public void SetForwardErrorCorrection(bool value)
{
var result = UnsafeNativeMethods.EncoderCtl(_ptr, Ctl.SetInbandFECRequest, value ? 1 : 0);
if (result < 0)
throw new Exception(((OpusError)result).ToString());
}

/// <summary> Gets or sets whether Forward Error Correction is enabled. </summary>
public void SetBitrate(int value)
{
var result = UnsafeNativeMethods.EncoderCtl(_ptr, Ctl.SetBitrateRequest, value * 1000);
if (result < 0)
throw new Exception(((OpusError)result).ToString());
}

protected override void Dispose(bool disposing)
{
if (_ptr != IntPtr.Zero)
{
UnsafeNativeMethods.DestroyEncoder(_ptr);
_ptr = IntPtr.Zero;
}
}
}
}

+ 0
- 32
src/Discord.Net.Audio/Sodium/SecretBox.cs View File

@@ -1,32 +0,0 @@
using System.Runtime.InteropServices;
#if NET45
using System.Security;
#endif

namespace Discord.Audio.Sodium
{
internal unsafe static class SecretBox
{
#if NET45
[SuppressUnmanagedCodeSecurity]
#endif
private static class SafeNativeMethods
{
[DllImport("libsodium", EntryPoint = "crypto_secretbox_easy", CallingConvention = CallingConvention.Cdecl)]
public static extern int SecretBoxEasy(byte* output, byte[] input, long inputLength, byte[] nonce, byte[] secret);
[DllImport("libsodium", EntryPoint = "crypto_secretbox_open_easy", CallingConvention = CallingConvention.Cdecl)]
public static extern int SecretBoxOpenEasy(byte[] output, byte* input, long inputLength, byte[] nonce, byte[] secret);
}

public static int Encrypt(byte[] input, long inputLength, byte[] output, int outputOffset, byte[] nonce, byte[] secret)
{
fixed (byte* outPtr = output)
return SafeNativeMethods.SecretBoxEasy(outPtr + outputOffset, input, inputLength, nonce, secret);
}
public static int Decrypt(byte[] input, int inputOffset, long inputLength, byte[] output, byte[] nonce, byte[] secret)
{
fixed (byte* inPtr = input)
return SafeNativeMethods.SecretBoxOpenEasy(output, inPtr + inputLength, inputLength, nonce, secret);
}
}
}

+ 0
- 13
src/Discord.Net.Audio/UserIsTalkingEventArgs.cs View File

@@ -1,13 +0,0 @@
namespace Discord
{
public class UserIsSpeakingEventArgs : UserEventArgs
{
public bool IsSpeaking { get; }

public UserIsSpeakingEventArgs(User user, bool isSpeaking)
: base(user)
{
IsSpeaking = isSpeaking;
}
}
}

+ 0
- 39
src/Discord.Net.Audio/VirtualClient.cs View File

@@ -1,39 +0,0 @@
using Discord.Net.Rest;
using Discord.Net.WebSockets;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace Discord.Audio
{
internal class VirtualClient : IAudioClient
{
private readonly AudioClient _client;

public Server Server { get; }

public int Id => 0;
public string SessionId => _client.Server == Server ? _client.SessionId : null;

public ConnectionState State => _client.Server == Server ? _client.State : ConnectionState.Disconnected;
public VoiceChannel Channel => _client.Server == Server ? _client.Channel : null;
public CancellationToken CancelToken => _client.Server == Server ? _client.CancelToken : CancellationToken.None;

public RestClient ClientAPI => _client.Server == Server ? _client.ClientAPI : null;
public GatewaySocket GatewaySocket => _client.Server == Server ? _client.GatewaySocket : null;
public VoiceSocket VoiceSocket => _client.Server == Server ? _client.VoiceSocket : null;

public VirtualClient(AudioClient client, Server server)
{
_client = client;
Server = server;
}

public Task Disconnect() => _client.Service.Leave(Server);
public Task Join(VoiceChannel channel) => _client.Join(channel);

public void Send(byte[] data, int offset, int count) => _client.Send(data, offset, count);
public void Clear() => _client.Clear();
public void Wait() => _client.Wait();
}
}

+ 0
- 140
src/Discord.Net.Audio/VoiceBuffer.cs View File

@@ -1,140 +0,0 @@
using Nito.AsyncEx;
using System;
using System.Threading;

namespace Discord.Audio
{
internal class VoiceBuffer
{
private readonly int _frameSize, _frameCount, _bufferSize;
private readonly byte[] _buffer;
private readonly byte[] _blankFrame;
private ushort _readCursor, _writeCursor;
private ManualResetEventSlim _notOverflowEvent;
private bool _isClearing;
private AsyncLock _lock;

public int FrameSize => _frameSize;
public int FrameCount => _frameCount;
public ushort ReadPos => _readCursor;
public ushort WritePos => _writeCursor;

public VoiceBuffer(int frameCount, int frameSize)
{
_frameSize = frameSize;
_frameCount = frameCount;
_bufferSize = _frameSize * _frameCount;
_readCursor = 0;
_writeCursor = 0;
_buffer = new byte[_bufferSize];
_blankFrame = new byte[_frameSize];
_notOverflowEvent = new ManualResetEventSlim(); //Notifies when an overflow is solved
_lock = new AsyncLock();
}

public void Push(byte[] buffer, int offset, int count, CancellationToken cancelToken)
{
if (cancelToken.IsCancellationRequested)
throw new OperationCanceledException("Client is disconnected.", cancelToken);

int wholeFrames = count / _frameSize;
int expectedBytes = wholeFrames * _frameSize;
int lastFrameSize = count - expectedBytes;

using (_lock.Lock())
{
for (int i = 0, pos = offset; i <= wholeFrames; i++, pos += _frameSize)
{
//If the read cursor is in the next position, wait for it to move.
ushort nextPosition = _writeCursor;
AdvanceCursorPos(ref nextPosition);
if (_readCursor == nextPosition)
{
_notOverflowEvent.Reset();
try
{
_notOverflowEvent.Wait(cancelToken);
}
catch (OperationCanceledException ex)
{
throw new OperationCanceledException("Client is disconnected.", ex, cancelToken);
}
}

if (i == wholeFrames)
{
//If there are no partial frames, skip this step
if (lastFrameSize == 0)
break;

//Copy partial frame
Buffer.BlockCopy(buffer, pos, _buffer, _writeCursor * _frameSize, lastFrameSize);

//Wipe the end of the buffer
Buffer.BlockCopy(_blankFrame, 0, _buffer, _writeCursor * _frameSize + lastFrameSize, _frameSize - lastFrameSize);
}
else
{
//Copy full frame
Buffer.BlockCopy(buffer, pos, _buffer, _writeCursor * _frameSize, _frameSize);
}

//Advance the write cursor to the next position
AdvanceCursorPos(ref _writeCursor);
}
}
}

public bool Pop(byte[] buffer)
{
//using (_lock.Lock())
//{
if (_writeCursor == _readCursor)
{
_notOverflowEvent.Set();
return false;
}

bool isClearing = _isClearing;
if (!isClearing)
Buffer.BlockCopy(_buffer, _readCursor * _frameSize, buffer, 0, _frameSize);

//Advance the read cursor to the next position
AdvanceCursorPos(ref _readCursor);
_notOverflowEvent.Set();
return !isClearing;
//}
}

public void Clear(CancellationToken cancelToken)
{
using (_lock.Lock())
{
_isClearing = true;
for (int i = 0; i < _frameCount; i++)
Buffer.BlockCopy(_blankFrame, 0, _buffer, i * _frameCount, i++);

_writeCursor = 0;
_readCursor = 0;
_isClearing = false;
}
}

public void Wait(CancellationToken cancelToken)
{
while (true)
{
_notOverflowEvent.Wait(cancelToken);
if (_writeCursor == _readCursor)
break;
}
}

private void AdvanceCursorPos(ref ushort pos)
{
pos++;
if (pos == _frameCount)
pos = 0;
}
}
}

+ 0
- 15
src/Discord.Net.Audio/VoiceDisconnectedEventArgs.cs View File

@@ -1,15 +0,0 @@
using System;

namespace Discord
{
public class VoiceDisconnectedEventArgs : DisconnectedEventArgs
{
public ulong ServerId { get; }

public VoiceDisconnectedEventArgs(ulong serverId, bool wasUnexpected, Exception ex)
: base(wasUnexpected, ex)
{
ServerId = serverId;
}
}
}

BIN
src/Discord.Net.Audio/libsodium.dll View File


Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save