@@ -1,6 +1,6 @@ | |||
| |||
Microsoft Visual Studio Solution File, Format Version 12.00 | |||
# Visual Studio 2012 | |||
# Visual Studio 14 | |||
VisualStudioVersion = 14.0.23107.0 | |||
MinimumVisualStudioVersion = 10.0.40219.1 | |||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8D7989F0-66CE-4DBB-8230-D8C811E9B1D7}" | |||
@@ -13,10 +13,6 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Commands", "src | |||
EndProject | |||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "net45", "net45", "{DF03D4E8-38F6-4FE1-BC52-E38124BE8AFD}" | |||
EndProject | |||
Project("{9344BDBB-3E7F-41FC-A0DD-8665D75EE146}") = "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("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{6317A2E6-8E36-4C3E-949B-3F10EC888AB9}" | |||
EndProject | |||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net.Tests", "test\Discord.Net.Tests\Discord.Net.Tests.csproj", "{855D6B1D-847B-42DA-BE6A-23683EA89511}" | |||
@@ -26,43 +22,58 @@ 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 | |||
Global | |||
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 = FullDebug|Any CPU | |||
{ACFB060B-EC8A-4926-B293-04C01E17EE23}.FullDebug|Any CPU.Build.0 = FullDebug|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 | |||
{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}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{1B5603B4-6F8F-4289-B945-7BAAE523D740}.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 = FullDebug|Any CPU | |||
{8D71A857-879A-4A10-859E-5FF824ED6688}.FullDebug|Any CPU.Build.0 = FullDebug|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 | |||
{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}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{ACFB060B-EC8A-4926-B293-04C01E17EE23}.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 | |||
EndGlobalSection | |||
GlobalSection(SolutionProperties) = preSolution | |||
HideSolutionNode = FALSE | |||
EndGlobalSection | |||
GlobalSection(NestedProjects) = preSolution | |||
{EA68EBE2-51C8-4440-9EF7-D633C90A5D35} = {8D7989F0-66CE-4DBB-8230-D8C811E9B1D7} | |||
{DF03D4E8-38F6-4FE1-BC52-E38124BE8AFD} = {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} | |||
{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} | |||
{855D6B1D-847B-42DA-BE6A-23683EA89511} = {6317A2E6-8E36-4C3E-949B-3F10EC888AB9} | |||
EndGlobalSection | |||
GlobalSection(SolutionProperties) = preSolution | |||
HideSolutionNode = FALSE | |||
EndGlobalSection | |||
EndGlobal |
@@ -1,10 +1,10 @@ | |||
# Discord.Net v0.7.3-beta4 | |||
# Discord.Net v0.8.0-beta1 | |||
An unofficial .Net API Wrapper for the Discord client (http://discordapp.com). | |||
Check out the [documentation](https://discordnet.readthedocs.org/en/latest/) or join the [Discord API Chat](https://discord.gg/0SBTUU1wZTVjAMPx). | |||
### Current Features | |||
- Using Discord API version 3 | |||
- Using Discord API v3 | |||
- Supports .Net 4.5 and DNX 4.5.1 | |||
- Server Management (Servers, Channels, Messages, Invites, Roles, Users) | |||
- Send/Receieve Messages (Including mentions and formatting) | |||
@@ -17,6 +17,4 @@ You can download Discord.Net from NuGet: | |||
- [Discord.Net.Commands](https://www.nuget.org/packages/Discord.Net.Commands/) | |||
### Known Issues | |||
- Due to current Discord restrictions, private messages are blocked unless both the sender and recipient are members of the same server. | |||
- The Message cache does not currently clean up when their entries are no longer referenced, and there is currently no cap to it. For now, disconnecting and reconnecting will clear all caches. | |||
- DNX Core 5.0 is experiencing several network-related issues and support has been temporarily dropped. |
@@ -52,11 +52,14 @@ | |||
<Compile Include="..\Discord.Net.Commands\CommandsPlugin.Events.cs"> | |||
<Link>CommandsPlugin.Events.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Shared\TaskHelper.cs"> | |||
<Link>Shared\TaskHelper.cs</Link> | |||
</Compile> | |||
<Compile Include="Properties\AssemblyInfo.cs" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<ProjectReference Include="..\Discord.Net.Net45\Discord.Net.csproj"> | |||
<Project>{8d71a857-879a-4a10-859e-5ff824ed6688}</Project> | |||
<Name>Discord.Net</Name> | |||
</ProjectReference> | |||
</ItemGroup> | |||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | |||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. | |||
Other similar extension points exist, see Microsoft.Common.targets. | |||
@@ -65,10 +68,4 @@ | |||
<Target Name="AfterBuild"> | |||
</Target> | |||
--> | |||
<ItemGroup> | |||
<ProjectReference Include="..\Discord.Net.Net45\Discord.Net.csproj"> | |||
<Project>{8D71A857-879A-4A10-859E-5FF824ED6688}</Project> | |||
<Name>Discord.Net</Name> | |||
</ProjectReference> | |||
</ItemGroup> | |||
</Project> |
@@ -13,6 +13,6 @@ using System.Runtime.InteropServices; | |||
[assembly: ComVisible(false)] | |||
[assembly: Guid("76ea00e6-ea24-41e1-acb2-639c0313fa80")] | |||
[assembly: AssemblyVersion("0.7.3.0")] | |||
[assembly: AssemblyFileVersion("0.7.3.0")] | |||
[assembly: AssemblyVersion("0.8.0.0")] | |||
[assembly: AssemblyFileVersion("0.8.0.0")] | |||
@@ -11,14 +11,10 @@ namespace Discord.Commands | |||
public string ArgText { get; } | |||
public int? Permissions { get; } | |||
public string[] Args { get; } | |||
public User User => Message.User; | |||
public string UserId => Message.UserId; | |||
public Member Member => Message.Member; | |||
public Channel Channel => Message.Channel; | |||
public string ChannelId => Message.ChannelId; | |||
public Server Server => Message.Channel.Server; | |||
public string ServerId => Message.Channel.ServerId; | |||
public CommandEventArgs(Message message, Command command, string commandText, string argText, int? permissions, string[] args) | |||
{ | |||
@@ -8,7 +8,7 @@ namespace Discord.Commands | |||
{ | |||
private readonly DiscordClient _client; | |||
private List<Command> _commands; | |||
private Func<User, Server, int> _getPermissions; | |||
private Func<User, int> _getPermissions; | |||
public IEnumerable<Command> Commands => _commands; | |||
@@ -17,7 +17,7 @@ namespace Discord.Commands | |||
public bool RequireCommandCharInPublic { get; set; } | |||
public bool RequireCommandCharInPrivate { get; set; } | |||
public CommandsPlugin(DiscordClient client, Func<User, Server, int> getPermissions = null) | |||
public CommandsPlugin(DiscordClient client, Func<User, int> getPermissions = null) | |||
{ | |||
_client = client; | |||
_getPermissions = getPermissions; | |||
@@ -28,14 +28,14 @@ namespace Discord.Commands | |||
RequireCommandCharInPublic = true; | |||
RequireCommandCharInPrivate = true; | |||
client.MessageCreated += async (s, e) => | |||
client.MessageReceived += async (s, e) => | |||
{ | |||
//If commands aren't being used, don't bother processing them | |||
if (_commands.Count == 0) | |||
return; | |||
//Ignore messages from ourselves | |||
if (e.Message.UserId == client.CurrentUserId) | |||
if (e.Message.User == client.CurrentUser) | |||
return; | |||
//Check for the command character | |||
@@ -96,7 +96,7 @@ namespace Discord.Commands | |||
argText = msg.Substring(args[cmd.Parts.Length].Index); | |||
//Check Permissions | |||
int permissions = _getPermissions != null ? _getPermissions(e.Message.User, e.Message.Channel?.Server) : 0; | |||
int permissions = _getPermissions != null ? _getPermissions(e.Message.User) : 0; | |||
var eventArgs = new CommandEventArgs(e.Message, cmd, msg, argText, permissions, newArgs); | |||
if (permissions < cmd.MinPerms) | |||
{ | |||
@@ -110,7 +110,7 @@ namespace Discord.Commands | |||
{ | |||
var task = cmd.Handler(eventArgs); | |||
if (task != null) | |||
await task; | |||
await task.ConfigureAwait(false); | |||
} | |||
catch (Exception ex) | |||
{ | |||
@@ -1,11 +1,10 @@ | |||
{ | |||
"version": "0.7.3", | |||
"version": "0.8.0-beta1", | |||
"description": "A Discord.Net extension adding basic command support.", | |||
"authors": [ "RogueException" ], | |||
"tags": [ "discord", "discordapp" ], | |||
"projectUrl": "https://github.com/RogueException/Discord.Net", | |||
"licenseUrl": "http://opensource.org/licenses/MIT", | |||
"compile": ["**/*.cs", "../Discord.Net/Shared/*.cs"], | |||
"repository": { | |||
"type": "git", | |||
"url": "git://github.com/RogueException/Discord.Net" | |||
@@ -14,7 +13,7 @@ | |||
"warningsAsErrors": true | |||
}, | |||
"dependencies": { | |||
"Discord.Net": "0.7.3" | |||
"Discord.Net": "0.8.0-beta1" | |||
}, | |||
"frameworks": { | |||
"net45": { }, | |||
@@ -67,37 +67,74 @@ | |||
</Reference> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<Content Include="lib\libopus.so" /> | |||
<Content Include="lib\libsodium.dll" /> | |||
<Content Include="lib\opus.dll" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<Compile Include="..\Discord.Net\API\Common.cs"> | |||
<Link>API\Common.cs</Link> | |||
<Compile Include="..\Discord.Net\API\Auth.cs"> | |||
<Link>API\Auth.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\API\Bans.cs"> | |||
<Link>API\Bans.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\API\Channels.cs"> | |||
<Link>API\Channels.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\API\Endpoints.cs"> | |||
<Link>API\Endpoints.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\API\HttpException.cs"> | |||
<Link>API\HttpException.cs</Link> | |||
<Compile Include="..\Discord.Net\API\Enums\AvatarImageType.cs"> | |||
<Link>API\Enums\AvatarImageType.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\API\Enums\ChannelType.cs"> | |||
<Link>API\Enums\ChannelType.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\API\Enums\PermissionTarget.cs"> | |||
<Link>API\Enums\PermissionTarget.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\API\Enums\Regions.cs"> | |||
<Link>API\Enums\Regions.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\API\Enums\StringEnum.cs"> | |||
<Link>API\Enums\StringEnum.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\API\Enums\UserStatus.cs"> | |||
<Link>API\Enums\UserStatus.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\API\Invites.cs"> | |||
<Link>API\Invites.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\API\Maintenance.cs"> | |||
<Link>API\Maintenance.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\API\Requests.cs"> | |||
<Link>API\Requests.cs</Link> | |||
<Compile Include="..\Discord.Net\API\Members.cs"> | |||
<Link>API\Members.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\API\Responses.cs"> | |||
<Link>API\Responses.cs</Link> | |||
<Compile Include="..\Discord.Net\API\Messages.cs"> | |||
<Link>API\Messages.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\API\RestClient.BuiltIn.cs"> | |||
<Link>API\RestClient.BuiltIn.cs</Link> | |||
<Compile Include="..\Discord.Net\API\Permissions.cs"> | |||
<Link>API\Permissions.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\API\RestClient.cs"> | |||
<Link>API\RestClient.cs</Link> | |||
<Compile Include="..\Discord.Net\API\Presence.cs"> | |||
<Link>API\Presence.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\API\RestClient.Events.cs"> | |||
<Link>API\RestClient.Events.cs</Link> | |||
<Compile Include="..\Discord.Net\API\Roles.cs"> | |||
<Link>API\Roles.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\API\RestClient.SharpRest.cs"> | |||
<Link>API\RestClient.SharpRest.cs</Link> | |||
<Compile Include="..\Discord.Net\API\Servers.cs"> | |||
<Link>API\Servers.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\API\Users.cs"> | |||
<Link>API\Users.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\API\Voice.cs"> | |||
<Link>API\Voice.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\API\WebSockets.cs"> | |||
<Link>API\WebSockets.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Audio\IDiscordVoiceBuffer.cs"> | |||
<Link>Audio\IDiscordVoiceBuffer.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Audio\IDiscordVoiceClient.cs"> | |||
<Link>Audio\IDiscordVoiceClient.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Audio\Opus.cs"> | |||
<Link>Audio\Opus.cs</Link> | |||
@@ -111,74 +148,68 @@ | |||
<Compile Include="..\Discord.Net\Audio\Sodium.cs"> | |||
<Link>Audio\Sodium.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Collections\AsyncCollection.cs"> | |||
<Link>Collections\AsyncCollection.cs</Link> | |||
<Compile Include="..\Discord.Net\Audio\VoiceBuffer.cs"> | |||
<Link>Audio\VoiceBuffer.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Collections\Channels.cs"> | |||
<Link>Collections\Channels.cs</Link> | |||
<Compile Include="..\Discord.Net\DiscordAPIClient.cs"> | |||
<Link>DiscordAPIClient.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Collections\Members.cs"> | |||
<Link>Collections\Members.cs</Link> | |||
<Compile Include="..\Discord.Net\DiscordAPIClientConfig.cs"> | |||
<Link>DiscordAPIClientConfig.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Collections\Messages.cs"> | |||
<Link>Collections\Messages.cs</Link> | |||
<Compile Include="..\Discord.Net\DiscordClient.Bans.cs"> | |||
<Link>DiscordClient.Bans.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Collections\Roles.cs"> | |||
<Link>Collections\Roles.cs</Link> | |||
<Compile Include="..\Discord.Net\DiscordClient.Channels.cs"> | |||
<Link>DiscordClient.Channels.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Collections\Servers.cs"> | |||
<Link>Collections\Servers.cs</Link> | |||
<Compile Include="..\Discord.Net\DiscordClient.cs"> | |||
<Link>DiscordClient.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Collections\Users.cs"> | |||
<Link>Collections\Users.cs</Link> | |||
<Compile Include="..\Discord.Net\DiscordClient.Invites.cs"> | |||
<Link>DiscordClient.Invites.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\DiscordAPIClient.cs"> | |||
<Link>DiscordAPIClient.cs</Link> | |||
<Compile Include="..\Discord.Net\DiscordClient.Messages.cs"> | |||
<Link>DiscordClient.Messages.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\DiscordClient.API.cs"> | |||
<Link>DiscordClient.API.cs</Link> | |||
<Compile Include="..\Discord.Net\DiscordClient.Permissions.cs"> | |||
<Link>DiscordClient.Permissions.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\DiscordClient.Cache.cs"> | |||
<Link>DiscordClient.Cache.cs</Link> | |||
<Compile Include="..\Discord.Net\DiscordClient.Roles.cs"> | |||
<Link>DiscordClient.Roles.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\DiscordClient.cs"> | |||
<Link>DiscordClient.cs</Link> | |||
<Compile Include="..\Discord.Net\DiscordClient.Servers.cs"> | |||
<Link>DiscordClient.Servers.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\DiscordClient.Users.cs"> | |||
<Link>DiscordClient.Users.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\DiscordClient.Events.cs"> | |||
<Link>DiscordClient.Events.cs</Link> | |||
<Compile Include="..\Discord.Net\DiscordClient.Voice.cs"> | |||
<Link>DiscordClient.Voice.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\DiscordClientConfig.cs"> | |||
<Link>DiscordClientConfig.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\DiscordSimpleClient.cs"> | |||
<Link>DiscordSimpleClient.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\DiscordSimpleClient.Events.cs"> | |||
<Link>DiscordSimpleClient.Events.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\DiscordSimpleClient.Voice.cs"> | |||
<Link>DiscordSimpleClient.Voice.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\DiscordSimpleClientConfig.cs"> | |||
<Link>DiscordSimpleClientConfig.cs</Link> | |||
<Compile Include="..\Discord.Net\DiscordWSClient.cs"> | |||
<Link>DiscordWSClient.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Enums\ChannelTypes.cs"> | |||
<Link>Enums\ChannelTypes.cs</Link> | |||
<Compile Include="..\Discord.Net\DiscordWSClient.Events.cs"> | |||
<Link>DiscordWSClient.Events.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Enums\PermissionTarget.cs"> | |||
<Link>Enums\PermissionTarget.cs</Link> | |||
<Compile Include="..\Discord.Net\DiscordWSClient.Voice.cs"> | |||
<Link>DiscordWSClient.Voice.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Enums\Regions.cs"> | |||
<Link>Enums\Regions.cs</Link> | |||
<Compile Include="..\Discord.Net\DiscordWSClientConfig.cs"> | |||
<Link>DiscordWSClientConfig.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Enums\UserStatus.cs"> | |||
<Link>Enums\UserStatus.cs</Link> | |||
<Compile Include="..\Discord.Net\Helpers\AsyncCollection.cs"> | |||
<Link>Helpers\AsyncCollection.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Format.cs"> | |||
<Link>Format.cs</Link> | |||
<Compile Include="..\Discord.Net\Helpers\BitHelper.cs"> | |||
<Link>Helpers\BitHelper.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Helpers\CollectionHelper.cs"> | |||
<Link>Helpers\CollectionHelper.cs</Link> | |||
<Compile Include="..\Discord.Net\Helpers\CachedObject.cs"> | |||
<Link>Helpers\CachedObject.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Helpers\EpochTime.cs"> | |||
<Link>Helpers\EpochTime.cs</Link> | |||
@@ -186,32 +217,38 @@ | |||
<Compile Include="..\Discord.Net\Helpers\Extensions.cs"> | |||
<Link>Helpers\Extensions.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Helpers\MessageCleaner.cs"> | |||
<Link>Helpers\MessageCleaner.cs</Link> | |||
<Compile Include="..\Discord.Net\Helpers\Format.cs"> | |||
<Link>Helpers\Format.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Helpers\TaskExtensions.cs"> | |||
<Link>Helpers\TaskExtensions.cs</Link> | |||
<Compile Include="..\Discord.Net\Helpers\Mention.cs"> | |||
<Link>Helpers\Mention.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Mention.cs"> | |||
<Link>Mention.cs</Link> | |||
<Compile Include="..\Discord.Net\Helpers\Reference.cs"> | |||
<Link>Helpers\Reference.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Helpers\TaskHelper.cs"> | |||
<Link>Helpers\TaskHelper.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\HttpException.cs"> | |||
<Link>HttpException.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Models\Channel.cs"> | |||
<Link>Models\Channel.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Models\Color.cs"> | |||
<Link>Models\Color.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Models\GlobalUser.cs"> | |||
<Link>Models\GlobalUser.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Models\Invite.cs"> | |||
<Link>Models\Invite.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Models\Member.cs"> | |||
<Link>Models\Member.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Models\Message.cs"> | |||
<Link>Models\Message.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Models\PackedColor.cs"> | |||
<Link>Models\PackedColor.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Models\PackedPermissions.cs"> | |||
<Link>Models\PackedPermissions.cs</Link> | |||
<Compile Include="..\Discord.Net\Models\Permissions.cs"> | |||
<Link>Models\Permissions.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Models\Role.cs"> | |||
<Link>Models\Role.cs</Link> | |||
@@ -222,59 +259,62 @@ | |||
<Compile Include="..\Discord.Net\Models\User.cs"> | |||
<Link>Models\User.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Shared\TaskHelper.cs"> | |||
<Link>Shared\TaskHelper.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\TimeoutException.cs"> | |||
<Link>TimeoutException.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\WebSockets\Data\Commands.cs"> | |||
<Link>WebSockets\Data\Commands.cs</Link> | |||
<Compile Include="..\Discord.Net\Net\Rest\IRestEngine.cs"> | |||
<Link>Net\Rest\IRestEngine.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\WebSockets\Data\DataWebSocket.cs"> | |||
<Link>WebSockets\Data\DataWebSocket.cs</Link> | |||
<Compile Include="..\Discord.Net\Net\Rest\RestClient.cs"> | |||
<Link>Net\Rest\RestClient.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\WebSockets\Data\DataWebSockets.Events.cs"> | |||
<Link>WebSockets\Data\DataWebSockets.Events.cs</Link> | |||
<Compile Include="..\Discord.Net\Net\Rest\RestClient.Events.cs"> | |||
<Link>Net\Rest\RestClient.Events.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\WebSockets\Data\Events.cs"> | |||
<Link>WebSockets\Data\Events.cs</Link> | |||
<Compile Include="..\Discord.Net\Net\Rest\SharpRestEngine.cs"> | |||
<Link>Net\Rest\SharpRestEngine.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\WebSockets\Voice\Commands.cs"> | |||
<Link>WebSockets\Voice\Commands.cs</Link> | |||
<Compile Include="..\Discord.Net\Net\WebSockets\DataWebSocket.cs"> | |||
<Link>Net\WebSockets\DataWebSocket.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\WebSockets\Voice\Events.cs"> | |||
<Link>WebSockets\Voice\Events.cs</Link> | |||
<Compile Include="..\Discord.Net\Net\WebSockets\DataWebSockets.Events.cs"> | |||
<Link>Net\WebSockets\DataWebSockets.Events.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\WebSockets\Voice\VoiceBuffer.cs"> | |||
<Link>WebSockets\Voice\VoiceBuffer.cs</Link> | |||
<Compile Include="..\Discord.Net\Net\WebSockets\IWebSocketEngine.cs"> | |||
<Link>Net\WebSockets\IWebSocketEngine.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\WebSockets\Voice\VoiceWebSocket.cs"> | |||
<Link>WebSockets\Voice\VoiceWebSocket.cs</Link> | |||
<Compile Include="..\Discord.Net\Net\WebSockets\VoiceWebSocket.cs"> | |||
<Link>Net\WebSockets\VoiceWebSocket.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\WebSockets\Voice\VoiceWebSocket.Events.cs"> | |||
<Link>WebSockets\Voice\VoiceWebSocket.Events.cs</Link> | |||
<Compile Include="..\Discord.Net\Net\WebSockets\VoiceWebSocket.Events.cs"> | |||
<Link>Net\WebSockets\VoiceWebSocket.Events.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\WebSockets\WebSocket.BuiltIn.cs"> | |||
<Link>WebSockets\WebSocket.BuiltIn.cs</Link> | |||
<Compile Include="..\Discord.Net\Net\WebSockets\WebSocket.cs"> | |||
<Link>Net\WebSockets\WebSocket.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\WebSockets\WebSocket.cs"> | |||
<Link>WebSockets\WebSocket.cs</Link> | |||
<Compile Include="..\Discord.Net\Net\WebSockets\WebSocket.Events.cs"> | |||
<Link>Net\WebSockets\WebSocket.Events.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\WebSockets\WebSocket.Events.cs"> | |||
<Link>WebSockets\WebSocket.Events.cs</Link> | |||
<Compile Include="..\Discord.Net\Net\WebSockets\WebSocketSharpEngine.cs"> | |||
<Link>Net\WebSockets\WebSocketSharpEngine.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\WebSockets\WebSocket.WebSocketSharp.cs"> | |||
<Link>WebSockets\WebSocket.WebSocketSharp.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\WebSockets\WebSocketMessage.cs"> | |||
<Link>WebSockets\WebSocketMessage.cs</Link> | |||
<Compile Include="..\Discord.Net\TimeoutException.cs"> | |||
<Link>TimeoutException.cs</Link> | |||
</Compile> | |||
<Compile Include="Properties\AssemblyInfo.cs" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<None Include="..\Discord.Net\lib\libopus.so"> | |||
<Link>lib\libopus.so</Link> | |||
</None> | |||
<None Include="packages.config" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<Content Include="..\Discord.Net\lib\libsodium.dll"> | |||
<Link>lib\libsodium.dll</Link> | |||
</Content> | |||
<Content Include="..\Discord.Net\lib\opus.dll"> | |||
<Link>lib\opus.dll</Link> | |||
</Content> | |||
</ItemGroup> | |||
<ItemGroup /> | |||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | |||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. | |||
Other similar extension points exist, see Microsoft.Common.targets. | |||
@@ -13,5 +13,5 @@ using System.Runtime.InteropServices; | |||
[assembly: ComVisible(false)] | |||
[assembly: Guid("76ea00e6-ea24-41e1-acb2-639c0313fa80")] | |||
[assembly: AssemblyVersion("0.7.3.0")] | |||
[assembly: AssemblyFileVersion("0.7.3.0")] | |||
[assembly: AssemblyVersion("0.8.0")] | |||
[assembly: AssemblyFileVersion("0.8.0")] |
@@ -0,0 +1,29 @@ | |||
//Ignore unused/unassigned variable warnings | |||
#pragma warning disable CS0649 | |||
#pragma warning disable CS0169 | |||
using Newtonsoft.Json; | |||
namespace Discord.API | |||
{ | |||
//Gateway | |||
public class GatewayResponse | |||
{ | |||
[JsonProperty("url")] | |||
public string Url; | |||
} | |||
//Login | |||
public sealed class LoginRequest | |||
{ | |||
[JsonProperty("email")] | |||
public string Email; | |||
[JsonProperty("password")] | |||
public string Password; | |||
} | |||
public sealed class LoginResponse | |||
{ | |||
[JsonProperty("token")] | |||
public string Token; | |||
} | |||
} |
@@ -0,0 +1,10 @@ | |||
//Ignore unused/unassigned variable warnings | |||
#pragma warning disable CS0649 | |||
#pragma warning disable CS0169 | |||
namespace Discord.API | |||
{ | |||
//Events | |||
internal sealed class BanAddEvent : MemberReference { } | |||
internal sealed class BanRemoveEvent : MemberReference { } | |||
} |
@@ -0,0 +1,100 @@ | |||
//Ignore unused/unassigned variable warnings | |||
#pragma warning disable CS0649 | |||
#pragma warning disable CS0169 | |||
using Newtonsoft.Json; | |||
using System.Collections; | |||
using System.Collections.Generic; | |||
namespace Discord.API | |||
{ | |||
//Common | |||
public class ChannelReference | |||
{ | |||
[JsonProperty("id")] | |||
public string Id; | |||
[JsonProperty("guild_id")] | |||
public string GuildId; | |||
[JsonProperty("name")] | |||
public string Name; | |||
[JsonProperty("type")] | |||
public string Type; | |||
} | |||
public class ChannelInfo : ChannelReference | |||
{ | |||
public sealed class PermissionOverwrite | |||
{ | |||
[JsonProperty("type")] | |||
public string Type; | |||
[JsonProperty("id")] | |||
public string Id; | |||
[JsonProperty("deny")] | |||
public uint Deny; | |||
[JsonProperty("allow")] | |||
public uint Allow; | |||
} | |||
[JsonProperty("last_message_id")] | |||
public string LastMessageId; | |||
[JsonProperty("is_private")] | |||
public bool IsPrivate; | |||
[JsonProperty("position")] | |||
public int? Position; | |||
[JsonProperty("topic")] | |||
public string Topic; | |||
[JsonProperty("permission_overwrites")] | |||
public PermissionOverwrite[] PermissionOverwrites; | |||
[JsonProperty("recipient")] | |||
public UserReference Recipient; | |||
} | |||
//Create | |||
public class CreateChannelRequest | |||
{ | |||
[JsonProperty("name")] | |||
public string Name; | |||
[JsonProperty("type")] | |||
public string Type; | |||
} | |||
public class CreatePMChannelRequest | |||
{ | |||
[JsonProperty("recipient_id")] | |||
public string RecipientId; | |||
} | |||
public class CreateChannelResponse : ChannelInfo { } | |||
//Edit | |||
public class EditChannelRequest | |||
{ | |||
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] | |||
public string Name; | |||
[JsonProperty("topic", NullValueHandling = NullValueHandling.Ignore)] | |||
public string Topic; | |||
} | |||
public class EditChannelResponse : ChannelInfo { } | |||
//Destroy | |||
public class DestroyChannelResponse : ChannelInfo { } | |||
//Reorder | |||
public class ReorderChannelsRequest : IEnumerable<ReorderChannelsRequest.Channel> | |||
{ | |||
public sealed class Channel | |||
{ | |||
[JsonProperty("id")] | |||
public string Id; | |||
[JsonProperty("position")] | |||
public uint Position; | |||
} | |||
private IEnumerable<Channel> _channels; | |||
public ReorderChannelsRequest(IEnumerable<Channel> channels) { _channels = channels; } | |||
public IEnumerator<Channel> GetEnumerator() => _channels.GetEnumerator(); | |||
IEnumerator IEnumerable.GetEnumerator() => _channels.GetEnumerator(); | |||
} | |||
//Events | |||
internal sealed class ChannelCreateEvent : ChannelInfo { } | |||
internal sealed class ChannelDeleteEvent : ChannelInfo { } | |||
internal sealed class ChannelUpdateEvent : ChannelInfo { } | |||
} |
@@ -1,307 +0,0 @@ | |||
//Ignore unused/unassigned variable warnings | |||
#pragma warning disable CS0649 | |||
#pragma warning disable CS0169 | |||
using Newtonsoft.Json; | |||
using System; | |||
namespace Discord.API | |||
{ | |||
//User | |||
public class UserReference | |||
{ | |||
[JsonProperty("username")] | |||
public string Username; | |||
[JsonProperty("id")] | |||
public string Id; | |||
[JsonProperty("discriminator")] | |||
public string Discriminator; | |||
[JsonProperty("avatar")] | |||
public string Avatar; | |||
} | |||
public class SelfUserInfo : UserReference | |||
{ | |||
[JsonProperty("email")] | |||
public string Email; | |||
[JsonProperty("verified")] | |||
public bool IsVerified; | |||
} | |||
//Members | |||
public class MemberReference | |||
{ | |||
[JsonProperty("user_id")] | |||
public string UserId; | |||
[JsonProperty("user")] | |||
public UserReference User; | |||
[JsonProperty("guild_id")] | |||
public string GuildId; | |||
} | |||
public class MemberInfo : MemberReference | |||
{ | |||
[JsonProperty("joined_at")] | |||
public DateTime? JoinedAt; | |||
[JsonProperty("roles")] | |||
public string[] Roles; | |||
} | |||
public class ExtendedMemberInfo : MemberInfo | |||
{ | |||
[JsonProperty("mute")] | |||
public bool? IsMuted; | |||
[JsonProperty("deaf")] | |||
public bool? IsDeafened; | |||
} | |||
public class PresenceMemberInfo : MemberReference | |||
{ | |||
[JsonProperty("game_id")] | |||
public string GameId; | |||
[JsonProperty("status")] | |||
public string Status; | |||
} | |||
public class VoiceMemberInfo : MemberReference | |||
{ | |||
[JsonProperty("channel_id")] | |||
public string ChannelId; | |||
[JsonProperty("suppress")] | |||
public bool? IsSuppressed; | |||
[JsonProperty("session_id")] | |||
public string SessionId; | |||
[JsonProperty("self_mute")] | |||
public bool? IsSelfMuted; | |||
[JsonProperty("self_deaf")] | |||
public bool? IsSelfDeafened; | |||
[JsonProperty("mute")] | |||
public bool? IsMuted; | |||
[JsonProperty("deaf")] | |||
public bool? IsDeafened; | |||
[JsonProperty("token")] | |||
public string Token; | |||
} | |||
//Channels | |||
public class ChannelReference | |||
{ | |||
[JsonProperty("id")] | |||
public string Id; | |||
[JsonProperty("guild_id")] | |||
public string GuildId; | |||
[JsonProperty("name")] | |||
public string Name; | |||
[JsonProperty("type")] | |||
public string Type; | |||
} | |||
public class ChannelInfo : ChannelReference | |||
{ | |||
public sealed class PermissionOverwrite | |||
{ | |||
[JsonProperty("type")] | |||
public string Type; | |||
[JsonProperty("id")] | |||
public string Id; | |||
[JsonProperty("deny")] | |||
public uint Deny; | |||
[JsonProperty("allow")] | |||
public uint Allow; | |||
} | |||
[JsonProperty("last_message_id")] | |||
public string LastMessageId; | |||
[JsonProperty("is_private")] | |||
public bool IsPrivate; | |||
[JsonProperty("position")] | |||
public int? Position; | |||
[JsonProperty(PropertyName = "topic")] | |||
public string Topic; | |||
[JsonProperty("permission_overwrites")] | |||
public PermissionOverwrite[] PermissionOverwrites; | |||
[JsonProperty("recipient")] | |||
public UserReference Recipient; | |||
} | |||
//Guilds (Servers) | |||
public class GuildReference | |||
{ | |||
[JsonProperty("id")] | |||
public string Id; | |||
[JsonProperty("name")] | |||
public string Name; | |||
} | |||
public class GuildInfo : GuildReference | |||
{ | |||
[JsonProperty("afk_channel_id")] | |||
public string AFKChannelId; | |||
[JsonProperty("afk_timeout")] | |||
public int AFKTimeout; | |||
[JsonProperty("embed_channel_id")] | |||
public string EmbedChannelId; | |||
[JsonProperty("embed_enabled")] | |||
public bool EmbedEnabled; | |||
[JsonProperty("icon")] | |||
public string Icon; | |||
[JsonProperty("joined_at")] | |||
public DateTime? JoinedAt; | |||
[JsonProperty("owner_id")] | |||
public string OwnerId; | |||
[JsonProperty("region")] | |||
public string Region; | |||
[JsonProperty("roles")] | |||
public RoleInfo[] Roles; | |||
} | |||
public class ExtendedGuildInfo : GuildInfo | |||
{ | |||
[JsonProperty("channels")] | |||
public ChannelInfo[] Channels; | |||
[JsonProperty("members")] | |||
public ExtendedMemberInfo[] Members; | |||
[JsonProperty("presences")] | |||
public PresenceMemberInfo[] Presences; | |||
[JsonProperty("voice_states")] | |||
public VoiceMemberInfo[] VoiceStates; | |||
[JsonProperty("unavailable")] | |||
public bool Unavailable; | |||
} | |||
//Messages | |||
public class MessageReference | |||
{ | |||
[JsonProperty("id")] | |||
public string Id; | |||
[JsonProperty("channel_id")] | |||
public string ChannelId; | |||
[JsonProperty("message_id")] | |||
public string MessageId { get { return Id; } set { Id = value; } } | |||
} | |||
public class Message : MessageReference | |||
{ | |||
public sealed class Attachment | |||
{ | |||
[JsonProperty("id")] | |||
public string Id; | |||
[JsonProperty("url")] | |||
public string Url; | |||
[JsonProperty("proxy_url")] | |||
public string ProxyUrl; | |||
[JsonProperty("size")] | |||
public int Size; | |||
[JsonProperty("filename")] | |||
public string Filename; | |||
[JsonProperty("width")] | |||
public int Width; | |||
[JsonProperty("height")] | |||
public int Height; | |||
} | |||
public sealed class Embed | |||
{ | |||
public sealed class Reference | |||
{ | |||
[JsonProperty("url")] | |||
public string Url; | |||
[JsonProperty("name")] | |||
public string Name; | |||
} | |||
public sealed class ThumbnailInfo | |||
{ | |||
[JsonProperty("url")] | |||
public string Url; | |||
[JsonProperty("proxy_url")] | |||
public string ProxyUrl; | |||
[JsonProperty("width")] | |||
public int Width; | |||
[JsonProperty("height")] | |||
public int Height; | |||
} | |||
[JsonProperty("url")] | |||
public string Url; | |||
[JsonProperty("type")] | |||
public string Type; | |||
[JsonProperty("title")] | |||
public string Title; | |||
[JsonProperty("description")] | |||
public string Description; | |||
[JsonProperty("author")] | |||
public Reference Author; | |||
[JsonProperty("provider")] | |||
public Reference Provider; | |||
[JsonProperty("thumbnail")] | |||
public ThumbnailInfo Thumbnail; | |||
} | |||
[JsonProperty("tts")] | |||
public bool? IsTextToSpeech; | |||
[JsonProperty("mention_everyone")] | |||
public bool? IsMentioningEveryone; | |||
[JsonProperty("timestamp")] | |||
public DateTime? Timestamp; | |||
[JsonProperty("edited_timestamp")] | |||
public DateTime? EditedTimestamp; | |||
[JsonProperty("mentions")] | |||
public UserReference[] Mentions; | |||
[JsonProperty("embeds")] | |||
public Embed[] Embeds; //TODO: Parse this | |||
[JsonProperty("attachments")] | |||
public Attachment[] Attachments; | |||
[JsonProperty("content")] | |||
public string Content; | |||
[JsonProperty("author")] | |||
public UserReference Author; | |||
[JsonProperty("nonce")] | |||
public string Nonce; | |||
} | |||
//Roles | |||
public class RoleReference | |||
{ | |||
[JsonProperty("guild_id")] | |||
public string GuildId; | |||
[JsonProperty("role_id")] | |||
public string RoleId; | |||
} | |||
public class RoleInfo | |||
{ | |||
[JsonProperty("permissions")] | |||
public uint? Permissions; | |||
[JsonProperty("name")] | |||
public string Name; | |||
[JsonProperty("position")] | |||
public int? Position; | |||
[JsonProperty("hoist")] | |||
public bool? Hoist; | |||
[JsonProperty("color")] | |||
public uint? Color; | |||
[JsonProperty("id")] | |||
public string Id; | |||
[JsonProperty("managed")] | |||
public bool? Managed; | |||
} | |||
//Invites | |||
public class Invite | |||
{ | |||
[JsonProperty("inviter")] | |||
public UserReference Inviter; | |||
[JsonProperty("guild")] | |||
public GuildReference Guild; | |||
[JsonProperty("channel")] | |||
public ChannelReference Channel; | |||
[JsonProperty("code")] | |||
public string Code; | |||
[JsonProperty("xkcdpass")] | |||
public string XkcdPass; | |||
} | |||
public class ExtendedInvite : Invite | |||
{ | |||
[JsonProperty("max_age")] | |||
public int ?MaxAge; | |||
[JsonProperty("max_uses")] | |||
public int? MaxUses; | |||
[JsonProperty("revoked")] | |||
public bool? IsRevoked; | |||
[JsonProperty("temporary")] | |||
public bool? IsTemporary; | |||
[JsonProperty("uses")] | |||
public int? Uses; | |||
[JsonProperty("created_at")] | |||
public DateTime? CreatedAt; | |||
} | |||
} |
@@ -1,15 +1,13 @@ | |||
namespace Discord.API | |||
{ | |||
internal static class Endpoints | |||
public static class Endpoints | |||
{ | |||
public const string BaseStatusApi = "https://status.discordapp.com/api/v2/"; | |||
public const string BaseApi = "https://discordapp.com/api/"; | |||
//public const string Track = "track"; | |||
public const string Gateway = "gateway"; | |||
public const string Auth = "auth"; | |||
public const string AuthFingerprint = "auth/fingerprint"; | |||
public const string AuthRegister = "auth/register"; | |||
public const string AuthLogin = "auth/login"; | |||
public const string AuthLogout = "auth/logout"; | |||
@@ -42,7 +40,7 @@ | |||
public const string Voice = "voice"; | |||
public const string VoiceRegions = "voice/regions"; | |||
public const string VoiceIce = "voice/ice"; | |||
//public const string VoiceIce = "voice/ice"; | |||
public const string StatusActiveMaintenance = "scheduled-maintenances/active.json"; | |||
public const string StatusUpcomingMaintenance = "scheduled-maintenances/upcoming.json"; | |||
@@ -0,0 +1,9 @@ | |||
namespace Discord | |||
{ | |||
public enum ImageType | |||
{ | |||
None, | |||
Jpeg, | |||
Png | |||
} | |||
} |
@@ -0,0 +1,28 @@ | |||
namespace Discord | |||
{ | |||
public class ChannelType : StringEnum | |||
{ | |||
/// <summary> A text-only channel. </summary> | |||
public static readonly ChannelType Text = new ChannelType("text"); | |||
/// <summary> A voice-only channel. </summary> | |||
public static readonly ChannelType Voice = new ChannelType("voice"); | |||
private ChannelType(string value) | |||
: base(value) { } | |||
public static ChannelType FromString(string value) | |||
{ | |||
switch (value) | |||
{ | |||
case null: | |||
return null; | |||
case "text": | |||
return ChannelType.Text; | |||
case "voice": | |||
return ChannelType.Voice; | |||
default: | |||
return new ChannelType(value); | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,28 @@ | |||
namespace Discord | |||
{ | |||
public class PermissionTarget : StringEnum | |||
{ | |||
/// <summary> A text-only channel. </summary> | |||
public static readonly PermissionTarget Role = new PermissionTarget("role"); | |||
/// <summary> A voice-only channel. </summary> | |||
public static readonly PermissionTarget User = new PermissionTarget("member"); | |||
private PermissionTarget(string value) | |||
: base(value) { } | |||
public static PermissionTarget FromString(string value) | |||
{ | |||
switch (value) | |||
{ | |||
case null: | |||
return null; | |||
case "role": | |||
return PermissionTarget.Role; | |||
case "member": | |||
return PermissionTarget.User; | |||
default: | |||
return new PermissionTarget(value); | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,38 @@ | |||
namespace Discord | |||
{ | |||
public class Region : StringEnum | |||
{ | |||
public static readonly Region USWest = new Region("us-west"); | |||
public static readonly Region USEast = new Region("us-east"); | |||
public static readonly Region Singapore = new Region("singapore"); | |||
public static readonly Region London = new Region("london"); | |||
public static readonly Region Sydney = new Region("sydney"); | |||
public static readonly Region Amsterdam = new Region("amsterdam"); | |||
private Region(string value) | |||
: base(value) { } | |||
public static Region FromString(string value) | |||
{ | |||
switch (value) | |||
{ | |||
case null: | |||
return null; | |||
case "us-west": | |||
return Region.USWest; | |||
case "us-east": | |||
return Region.USEast; | |||
case "singapore": | |||
return Region.Singapore; | |||
case "london": | |||
return Region.London; | |||
case "sydney": | |||
return Region.Sydney; | |||
case "amsterdam": | |||
return Region.Amsterdam; | |||
default: | |||
return new Region(value); | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,52 @@ | |||
namespace Discord | |||
{ | |||
public abstract class StringEnum | |||
{ | |||
private string _value; | |||
protected StringEnum(string value) | |||
{ | |||
_value = value; | |||
} | |||
public string Value => _value; | |||
public override string ToString() => _value; | |||
public override bool Equals(object obj) | |||
{ | |||
var enum2 = obj as StringEnum; | |||
if (enum2 == (StringEnum)null) | |||
return false; | |||
else | |||
return _value == enum2._value; | |||
} | |||
public override int GetHashCode() | |||
{ | |||
return _value.GetHashCode(); | |||
} | |||
public static bool operator ==(StringEnum a, StringEnum b) | |||
{ | |||
return a?._value == b?._value; | |||
} | |||
public static bool operator !=(StringEnum a, StringEnum b) | |||
{ | |||
return a?._value != b?._value; | |||
} | |||
public static bool operator ==(StringEnum a, string b) | |||
{ | |||
return a?._value == b; | |||
} | |||
public static bool operator !=(StringEnum a, string b) | |||
{ | |||
return a?._value != b; | |||
} | |||
public static bool operator ==(string a, StringEnum b) | |||
{ | |||
return a == b?._value; | |||
} | |||
public static bool operator !=(string a, StringEnum b) | |||
{ | |||
return a != b?._value; | |||
} | |||
} | |||
} |
@@ -0,0 +1,32 @@ | |||
namespace Discord | |||
{ | |||
public class UserStatus : StringEnum | |||
{ | |||
/// <summary> User is currently online and active. </summary> | |||
public static readonly UserStatus Online = new UserStatus("online"); | |||
/// <summary> User is currently online but inactive. </summary> | |||
public static readonly UserStatus Idle = new UserStatus("idle"); | |||
/// <summary> User is offline. </summary> | |||
public static readonly UserStatus Offline = new UserStatus("offline"); | |||
private UserStatus(string value) | |||
: base(value) { } | |||
public static UserStatus FromString(string value) | |||
{ | |||
switch (value) | |||
{ | |||
case null: | |||
return null; | |||
case "online": | |||
return UserStatus.Online; | |||
case "idle": | |||
return UserStatus.Idle; | |||
case "offline": | |||
return UserStatus.Offline; | |||
default: | |||
return new UserStatus(value); | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,59 @@ | |||
//Ignore unused/unassigned variable warnings | |||
#pragma warning disable CS0649 | |||
#pragma warning disable CS0169 | |||
using Newtonsoft.Json; | |||
using System; | |||
namespace Discord.API | |||
{ | |||
//Common | |||
public class InviteReference | |||
{ | |||
[JsonProperty("inviter")] | |||
public UserReference Inviter; | |||
[JsonProperty("guild")] | |||
public GuildReference Guild; | |||
[JsonProperty("channel")] | |||
public ChannelReference Channel; | |||
[JsonProperty("code")] | |||
public string Code; | |||
[JsonProperty("xkcdpass")] | |||
public string XkcdPass; | |||
} | |||
public class InviteInfo : InviteReference | |||
{ | |||
[JsonProperty("max_age")] | |||
public int? MaxAge; | |||
[JsonProperty("max_uses")] | |||
public int? MaxUses; | |||
[JsonProperty("revoked")] | |||
public bool? IsRevoked; | |||
[JsonProperty("temporary")] | |||
public bool? IsTemporary; | |||
[JsonProperty("uses")] | |||
public int? Uses; | |||
[JsonProperty("created_at")] | |||
public DateTime? CreatedAt; | |||
} | |||
//Create | |||
public class CreateInviteRequest | |||
{ | |||
[JsonProperty("max_age")] | |||
public int MaxAge; | |||
[JsonProperty("max_uses")] | |||
public int MaxUses; | |||
[JsonProperty("temporary")] | |||
public bool IsTemporary; | |||
[JsonProperty("xkcdpass")] | |||
public bool WithXkcdPass; | |||
} | |||
public class CreateInviteResponse : InviteInfo { } | |||
//Get | |||
public class GetInviteResponse : InviteReference { } | |||
//Accept | |||
public class AcceptInviteResponse : InviteReference { } | |||
} |
@@ -0,0 +1,33 @@ | |||
//Ignore unused/unassigned variable warnings | |||
#pragma warning disable CS0649 | |||
#pragma warning disable CS0169 | |||
using Newtonsoft.Json; | |||
using System; | |||
namespace Discord.API | |||
{ | |||
public class GetIncidentsResponse | |||
{ | |||
[JsonProperty("page")] | |||
public PageData Page; | |||
[JsonProperty("scheduled_maintenances")] | |||
public MaintenanceData[] ScheduledMaintenances; | |||
public sealed class PageData | |||
{ | |||
[JsonProperty("id")] | |||
public string Id; | |||
[JsonProperty("name")] | |||
public string Name; | |||
[JsonProperty("url")] | |||
public string Url; | |||
[JsonProperty("updated-at")] | |||
public DateTime? UpdatedAt; | |||
} | |||
public sealed class MaintenanceData | |||
{ | |||
} | |||
} | |||
} |
@@ -0,0 +1,90 @@ | |||
//Ignore unused/unassigned variable warnings | |||
#pragma warning disable CS0649 | |||
#pragma warning disable CS0169 | |||
using Newtonsoft.Json; | |||
using System; | |||
using System.Collections.Generic; | |||
namespace Discord.API | |||
{ | |||
//Common | |||
public class MemberReference | |||
{ | |||
[JsonProperty("user_id")] | |||
public string UserId; | |||
[JsonProperty("guild_id")] | |||
public string GuildId; | |||
[JsonProperty("user")] | |||
private UserReference _user; | |||
public UserReference User | |||
{ | |||
get { return _user; } | |||
set | |||
{ | |||
_user = value; | |||
UserId = User.Id; | |||
} | |||
} | |||
} | |||
public class MemberInfo : MemberReference | |||
{ | |||
[JsonProperty("joined_at")] | |||
public DateTime? JoinedAt; | |||
[JsonProperty("roles")] | |||
public string[] Roles; | |||
} | |||
public class ExtendedMemberInfo : MemberInfo | |||
{ | |||
[JsonProperty("mute")] | |||
public bool? IsServerMuted; | |||
[JsonProperty("deaf")] | |||
public bool? IsServerDeafened; | |||
} | |||
public class PresenceInfo : MemberReference | |||
{ | |||
[JsonProperty("game_id")] | |||
public string GameId; | |||
[JsonProperty("status")] | |||
public string Status; | |||
[JsonProperty("roles")] //TODO: Might be temporary | |||
public string[] Roles; | |||
} | |||
public class VoiceMemberInfo : MemberReference | |||
{ | |||
[JsonProperty("channel_id")] | |||
public string ChannelId; | |||
[JsonProperty("session_id")] | |||
public string SessionId; | |||
[JsonProperty("token")] | |||
public string Token; | |||
[JsonProperty("self_mute")] | |||
public bool? IsSelfMuted; | |||
[JsonProperty("self_deaf")] | |||
public bool? IsSelfDeafened; | |||
[JsonProperty("mute")] | |||
public bool? IsServerMuted; | |||
[JsonProperty("deaf")] | |||
public bool? IsServerDeafened; | |||
[JsonProperty("suppress")] | |||
public bool? IsServerSuppressed; | |||
} | |||
public class EditMemberRequest | |||
{ | |||
[JsonProperty("mute", NullValueHandling = NullValueHandling.Ignore)] | |||
public bool? Mute; | |||
[JsonProperty("deaf", NullValueHandling = NullValueHandling.Ignore)] | |||
public bool? Deaf; | |||
[JsonProperty("roles", NullValueHandling = NullValueHandling.Ignore)] | |||
public IEnumerable<string> Roles; | |||
} | |||
//Events | |||
internal sealed class MemberAddEvent : MemberInfo { } | |||
internal sealed class MemberUpdateEvent : MemberInfo { } | |||
internal sealed class MemberRemoveEvent : MemberInfo { } | |||
internal sealed class MemberVoiceStateUpdateEvent : VoiceMemberInfo { } | |||
} |
@@ -0,0 +1,133 @@ | |||
//Ignore unused/unassigned variable warnings | |||
#pragma warning disable CS0649 | |||
#pragma warning disable CS0169 | |||
using Newtonsoft.Json; | |||
using System; | |||
using System.Collections.Generic; | |||
namespace Discord.API | |||
{ | |||
//Common | |||
public class MessageReference | |||
{ | |||
[JsonProperty("id")] | |||
public string Id; | |||
[JsonProperty("channel_id")] | |||
public string ChannelId; | |||
[JsonProperty("message_id")] | |||
public string MessageId { get { return Id; } set { Id = value; } } | |||
} | |||
public class MessageInfo : MessageReference | |||
{ | |||
public sealed class Attachment | |||
{ | |||
[JsonProperty("id")] | |||
public string Id; | |||
[JsonProperty("url")] | |||
public string Url; | |||
[JsonProperty("proxy_url")] | |||
public string ProxyUrl; | |||
[JsonProperty("size")] | |||
public int Size; | |||
[JsonProperty("filename")] | |||
public string Filename; | |||
[JsonProperty("width")] | |||
public int Width; | |||
[JsonProperty("height")] | |||
public int Height; | |||
} | |||
public sealed class Embed | |||
{ | |||
public sealed class Reference | |||
{ | |||
[JsonProperty("url")] | |||
public string Url; | |||
[JsonProperty("name")] | |||
public string Name; | |||
} | |||
public sealed class ThumbnailInfo | |||
{ | |||
[JsonProperty("url")] | |||
public string Url; | |||
[JsonProperty("proxy_url")] | |||
public string ProxyUrl; | |||
[JsonProperty("width")] | |||
public int Width; | |||
[JsonProperty("height")] | |||
public int Height; | |||
} | |||
[JsonProperty("url")] | |||
public string Url; | |||
[JsonProperty("type")] | |||
public string Type; | |||
[JsonProperty("title")] | |||
public string Title; | |||
[JsonProperty("description")] | |||
public string Description; | |||
[JsonProperty("author")] | |||
public Reference Author; | |||
[JsonProperty("provider")] | |||
public Reference Provider; | |||
[JsonProperty("thumbnail")] | |||
public ThumbnailInfo Thumbnail; | |||
} | |||
[JsonProperty("tts")] | |||
public bool? IsTextToSpeech; | |||
[JsonProperty("mention_everyone")] | |||
public bool? IsMentioningEveryone; | |||
[JsonProperty("timestamp")] | |||
public DateTime? Timestamp; | |||
[JsonProperty("edited_timestamp")] | |||
public DateTime? EditedTimestamp; | |||
[JsonProperty("mentions")] | |||
public UserReference[] Mentions; | |||
[JsonProperty("embeds")] | |||
public Embed[] Embeds; //TODO: Parse this | |||
[JsonProperty("attachments")] | |||
public Attachment[] Attachments; | |||
[JsonProperty("content")] | |||
public string Content; | |||
[JsonProperty("author")] | |||
public UserReference Author; | |||
[JsonProperty("nonce")] | |||
public string Nonce; | |||
} | |||
//Create | |||
internal sealed class SendMessageRequest | |||
{ | |||
[JsonProperty("content")] | |||
public string Content; | |||
[JsonProperty("mentions")] | |||
public IEnumerable<string> Mentions; | |||
[JsonProperty("nonce", NullValueHandling = NullValueHandling.Ignore)] | |||
public string Nonce; | |||
[JsonProperty("tts", NullValueHandling = NullValueHandling.Ignore)] | |||
public bool IsTTS; | |||
} | |||
public sealed class SendMessageResponse : MessageInfo { } | |||
//Edit | |||
internal sealed class EditMessageRequest | |||
{ | |||
[JsonProperty("content", NullValueHandling = NullValueHandling.Ignore)] | |||
public string Content; | |||
[JsonProperty("mentions", NullValueHandling = NullValueHandling.Ignore)] | |||
public IEnumerable<string> Mentions; | |||
} | |||
public sealed class EditMessageResponse : MessageInfo { } | |||
//Get | |||
public sealed class GetMessagesResponse : List<MessageInfo> { } | |||
//Events | |||
internal sealed class MessageCreateEvent : MessageInfo { } | |||
internal sealed class MessageUpdateEvent : MessageInfo { } | |||
internal sealed class MessageDeleteEvent : MessageReference { } | |||
internal sealed class MessageAckEvent : MessageReference { } | |||
} |
@@ -0,0 +1,21 @@ | |||
//Ignore unused/unassigned variable warnings | |||
#pragma warning disable CS0649 | |||
#pragma warning disable CS0169 | |||
using Newtonsoft.Json; | |||
namespace Discord.API | |||
{ | |||
//Create/Edit | |||
internal sealed class SetChannelPermissionsRequest | |||
{ | |||
[JsonProperty("id")] | |||
public string Id; | |||
[JsonProperty("type")] | |||
public string Type; | |||
[JsonProperty("allow")] | |||
public uint Allow; | |||
[JsonProperty("deny")] | |||
public uint Deny; | |||
} | |||
} |
@@ -0,0 +1,33 @@ | |||
//Ignore unused/unassigned variable warnings | |||
#pragma warning disable CS0649 | |||
#pragma warning disable CS0169 | |||
using Newtonsoft.Json; | |||
namespace Discord.API | |||
{ | |||
//Commands | |||
internal sealed class UpdateStatusCommand : WebSocketMessage<UpdateStatusCommand.Data> | |||
{ | |||
public UpdateStatusCommand() : base(3) { } | |||
public class Data | |||
{ | |||
[JsonProperty("idle_since")] | |||
public ulong? IdleSince; | |||
[JsonProperty("game_id")] | |||
public int? GameId; | |||
} | |||
} | |||
//Events | |||
internal sealed class TypingStartEvent | |||
{ | |||
[JsonProperty("user_id")] | |||
public string UserId; | |||
[JsonProperty("channel_id")] | |||
public string ChannelId; | |||
[JsonProperty("timestamp")] | |||
public int Timestamp; | |||
} | |||
internal sealed class PresenceUpdateEvent : PresenceInfo { } | |||
} |
@@ -1,178 +0,0 @@ | |||
//Ignore unused/unassigned variable warnings | |||
#pragma warning disable CS0649 | |||
#pragma warning disable CS0169 | |||
using Newtonsoft.Json; | |||
using System.Collections.Generic; | |||
using System.Collections; | |||
namespace Discord.API | |||
{ | |||
//Auth | |||
internal sealed class RegisterRequest | |||
{ | |||
[JsonProperty("fingerprint")] | |||
public string Fingerprint; | |||
[JsonProperty("username")] | |||
public string Username; | |||
} | |||
internal sealed class LoginRequest | |||
{ | |||
[JsonProperty("email")] | |||
public string Email; | |||
[JsonProperty("password")] | |||
public string Password; | |||
} | |||
//Channels | |||
internal sealed class CreateChannelRequest | |||
{ | |||
[JsonProperty("name")] | |||
public string Name; | |||
[JsonProperty("type")] | |||
public string Type; | |||
} | |||
internal sealed class CreatePMChannelRequest | |||
{ | |||
[JsonProperty("recipient_id")] | |||
public string RecipientId; | |||
} | |||
internal sealed class EditChannelRequest | |||
{ | |||
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] | |||
public string Name; | |||
[JsonProperty("topic", NullValueHandling = NullValueHandling.Ignore)] | |||
public string Topic; | |||
} | |||
internal sealed class ReorderChannelsRequest : IEnumerable<ReorderChannelsRequest.Channel> | |||
{ | |||
public sealed class Channel | |||
{ | |||
[JsonProperty("id")] | |||
public string Id; | |||
[JsonProperty("position")] | |||
public uint Position; | |||
} | |||
private IEnumerable<Channel> _channels; | |||
public ReorderChannelsRequest(IEnumerable<Channel> channels) { _channels = channels; } | |||
public IEnumerator<Channel> GetEnumerator() =>_channels.GetEnumerator(); | |||
IEnumerator IEnumerable.GetEnumerator() => _channels.GetEnumerator(); | |||
} | |||
//Invites | |||
internal sealed class CreateInviteRequest | |||
{ | |||
[JsonProperty("max_age")] | |||
public int MaxAge; | |||
[JsonProperty("max_uses")] | |||
public int MaxUses; | |||
[JsonProperty("temporary")] | |||
public bool IsTemporary; | |||
[JsonProperty("xkcdpass")] | |||
public bool WithXkcdPass; | |||
} | |||
//Members | |||
internal sealed class EditMemberRequest | |||
{ | |||
[JsonProperty(PropertyName = "mute", NullValueHandling = NullValueHandling.Ignore)] | |||
public bool? Mute; | |||
[JsonProperty(PropertyName = "deaf", NullValueHandling = NullValueHandling.Ignore)] | |||
public bool? Deaf; | |||
[JsonProperty(PropertyName = "roles", NullValueHandling = NullValueHandling.Ignore)] | |||
public IEnumerable<string> Roles; | |||
} | |||
//Messages | |||
internal sealed class SendMessageRequest | |||
{ | |||
[JsonProperty("content")] | |||
public string Content; | |||
[JsonProperty("mentions")] | |||
public IEnumerable<string> Mentions; | |||
[JsonProperty("nonce", NullValueHandling = NullValueHandling.Ignore)] | |||
public string Nonce; | |||
[JsonProperty("tts", NullValueHandling = NullValueHandling.Ignore)] | |||
public bool IsTTS; | |||
} | |||
internal sealed class EditMessageRequest | |||
{ | |||
[JsonProperty("content", NullValueHandling = NullValueHandling.Ignore)] | |||
public string Content; | |||
[JsonProperty("mentions", NullValueHandling = NullValueHandling.Ignore)] | |||
public IEnumerable<string> Mentions; | |||
} | |||
//Permissions | |||
internal sealed class SetChannelPermissionsRequest //Both creates and modifies | |||
{ | |||
[JsonProperty("id")] | |||
public string Id; | |||
[JsonProperty("type")] | |||
public string Type; | |||
[JsonProperty("allow")] | |||
public uint Allow; | |||
[JsonProperty("deny")] | |||
public uint Deny; | |||
} | |||
//Profile | |||
internal sealed class EditProfileRequest | |||
{ | |||
[JsonProperty(PropertyName = "password")] | |||
public string CurrentPassword; | |||
[JsonProperty(PropertyName = "email", NullValueHandling = NullValueHandling.Ignore)] | |||
public string Email; | |||
[JsonProperty(PropertyName = "new_password")] | |||
public string Password; | |||
[JsonProperty(PropertyName = "username", NullValueHandling = NullValueHandling.Ignore)] | |||
public string Username; | |||
[JsonProperty(PropertyName = "avatar", NullValueHandling = NullValueHandling.Ignore)] | |||
public string Avatar; | |||
} | |||
//Roles | |||
internal sealed class EditRoleRequest | |||
{ | |||
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] | |||
public string Name; | |||
[JsonProperty("permissions", NullValueHandling = NullValueHandling.Ignore)] | |||
public uint? Permissions; | |||
[JsonProperty("hoist", NullValueHandling = NullValueHandling.Ignore)] | |||
public bool? Hoist; | |||
[JsonProperty("color", NullValueHandling = NullValueHandling.Ignore)] | |||
public uint? Color; | |||
} | |||
internal sealed class ReorderRolesRequest : IEnumerable<ReorderRolesRequest.Role> | |||
{ | |||
public sealed class Role | |||
{ | |||
[JsonProperty("id")] | |||
public string Id; | |||
[JsonProperty("position")] | |||
public uint Position; | |||
} | |||
private IEnumerable<Role> _roles; | |||
public ReorderRolesRequest(IEnumerable<Role> roles) { _roles = roles; } | |||
public IEnumerator<Role> GetEnumerator() => _roles.GetEnumerator(); | |||
IEnumerator IEnumerable.GetEnumerator() => _roles.GetEnumerator(); | |||
} | |||
//Servers | |||
internal sealed class CreateServerRequest | |||
{ | |||
[JsonProperty("name")] | |||
public string Name; | |||
[JsonProperty("region")] | |||
public string Region; | |||
} | |||
internal sealed class EditServerRequest | |||
{ | |||
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] | |||
public string Name; | |||
[JsonProperty("region", NullValueHandling = NullValueHandling.Ignore)] | |||
public string Region; | |||
} | |||
} |
@@ -1,116 +0,0 @@ | |||
//Ignore unused/unassigned variable warnings | |||
#pragma warning disable CS0649 | |||
#pragma warning disable CS0169 | |||
using Newtonsoft.Json; | |||
using System; | |||
using System.Collections.Generic; | |||
namespace Discord.API | |||
{ | |||
//Auth | |||
public sealed class GatewayResponse | |||
{ | |||
[JsonProperty("url")] | |||
public string Url; | |||
} | |||
public sealed class FingerprintResponse | |||
{ | |||
[JsonProperty("fingerprint")] | |||
public string Fingerprint; | |||
} | |||
public sealed class RegisterResponse | |||
{ | |||
[JsonProperty("token")] | |||
public string Token; | |||
} | |||
public sealed class LoginResponse | |||
{ | |||
[JsonProperty("token")] | |||
public string Token; | |||
} | |||
//Channels | |||
public sealed class CreateChannelResponse : ChannelInfo { } | |||
public sealed class DestroyChannelResponse : ChannelInfo { } | |||
public sealed class EditChannelResponse : ChannelInfo { } | |||
//Invites | |||
public sealed class CreateInviteResponse : ExtendedInvite { } | |||
public sealed class GetInviteResponse : Invite { } | |||
public sealed class AcceptInviteResponse : Invite { } | |||
//Messages | |||
public sealed class SendMessageResponse : Message { } | |||
public sealed class EditMessageResponse : Message { } | |||
public sealed class GetMessagesResponse : List<Message> { } | |||
//Profile | |||
public sealed class EditProfileResponse : SelfUserInfo { } | |||
//Roles | |||
public sealed class CreateRoleResponse : RoleInfo { } | |||
public sealed class EditRoleResponse : RoleInfo { } | |||
//Servers | |||
public sealed class CreateServerResponse : GuildInfo { } | |||
public sealed class DeleteServerResponse : GuildInfo { } | |||
public sealed class EditServerResponse : GuildInfo { } | |||
//Voice | |||
public sealed class GetRegionsResponse : List<GetRegionsResponse.RegionData> | |||
{ | |||
public sealed class RegionData | |||
{ | |||
[JsonProperty("sample_hostname")] | |||
public string Hostname; | |||
[JsonProperty("sample_port")] | |||
public int Port; | |||
[JsonProperty("id")] | |||
public string Id; | |||
[JsonProperty("name")] | |||
public string Name; | |||
} | |||
} | |||
public sealed class GetIceResponse | |||
{ | |||
[JsonProperty("ttl")] | |||
public string TTL; | |||
[JsonProperty("servers")] | |||
public ServerData[] Servers; | |||
public sealed class ServerData | |||
{ | |||
[JsonProperty("url")] | |||
public string URL; | |||
[JsonProperty("username")] | |||
public string Username; | |||
[JsonProperty("credential")] | |||
public string Credential; | |||
} | |||
} | |||
public sealed class GetIncidentsResponse | |||
{ | |||
[JsonProperty("page")] | |||
public PageData Page; | |||
[JsonProperty("scheduled_maintenances")] | |||
public MaintenanceData[] ScheduledMaintenances; | |||
public sealed class PageData | |||
{ | |||
[JsonProperty("id")] | |||
public string Id; | |||
[JsonProperty("name")] | |||
public string Name; | |||
[JsonProperty("url")] | |||
public string Url; | |||
[JsonProperty("updated-at")] | |||
public DateTime? UpdatedAt; | |||
} | |||
public sealed class MaintenanceData | |||
{ | |||
} | |||
} | |||
} |
@@ -1,67 +0,0 @@ | |||
/* | |||
using Discord.API; | |||
using System; | |||
using System.Globalization; | |||
using System.IO; | |||
using System.Net; | |||
using System.Net.Http; | |||
using System.Text; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
namespace Discord.API | |||
{ | |||
internal class BuiltInRestEngine : IRestEngine | |||
{ | |||
private readonly HttpClient _client; | |||
public BuiltInRestEngine(string userAgent, int timeout) | |||
{ | |||
_client = new HttpClient(new HttpClientHandler | |||
{ | |||
AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip, | |||
UseCookies = false, | |||
PreAuthenticate = false //We do auth ourselves | |||
}); | |||
_client.DefaultRequestHeaders.Add("accept", "*\/*"); | |||
_client.DefaultRequestHeaders.Add("accept-encoding", "gzip,deflate"); | |||
_client.DefaultRequestHeaders.Add("user-agent", userAgent); | |||
_client.Timeout = TimeSpan.FromMilliseconds(timeout); | |||
} | |||
public void SetToken(string token) | |||
{ | |||
_client.DefaultRequestHeaders.Remove("authorization"); | |||
if (token != null) | |||
_client.DefaultRequestHeaders.Add("authorization", token); | |||
} | |||
public async Task<string> Send(HttpMethod method, string path, string json, CancellationToken cancelToken) | |||
{ | |||
using (var request = new HttpRequestMessage(method, Endpoints.BaseApi + path)) | |||
{ | |||
if (json != null) | |||
request.Content = new StringContent(json, Encoding.UTF8, "application/json"); | |||
return await Send(request, cancelToken); | |||
} | |||
} | |||
public async Task<string> SendFile(HttpMethod method, string path, string filePath, CancellationToken cancelToken) | |||
{ | |||
using (var request = new HttpRequestMessage(method, Endpoints.BaseApi + path)) | |||
{ | |||
var content = new MultipartFormDataContent("Upload----" + DateTime.Now.ToString(CultureInfo.InvariantCulture)); | |||
content.Add(new StreamContent(File.OpenRead(filePath)), "file", Path.GetFileName(filePath)); | |||
request.Content = content; | |||
return await Send(request, cancelToken); | |||
} | |||
} | |||
private async Task<string> Send(HttpRequestMessage request, CancellationToken cancelToken) | |||
{ | |||
var response = await _client.SendAsync(request, cancelToken).ConfigureAwait(false); | |||
if (!response.IsSuccessStatusCode) | |||
throw new HttpException(response.StatusCode); | |||
return await response.Content.ReadAsStringAsync().ConfigureAwait(false); | |||
} | |||
} | |||
} | |||
*/ |
@@ -0,0 +1,87 @@ | |||
//Ignore unused/unassigned variable warnings | |||
#pragma warning disable CS0649 | |||
#pragma warning disable CS0169 | |||
using Newtonsoft.Json; | |||
using System.Collections; | |||
using System.Collections.Generic; | |||
namespace Discord.API | |||
{ | |||
//Common | |||
public class RoleReference | |||
{ | |||
[JsonProperty("guild_id")] | |||
public string GuildId; | |||
[JsonProperty("role_id")] | |||
public string RoleId; | |||
} | |||
public class RoleInfo | |||
{ | |||
[JsonProperty("permissions")] | |||
public uint? Permissions; | |||
[JsonProperty("name")] | |||
public string Name; | |||
[JsonProperty("position")] | |||
public int? Position; | |||
[JsonProperty("hoist")] | |||
public bool? Hoist; | |||
[JsonProperty("color")] | |||
public uint? Color; | |||
[JsonProperty("id")] | |||
public string Id; | |||
[JsonProperty("managed")] | |||
public bool? Managed; | |||
} | |||
//Create | |||
public sealed class CreateRoleResponse : RoleInfo { } | |||
//Edit | |||
public sealed class EditRoleRequest | |||
{ | |||
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] | |||
public string Name; | |||
[JsonProperty("permissions", NullValueHandling = NullValueHandling.Ignore)] | |||
public uint? Permissions; | |||
[JsonProperty("hoist", NullValueHandling = NullValueHandling.Ignore)] | |||
public bool? Hoist; | |||
[JsonProperty("color", NullValueHandling = NullValueHandling.Ignore)] | |||
public uint? Color; | |||
} | |||
public sealed class EditRoleResponse : RoleInfo { } | |||
//Reorder | |||
public sealed class ReorderRolesRequest : IEnumerable<ReorderRolesRequest.Role> | |||
{ | |||
public sealed class Role | |||
{ | |||
[JsonProperty("id")] | |||
public string Id; | |||
[JsonProperty("position")] | |||
public uint Position; | |||
} | |||
private IEnumerable<Role> _roles; | |||
public ReorderRolesRequest(IEnumerable<Role> roles) { _roles = roles; } | |||
public IEnumerator<Role> GetEnumerator() => _roles.GetEnumerator(); | |||
IEnumerator IEnumerable.GetEnumerator() => _roles.GetEnumerator(); | |||
} | |||
//Events | |||
internal sealed class RoleCreateEvent | |||
{ | |||
[JsonProperty("guild_id")] | |||
public string GuildId; | |||
[JsonProperty("role")] | |||
public RoleInfo Data; | |||
} | |||
internal sealed class RoleUpdateEvent | |||
{ | |||
[JsonProperty("guild_id")] | |||
public string GuildId; | |||
[JsonProperty("role")] | |||
public RoleInfo Data; | |||
} | |||
internal sealed class RoleDeleteEvent : RoleReference { } | |||
} |
@@ -0,0 +1,86 @@ | |||
//Ignore unused/unassigned variable warnings | |||
#pragma warning disable CS0649 | |||
#pragma warning disable CS0169 | |||
using Newtonsoft.Json; | |||
using System; | |||
namespace Discord.API | |||
{ | |||
//Common | |||
public class GuildReference | |||
{ | |||
[JsonProperty("id")] | |||
public string Id; | |||
[JsonProperty("name")] | |||
public string Name; | |||
} | |||
public class GuildInfo : GuildReference | |||
{ | |||
[JsonProperty("afk_channel_id")] | |||
public string AFKChannelId; | |||
[JsonProperty("afk_timeout")] | |||
public int? AFKTimeout; | |||
[JsonProperty("embed_channel_id")] | |||
public string EmbedChannelId; | |||
[JsonProperty("embed_enabled")] | |||
public bool EmbedEnabled; | |||
[JsonProperty("icon")] | |||
public string Icon; | |||
[JsonProperty("joined_at")] | |||
public DateTime? JoinedAt; | |||
[JsonProperty("owner_id")] | |||
public string OwnerId; | |||
[JsonProperty("region")] | |||
public string Region; | |||
[JsonProperty("roles")] | |||
public RoleInfo[] Roles; | |||
} | |||
public class ExtendedGuildInfo : GuildInfo | |||
{ | |||
[JsonProperty("channels")] | |||
public ChannelInfo[] Channels; | |||
[JsonProperty("members")] | |||
public ExtendedMemberInfo[] Members; | |||
[JsonProperty("presences")] | |||
public PresenceInfo[] Presences; | |||
[JsonProperty("voice_states")] | |||
public VoiceMemberInfo[] VoiceStates; | |||
[JsonProperty("unavailable")] | |||
public bool Unavailable; | |||
} | |||
//Create | |||
internal sealed class CreateServerRequest | |||
{ | |||
[JsonProperty("name")] | |||
public string Name; | |||
[JsonProperty("region")] | |||
public string Region; | |||
} | |||
public sealed class CreateServerResponse : GuildInfo { } | |||
//Edit | |||
internal sealed class EditServerRequest | |||
{ | |||
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] | |||
public string Name; | |||
[JsonProperty("region", NullValueHandling = NullValueHandling.Ignore)] | |||
public string Region; | |||
[JsonProperty("icon", NullValueHandling = NullValueHandling.Ignore)] | |||
public string Icon; | |||
[JsonProperty("afk_channel_id", NullValueHandling = NullValueHandling.Ignore)] | |||
public string AFKChannelId; | |||
[JsonProperty("afk_timeout", NullValueHandling = NullValueHandling.Ignore)] | |||
public int AFKTimeout; | |||
} | |||
public sealed class EditServerResponse : GuildInfo { } | |||
//Delete | |||
public sealed class DeleteServerResponse : GuildInfo { } | |||
//Events | |||
internal sealed class GuildCreateEvent : ExtendedGuildInfo { } | |||
internal sealed class GuildUpdateEvent : GuildInfo { } | |||
internal sealed class GuildDeleteEvent : ExtendedGuildInfo { } | |||
} |
@@ -0,0 +1,47 @@ | |||
//Ignore unused/unassigned variable warnings | |||
#pragma warning disable CS0649 | |||
#pragma warning disable CS0169 | |||
using Newtonsoft.Json; | |||
namespace Discord.API | |||
{ | |||
//Common | |||
public class UserReference | |||
{ | |||
[JsonProperty("username")] | |||
public string Username; | |||
[JsonProperty("id")] | |||
public string Id; | |||
[JsonProperty("discriminator")] | |||
public string Discriminator; | |||
[JsonProperty("avatar")] | |||
public string Avatar; | |||
} | |||
public class UserInfo : UserReference | |||
{ | |||
[JsonProperty("email")] | |||
public string Email; | |||
[JsonProperty("verified")] | |||
public bool? IsVerified; | |||
} | |||
//Edit | |||
internal sealed class EditUserRequest | |||
{ | |||
[JsonProperty("password")] | |||
public string CurrentPassword; | |||
[JsonProperty("email", NullValueHandling = NullValueHandling.Ignore)] | |||
public string Email; | |||
[JsonProperty("new_password")] | |||
public string Password; | |||
[JsonProperty("username", NullValueHandling = NullValueHandling.Ignore)] | |||
public string Username; | |||
[JsonProperty("avatar", NullValueHandling = NullValueHandling.Ignore)] | |||
public string Avatar; | |||
} | |||
public sealed class EditUserResponse : UserInfo { } | |||
//Events | |||
internal sealed class UserUpdateEvent : UserInfo { } | |||
} |
@@ -0,0 +1,153 @@ | |||
//Ignore unused/unassigned variable warnings | |||
#pragma warning disable CS0649 | |||
#pragma warning disable CS0169 | |||
using Newtonsoft.Json; | |||
using System.Collections.Generic; | |||
namespace Discord.API | |||
{ | |||
public class GetRegionsResponse : List<GetRegionsResponse.RegionData> | |||
{ | |||
public sealed class RegionData | |||
{ | |||
[JsonProperty("sample_hostname")] | |||
public string Hostname; | |||
[JsonProperty("sample_port")] | |||
public int Port; | |||
[JsonProperty("id")] | |||
public string Id; | |||
[JsonProperty("name")] | |||
public string Name; | |||
} | |||
} | |||
public class GetIceResponse | |||
{ | |||
[JsonProperty("ttl")] | |||
public string TTL; | |||
[JsonProperty("servers")] | |||
public ServerData[] Servers; | |||
public sealed class ServerData | |||
{ | |||
[JsonProperty("url")] | |||
public string URL; | |||
[JsonProperty("username")] | |||
public string Username; | |||
[JsonProperty("credential")] | |||
public string Credential; | |||
} | |||
} | |||
//Commands | |||
internal sealed class JoinVoiceCommand : WebSocketMessage<JoinVoiceCommand.Data> | |||
{ | |||
public JoinVoiceCommand() : base(4) { } | |||
public class Data | |||
{ | |||
[JsonProperty("guild_id")] | |||
public string ServerId; | |||
[JsonProperty("channel_id")] | |||
public string ChannelId; | |||
[JsonProperty("self_mute")] | |||
public string SelfMute; | |||
[JsonProperty("self_deaf")] | |||
public string SelfDeaf; | |||
} | |||
} | |||
//Events | |||
internal sealed class VoiceServerUpdateEvent | |||
{ | |||
[JsonProperty("guild_id")] | |||
public string GuildId; | |||
[JsonProperty("endpoint")] | |||
public string Endpoint; | |||
[JsonProperty("token")] | |||
public string Token; | |||
} | |||
//Commands (Voice) | |||
internal sealed class VoiceLoginCommand : WebSocketMessage<VoiceLoginCommand.Data> | |||
{ | |||
public VoiceLoginCommand() : base(0) { } | |||
public class Data | |||
{ | |||
[JsonProperty("server_id")] | |||
public string ServerId; | |||
[JsonProperty("user_id")] | |||
public string UserId; | |||
[JsonProperty("session_id")] | |||
public string SessionId; | |||
[JsonProperty("token")] | |||
public string Token; | |||
} | |||
} | |||
internal sealed class VoiceLogin2Command : WebSocketMessage<VoiceLogin2Command.Data> | |||
{ | |||
public VoiceLogin2Command() : base(1) { } | |||
public class Data | |||
{ | |||
public class SocketInfo | |||
{ | |||
[JsonProperty("address")] | |||
public string Address; | |||
[JsonProperty("port")] | |||
public int Port; | |||
[JsonProperty("mode")] | |||
public string Mode = "xsalsa20_poly1305"; | |||
} | |||
[JsonProperty("protocol")] | |||
public string Protocol = "udp"; | |||
[JsonProperty("data")] | |||
public SocketInfo SocketData = new SocketInfo(); | |||
} | |||
} | |||
internal sealed class VoiceKeepAliveCommand : WebSocketMessage<object> | |||
{ | |||
public VoiceKeepAliveCommand() : base(3, null) { } | |||
} | |||
internal sealed class IsTalkingCommand : WebSocketMessage<IsTalkingCommand.Data> | |||
{ | |||
public IsTalkingCommand() : base(5) { } | |||
public class Data | |||
{ | |||
[JsonProperty("delay")] | |||
public int Delay; | |||
[JsonProperty("speaking")] | |||
public bool IsSpeaking; | |||
} | |||
} | |||
//Events (Voice) | |||
public class VoiceReadyEvent | |||
{ | |||
[JsonProperty("ssrc")] | |||
public uint SSRC; | |||
[JsonProperty("port")] | |||
public ushort Port; | |||
[JsonProperty("modes")] | |||
public string[] Modes; | |||
[JsonProperty("heartbeat_interval")] | |||
public int HeartbeatInterval; | |||
} | |||
public class JoinServerEvent | |||
{ | |||
[JsonProperty("secret_key")] | |||
public byte[] SecretKey; | |||
[JsonProperty("mode")] | |||
public string Mode; | |||
} | |||
public class IsTalkingEvent | |||
{ | |||
[JsonProperty("user_id")] | |||
public string UserId; | |||
[JsonProperty("ssrc")] | |||
public uint SSRC; | |||
[JsonProperty("speaking")] | |||
public bool IsSpeaking; | |||
} | |||
} |
@@ -0,0 +1,116 @@ | |||
//Ignore unused/unassigned variable warnings | |||
#pragma warning disable CS0649 | |||
#pragma warning disable CS0169 | |||
using Newtonsoft.Json; | |||
using Newtonsoft.Json.Linq; | |||
using System.Collections.Generic; | |||
namespace Discord.API | |||
{ | |||
//Common | |||
public class WebSocketMessage | |||
{ | |||
[JsonProperty("op")] | |||
public int Operation; | |||
[JsonProperty("d")] | |||
public object Payload; | |||
[JsonProperty("t", NullValueHandling = NullValueHandling.Ignore)] | |||
public string Type; | |||
[JsonProperty("s", NullValueHandling = NullValueHandling.Ignore)] | |||
public int? Sequence; | |||
} | |||
internal abstract class WebSocketMessage<T> : WebSocketMessage | |||
where T : new() | |||
{ | |||
public WebSocketMessage() { Payload = new T(); } | |||
public WebSocketMessage(int op) { Operation = op; Payload = new T(); } | |||
public WebSocketMessage(int op, T payload) { Operation = op; Payload = payload; } | |||
[JsonIgnore] | |||
public new T Payload | |||
{ | |||
get | |||
{ | |||
if (base.Payload is JToken) | |||
base.Payload = (base.Payload as JToken).ToObject<T>(); | |||
return (T)base.Payload; | |||
} | |||
set { base.Payload = value; } | |||
} | |||
} | |||
//Commands | |||
internal sealed class KeepAliveCommand : WebSocketMessage<ulong> | |||
{ | |||
public KeepAliveCommand() : base(1, EpochTime.GetMilliseconds()) { } | |||
} | |||
internal sealed class LoginCommand : WebSocketMessage<LoginCommand.Data> | |||
{ | |||
public LoginCommand() : base(2) { } | |||
public class Data | |||
{ | |||
[JsonProperty("token")] | |||
public string Token; | |||
[JsonProperty("v")] | |||
public int Version = 3; | |||
[JsonProperty("properties")] | |||
public Dictionary<string, string> Properties = new Dictionary<string, string>(); | |||
[JsonProperty("large_threshold", NullValueHandling = NullValueHandling.Ignore)] | |||
public int? LargeThreshold; | |||
[JsonProperty("compress", NullValueHandling = NullValueHandling.Ignore)] | |||
public bool? Compress; | |||
} | |||
} | |||
internal sealed class ResumeCommand : WebSocketMessage<ResumeCommand.Data> | |||
{ | |||
public ResumeCommand() : base(6) { } | |||
public class Data | |||
{ | |||
[JsonProperty("session_id")] | |||
public string SessionId; | |||
[JsonProperty("seq")] | |||
public int Sequence; | |||
} | |||
} | |||
//Events | |||
internal sealed class ReadyEvent | |||
{ | |||
public sealed class ReadStateInfo | |||
{ | |||
[JsonProperty("id")] | |||
public string ChannelId; | |||
[JsonProperty("mention_count")] | |||
public int MentionCount; | |||
[JsonProperty("last_message_id")] | |||
public string LastMessageId; | |||
} | |||
[JsonProperty("v")] | |||
public int Version; | |||
[JsonProperty("user")] | |||
public UserInfo User; | |||
[JsonProperty("session_id")] | |||
public string SessionId; | |||
[JsonProperty("read_state")] | |||
public ReadStateInfo[] ReadState; | |||
[JsonProperty("guilds")] | |||
public ExtendedGuildInfo[] Guilds; | |||
[JsonProperty("private_channels")] | |||
public ChannelInfo[] PrivateChannels; | |||
[JsonProperty("heartbeat_interval")] | |||
public int HeartbeatInterval; | |||
} | |||
internal sealed class ResumedEvent | |||
{ | |||
[JsonProperty("heartbeat_interval")] | |||
public int HeartbeatInterval; | |||
} | |||
internal sealed class RedirectEvent | |||
{ | |||
[JsonProperty("url")] | |||
public string Url; | |||
} | |||
} |
@@ -0,0 +1,10 @@ | |||
namespace Discord.Audio | |||
{ | |||
public interface IDiscordVoiceBuffer | |||
{ | |||
int FrameSize { get; } | |||
int FrameCount { get; } | |||
ushort ReadPos { get; } | |||
ushort WritePos { get; } | |||
} | |||
} |
@@ -0,0 +1,16 @@ | |||
using System.Threading.Tasks; | |||
namespace Discord.Audio | |||
{ | |||
public interface IDiscordVoiceClient | |||
{ | |||
IDiscordVoiceBuffer OutputBuffer { get; } | |||
Task JoinChannel(string channelId); | |||
void SendVoicePCM(byte[] data, int count); | |||
void ClearVoicePCM(); | |||
Task WaitVoice(); | |||
} | |||
} |
@@ -1,7 +1,7 @@ | |||
using System; | |||
using System.Threading; | |||
namespace Discord.WebSockets.Voice | |||
namespace Discord.Audio | |||
{ | |||
internal class VoiceBuffer : IDiscordVoiceBuffer | |||
{ | |||
@@ -32,6 +32,9 @@ namespace Discord.WebSockets.Voice | |||
public void Push(byte[] buffer, int bytes, CancellationToken cancelToken) | |||
{ | |||
if (cancelToken.IsCancellationRequested) | |||
throw new OperationCanceledException("Client is disconnected.", cancelToken); | |||
int wholeFrames = bytes / _frameSize; | |||
int expectedBytes = wholeFrames * _frameSize; | |||
int lastFrameSize = bytes - expectedBytes; | |||
@@ -50,7 +53,10 @@ namespace Discord.WebSockets.Voice | |||
{ | |||
_notOverflowEvent.Wait(cancelToken); | |||
} | |||
catch (OperationCanceledException) { throw new InvalidOperationException("Client is not connected."); } | |||
catch (OperationCanceledException ex) | |||
{ | |||
throw new OperationCanceledException("Client is disconnected.", ex, cancelToken); | |||
} | |||
} | |||
if (i == wholeFrames) |
@@ -1,74 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
namespace Discord.Collections | |||
{ | |||
public sealed class Channels : AsyncCollection<Channel> | |||
{ | |||
internal Channels(DiscordClient client, object writerLock) | |||
: base(client, writerLock) { } | |||
internal Channel GetOrAdd(string id, string serverId, string recipientId = null) => GetOrAdd(id, () => new Channel(_client, id, serverId, recipientId)); | |||
internal new Channel TryRemove(string id) => base.TryRemove(id); | |||
protected override void OnCreated(Channel item) | |||
{ | |||
if (!item.IsPrivate) | |||
item.Server.AddChannel(item.Id); | |||
if (item.RecipientId != null) | |||
{ | |||
var user = item.Recipient; | |||
if (user.PrivateChannelId != null) | |||
throw new Exception("User already has a private channel."); | |||
user.PrivateChannelId = item.Id; | |||
user.AddRef(); | |||
} | |||
} | |||
protected override void OnRemoved(Channel item) | |||
{ | |||
if (!item.IsPrivate) | |||
{ | |||
var server = item.Server; | |||
if (server != null) | |||
item.Server.RemoveChannel(item.Id); | |||
} | |||
if (item.RecipientId != null) | |||
{ | |||
var user = item.Recipient; | |||
if (user != null) | |||
{ | |||
if (user.PrivateChannelId != item.Id) | |||
throw new Exception("User has a different private channel."); | |||
user.PrivateChannelId = null; | |||
user.RemoveRef(); | |||
} | |||
} | |||
} | |||
internal Channel this[string id] => Get(id); | |||
internal IEnumerable<Channel> Find(string serverId, string name, string type = null) | |||
{ | |||
if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
IEnumerable<Channel> result; | |||
if (name.StartsWith("#")) | |||
{ | |||
string name2 = name.Substring(1); | |||
result = this.Where(x => x.ServerId == serverId && | |||
string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) || string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); | |||
} | |||
else | |||
{ | |||
result = this.Where(x => x.ServerId == serverId && | |||
string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); | |||
} | |||
if (type != null) | |||
result = result.Where(x => x.Type == type); | |||
return result; | |||
} | |||
} | |||
} |
@@ -1,96 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
namespace Discord.Collections | |||
{ | |||
public sealed class Members : AsyncCollection<Member> | |||
{ | |||
internal Members(DiscordClient client, object writerLock) | |||
: base(client, writerLock) { } | |||
private string GetKey(string userId, string serverId) => serverId + '_' + userId; | |||
internal Member GetOrAdd(string userId, string serverId) => GetOrAdd(GetKey(userId, serverId), () => new Member(_client, userId, serverId)); | |||
internal Member TryRemove(string userId, string serverId) => base.TryRemove(GetKey(userId, serverId)); | |||
protected override void OnCreated(Member item) | |||
{ | |||
item.Server.AddMember(item); | |||
item.User.AddServer(item.ServerId); | |||
item.User.AddRef(); | |||
if (item.UserId == _client.CurrentUserId) | |||
item.Server.CurrentMember = item; | |||
} | |||
protected override void OnRemoved(Member item) | |||
{ | |||
var server = item.Server; | |||
if (server != null) | |||
{ | |||
server.RemoveMember(item); | |||
if (item.UserId == _client.CurrentUserId) | |||
server.CurrentMember = null; | |||
} | |||
var user = item.User; | |||
if (user != null) | |||
{ | |||
user.RemoveServer(item.ServerId); | |||
user.RemoveRef(); | |||
} | |||
} | |||
internal Member this[string userId, string serverId] | |||
{ | |||
get | |||
{ | |||
if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
if (userId == null) throw new ArgumentNullException(nameof(userId)); | |||
return Get(GetKey(userId, serverId)); | |||
} | |||
} | |||
internal IEnumerable<Member> Find(Server server, string name) | |||
{ | |||
if (server == null) throw new ArgumentNullException(nameof(server)); | |||
if (name == null) throw new ArgumentNullException(nameof(name)); | |||
if (name.StartsWith("@")) | |||
{ | |||
string name2 = name.Substring(1); | |||
return server.Members.Where(x => | |||
{ | |||
var user = x.User; | |||
if (user == null) | |||
return false; | |||
return string.Equals(user.Name, name, StringComparison.OrdinalIgnoreCase) || string.Equals(user.Name, name2, StringComparison.OrdinalIgnoreCase); | |||
}); | |||
} | |||
else | |||
{ | |||
return server.Members.Where(x => | |||
{ | |||
var user = x.User; | |||
if (user == null) | |||
return false; | |||
return string.Equals(x.User.Name, name, StringComparison.OrdinalIgnoreCase); | |||
}); | |||
} | |||
} | |||
internal Member Find(string username, string discriminator) | |||
{ | |||
if (username == null) throw new ArgumentNullException(nameof(username)); | |||
if (discriminator == null) throw new ArgumentNullException(nameof(discriminator)); | |||
if (username.StartsWith("@")) | |||
username = username.Substring(1); | |||
return this.Where(x => | |||
string.Equals(x.Name, username, StringComparison.OrdinalIgnoreCase) && | |||
x.Discriminator == discriminator | |||
) | |||
.FirstOrDefault(); | |||
} | |||
} | |||
} |
@@ -1,35 +0,0 @@ | |||
namespace Discord.Collections | |||
{ | |||
public sealed class Messages : AsyncCollection<Message> | |||
{ | |||
private readonly MessageCleaner _msgCleaner; | |||
internal Messages(DiscordClient client, object writerLock) | |||
: base(client, writerLock) | |||
{ | |||
_msgCleaner = new MessageCleaner(client); | |||
} | |||
internal Message GetOrAdd(string id, string channelId, string userId) => GetOrAdd(id, () => new Message(_client, id, channelId, userId)); | |||
internal new Message TryRemove(string id) => base.TryRemove(id); | |||
internal new Message Remap(string oldKey, string newKey) => base.Remap(oldKey, newKey); | |||
protected override void OnCreated(Message item) | |||
{ | |||
item.Channel.AddMessage(item.Id); | |||
item.User.AddRef(); | |||
} | |||
protected override void OnRemoved(Message item) | |||
{ | |||
var channel = item.Channel; | |||
if (channel != null) | |||
channel.RemoveMessage(item.Id); | |||
var user = item.User; | |||
if (user != null) | |||
user.RemoveRef(); | |||
} | |||
internal Message this[string id] => Get(id); | |||
internal string CleanText(string text) => _msgCleaner.Clean(text); | |||
} | |||
} |
@@ -1,46 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
namespace Discord.Collections | |||
{ | |||
public sealed class Roles : AsyncCollection<Role> | |||
{ | |||
internal Roles(DiscordClient client, object writerLock) | |||
: base(client, writerLock) { } | |||
internal Role GetOrAdd(string id, string serverId, bool isEveryone) => GetOrAdd(id, () => new Role(_client, id, serverId, isEveryone)); | |||
internal new Role TryRemove(string id) => base.TryRemove(id); | |||
protected override void OnCreated(Role item) | |||
{ | |||
item.Server.AddRole(item.Id); | |||
} | |||
protected override void OnRemoved(Role item) | |||
{ | |||
var server = item.Server; | |||
if (server != null) | |||
item.Server.RemoveRole(item.Id); | |||
} | |||
internal Role this[string id] => Get(id); | |||
internal IEnumerable<Role> Find(string serverId, string name) | |||
{ | |||
if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
if (name == null) throw new ArgumentNullException(nameof(name)); | |||
if (name.StartsWith("@")) | |||
{ | |||
string name2 = name.Substring(1); | |||
return this.Where(x => x.ServerId == serverId && | |||
string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) || string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); | |||
} | |||
else | |||
{ | |||
return this.Where(x => x.ServerId == serverId && | |||
string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); | |||
} | |||
} | |||
} | |||
} |
@@ -1,40 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
namespace Discord.Collections | |||
{ | |||
public sealed class Servers : AsyncCollection<Server> | |||
{ | |||
internal Servers(DiscordClient client, object writerLock) | |||
: base(client, writerLock) { } | |||
internal Server GetOrAdd(string id) => base.GetOrAdd(id, () => new Server(_client, id)); | |||
internal new Server TryRemove(string id) => base.TryRemove(id); | |||
protected override void OnCreated(Server item) { } | |||
protected override void OnRemoved(Server item) | |||
{ | |||
var channels = _client.Channels; | |||
foreach (var channelId in item.ChannelIds) | |||
channels.TryRemove(channelId); | |||
var members = _client.Members; | |||
foreach (var userId in item.UserIds) | |||
members.TryRemove(userId, item.Id); | |||
var roles = _client.Roles; | |||
foreach (var roleId in item.RoleIds) | |||
roles.TryRemove(roleId); | |||
} | |||
internal Server this[string id] => Get(id); | |||
internal IEnumerable<Server> Find(string name) | |||
{ | |||
if (name == null) throw new ArgumentNullException(nameof(name)); | |||
return this.Where(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); | |||
} | |||
} | |||
} |
@@ -1,37 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
namespace Discord.Collections | |||
{ | |||
public sealed class Users : AsyncCollection<User> | |||
{ | |||
internal Users(DiscordClient client, object writerLock) | |||
: base(client, writerLock) { } | |||
internal User GetOrAdd(string id) => GetOrAdd(id, () => new User(_client, id)); | |||
internal new User TryRemove(string id) => base.TryRemove(id); | |||
protected override void OnCreated(User item) { } | |||
protected override void OnRemoved(User item) { } | |||
internal User this[string id] => Get(id); | |||
internal IEnumerable<User> Find(string name) | |||
{ | |||
if (name == null) throw new ArgumentNullException(nameof(name)); | |||
if (name.StartsWith("@")) | |||
{ | |||
string name2 = name.Substring(1); | |||
return this.Where(x => | |||
string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) || string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); | |||
} | |||
else | |||
{ | |||
return this.Where(x => | |||
string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); | |||
} | |||
} | |||
} | |||
} |
@@ -1,4 +1,6 @@ | |||
using Discord.API; | |||
using Discord.Net; | |||
using Discord.Net.Rest; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
@@ -10,12 +12,15 @@ namespace Discord | |||
/// <summary> A lightweight wrapper around the Discord API. </summary> | |||
public class DiscordAPIClient | |||
{ | |||
private readonly DiscordAPIClientConfig _config; | |||
internal RestClient RestClient => _rest; | |||
private readonly RestClient _rest; | |||
public DiscordAPIClient(LogMessageSeverity logLevel, string userAgent, int timeout) | |||
public DiscordAPIClient(DiscordAPIClientConfig config = null) | |||
{ | |||
_rest = new RestClient(logLevel, userAgent, timeout); | |||
_config = config ?? new DiscordAPIClientConfig(); | |||
_rest = new RestClient(_config); | |||
} | |||
private string _token; | |||
@@ -34,16 +39,6 @@ namespace Discord | |||
//Auth | |||
public Task<GatewayResponse> Gateway() | |||
=> _rest.Get<GatewayResponse>(Endpoints.Gateway); | |||
public Task<FingerprintResponse> Fingerprint() | |||
=> _rest.Post<FingerprintResponse>(Endpoints.AuthFingerprint); | |||
public async Task<RegisterResponse> LoginAnonymous(string username, string fingerprint) | |||
{ | |||
if (username == null) throw new ArgumentNullException(nameof(username)); | |||
if (fingerprint == null) throw new ArgumentNullException(nameof(fingerprint)); | |||
var request = new RegisterRequest { Fingerprint = fingerprint, Username = username }; | |||
return await _rest.Post<RegisterResponse>(Endpoints.AuthRegister, request).ConfigureAwait(false); | |||
} | |||
public async Task<LoginResponse> Login(string email, string password) | |||
{ | |||
if (email == null) throw new ArgumentNullException(nameof(email)); | |||
@@ -115,11 +110,11 @@ namespace Discord | |||
} | |||
//Invites | |||
public Task<CreateInviteResponse> CreateInvite(string channelId, int maxAge, int maxUses, bool isTemporary, bool withXkcdPass) | |||
public Task<CreateInviteResponse> CreateInvite(string channelId, int maxAge, int maxUses, bool tempMembership, bool hasXkcd) | |||
{ | |||
if (channelId == null) throw new ArgumentNullException(nameof(channelId)); | |||
var request = new CreateInviteRequest { MaxAge = maxAge, MaxUses = maxUses, IsTemporary = isTemporary, WithXkcdPass = withXkcdPass }; | |||
var request = new CreateInviteRequest { MaxAge = maxAge, MaxUses = maxUses, IsTemporary = tempMembership, WithXkcdPass = hasXkcd }; | |||
return _rest.Post<CreateInviteResponse>(Endpoints.ChannelInvites(channelId), request); | |||
} | |||
public Task<GetInviteResponse> GetInvite(string inviteIdOrXkcd) | |||
@@ -142,7 +137,7 @@ namespace Discord | |||
} | |||
//Members | |||
public Task EditMember(string serverId, string userId, bool? mute = null, bool? deaf = null, IEnumerable<string> roles = null) | |||
public Task EditUser(string serverId, string userId, bool? mute = null, bool? deaf = null, IEnumerable<string> roles = null) | |||
{ | |||
if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
if (userId == null) throw new ArgumentNullException(nameof(userId)); | |||
@@ -235,26 +230,6 @@ namespace Discord | |||
return _rest.Delete(Endpoints.ChannelPermission(channelId, userOrRoleId), null); | |||
} | |||
//Profile | |||
public Task<EditProfileResponse> EditProfile(string currentPassword = "", | |||
string username = null, string email = null, string password = null, | |||
AvatarImageType avatarType = AvatarImageType.Png, byte[] avatar = null) | |||
{ | |||
if (currentPassword == null) throw new ArgumentNullException(nameof(currentPassword)); | |||
string avatarBase64 = null; | |||
if (avatarType == AvatarImageType.None) | |||
avatarBase64 = ""; | |||
else if (avatar != null) | |||
{ | |||
string base64 = Convert.ToBase64String(avatar); | |||
string type = avatarType == AvatarImageType.Jpeg ? "image/jpeg;base64" : "image/png;base64"; | |||
avatarBase64 = $"data:{type},{base64}"; | |||
} | |||
var request = new EditProfileRequest { CurrentPassword = currentPassword, Username = username, Email = email, Password = password, Avatar = avatarBase64 }; | |||
return _rest.Patch<EditProfileResponse>(Endpoints.UserMe, request); | |||
} | |||
//Roles | |||
public Task<RoleInfo> CreateRole(string serverId) | |||
{ | |||
@@ -304,18 +279,42 @@ namespace Discord | |||
return _rest.Delete<DeleteServerResponse>(Endpoints.Server(serverId)); | |||
} | |||
public Task<EditServerResponse> EditServer(string serverId, string name = null, string region = null) | |||
public Task<EditServerResponse> EditServer(string serverId, string name = null, string region = null, ImageType iconType = ImageType.Png, byte[] icon = null) | |||
{ | |||
if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
var request = new EditServerRequest { Name = name, Region = region }; | |||
var request = new EditServerRequest { Name = name, Region = region, Icon = Base64Picture(iconType, icon) }; | |||
return _rest.Patch<EditServerResponse>(Endpoints.Server(serverId), request); | |||
} | |||
//User | |||
public Task<EditUserResponse> EditUser(string currentPassword = "", | |||
string username = null, string email = null, string password = null, | |||
ImageType avatarType = ImageType.Png, byte[] avatar = null) | |||
{ | |||
if (currentPassword == null) throw new ArgumentNullException(nameof(currentPassword)); | |||
var request = new EditUserRequest { CurrentPassword = currentPassword, Username = username, Email = email, Password = password, Avatar = Base64Picture(avatarType, avatar) }; | |||
return _rest.Patch<EditUserResponse>(Endpoints.UserMe, request); | |||
} | |||
//Voice | |||
public Task<GetRegionsResponse> GetVoiceRegions() | |||
=> _rest.Get<GetRegionsResponse>(Endpoints.VoiceRegions); | |||
public Task<GetIceResponse> GetVoiceIce() | |||
=> _rest.Get<GetIceResponse>(Endpoints.VoiceIce); | |||
/*public Task<GetIceResponse> GetVoiceIce() | |||
=> _rest.Get<GetIceResponse>(Endpoints.VoiceIce);*/ | |||
private string Base64Picture(ImageType type, byte[] data) | |||
{ | |||
if (type == ImageType.None) | |||
return ""; | |||
else if (data != null) | |||
{ | |||
string base64 = Convert.ToBase64String(data); | |||
string imageType = type == ImageType.Jpeg ? "image/jpeg;base64" : "image/png;base64"; | |||
return $"data:{imageType},{base64}"; | |||
} | |||
return null; | |||
} | |||
} | |||
} |
@@ -0,0 +1,50 @@ | |||
using System; | |||
using System.Net; | |||
using System.Reflection; | |||
namespace Discord | |||
{ | |||
public class DiscordAPIClientConfig | |||
{ | |||
/// <summary> Specifies the minimum log level severity that will be sent to the LogMessage event. Warning: setting this to debug will really hurt performance but should help investigate any internal issues. </summary> | |||
public LogMessageSeverity LogLevel { get { return _logLevel; } set { SetValue(ref _logLevel, value); } } | |||
private LogMessageSeverity _logLevel = LogMessageSeverity.Info; | |||
/// <summary> Max time (in milliseconds) to wait for an API request to complete. </summary> | |||
public int APITimeout { get { return _apiTimeout; } set { SetValue(ref _apiTimeout, value); } } | |||
private int _apiTimeout = 10000; | |||
/// <summary> The proxy to user for API and WebSocket connections. </summary> | |||
public string ProxyUrl { get { return _proxyUrl; } set { SetValue(ref _proxyUrl, value); } } | |||
private string _proxyUrl = null; | |||
/// <summary> The credentials to use for this proxy. </summary> | |||
public NetworkCredential ProxyCredentials { get { return _proxyCredentials; } set { SetValue(ref _proxyCredentials, value); } } | |||
private NetworkCredential _proxyCredentials = null; | |||
internal string UserAgent | |||
{ | |||
get | |||
{ | |||
string version = typeof(DiscordClientConfig).GetTypeInfo().Assembly.GetName().Version.ToString(2); | |||
return $"Discord.Net/{version} (https://github.com/RogueException/Discord.Net)"; | |||
} | |||
} | |||
//Lock | |||
protected bool _isLocked; | |||
internal void Lock() { _isLocked = true; } | |||
protected void SetValue<T>(ref T storage, T value) | |||
{ | |||
if (_isLocked) | |||
throw new InvalidOperationException("Unable to modify a discord client's configuration after it has been created."); | |||
storage = value; | |||
} | |||
public DiscordAPIClientConfig Clone() | |||
{ | |||
var config = MemberwiseClone() as DiscordAPIClientConfig; | |||
config._isLocked = false; | |||
return config; | |||
} | |||
} | |||
} |
@@ -1,781 +0,0 @@ | |||
using Discord.API; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Net; | |||
using System.Threading.Tasks; | |||
namespace Discord | |||
{ | |||
public enum AvatarImageType | |||
{ | |||
None, | |||
Jpeg, | |||
Png | |||
} | |||
public partial class DiscordClient | |||
{ | |||
public const int MaxMessageSize = 2000; | |||
//Bans | |||
/// <summary> Bans a user from the provided server. </summary> | |||
public Task Ban(Member member) | |||
=> Ban(member?.ServerId, member?.UserId); | |||
/// <summary> Bans a user from the provided server. </summary> | |||
public Task Ban(Server server, User user) | |||
=> Ban(server?.Id, user?.Id); | |||
/// <summary> Bans a user from the provided server. </summary> | |||
public Task Ban(Server server, string userId) | |||
=> Ban(server?.Id, userId); | |||
/// <summary> Bans a user from the provided server. </summary> | |||
public Task Ban(string server, User user) | |||
=> Ban(server, user?.Id); | |||
/// <summary> Bans a user from the provided server. </summary> | |||
public Task Ban(string serverId, string userId) | |||
{ | |||
CheckReady(); | |||
if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
if (userId == null) throw new ArgumentNullException(nameof(userId)); | |||
return _api.Ban(serverId, userId); | |||
} | |||
/// <summary> Unbans a user from the provided server. </summary> | |||
public Task Unban(Member member) | |||
=> Unban(member?.ServerId, member?.UserId); | |||
/// <summary> Unbans a user from the provided server. </summary> | |||
public Task Unban(Server server, User user) | |||
=> Unban(server?.Id, user?.Id); | |||
/// <summary> Unbans a user from the provided server. </summary> | |||
public Task Unban(Server server, string userId) | |||
=> Unban(server?.Id, userId); | |||
/// <summary> Unbans a user from the provided server. </summary> | |||
public Task Unban(string server, User user) | |||
=> Unban(server, user?.Id); | |||
/// <summary> Unbans a user from the provided server. </summary> | |||
public async Task Unban(string serverId, string userId) | |||
{ | |||
CheckReady(); | |||
if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
if (userId == null) throw new ArgumentNullException(nameof(userId)); | |||
try { await _api.Unban(serverId, userId).ConfigureAwait(false); } | |||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||
} | |||
//Channels | |||
/// <summary> Creates a new channel with the provided name and type (see ChannelTypes). </summary> | |||
public Task<Channel> CreateChannel(Server server, string name, string type) | |||
=> CreateChannel(server?.Id, name, type); | |||
/// <summary> Creates a new channel with the provided name and type (see ChannelTypes). </summary> | |||
public async Task<Channel> CreateChannel(string serverId, string name, string type) | |||
{ | |||
CheckReady(); | |||
if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
if (name == null) throw new ArgumentNullException(nameof(name)); | |||
if (type == null) throw new ArgumentNullException(nameof(type)); | |||
var response = await _api.CreateChannel(serverId, name, type).ConfigureAwait(false); | |||
var channel = _channels.GetOrAdd(response.Id, response.GuildId, response.Recipient?.Id); | |||
channel.Update(response); | |||
return channel; | |||
} | |||
/// <summary> Returns the private channel with the provided user, creating one if it does not currently exist. </summary> | |||
public Task<Channel> CreatePMChannel(string userId) => CreatePMChannel(_users[userId], userId); | |||
/// <summary> Returns the private channel with the provided user, creating one if it does not currently exist. </summary> | |||
public Task<Channel> CreatePMChannel(User user) => CreatePMChannel(user, user?.Id); | |||
/// <summary> Returns the private channel with the provided user, creating one if it does not currently exist. </summary> | |||
public Task<Channel> CreatePMChannel(Member member) => CreatePMChannel(member.User, member.UserId); | |||
private async Task<Channel> CreatePMChannel(User user, string userId) | |||
{ | |||
CheckReady(); | |||
if (userId == null) throw new ArgumentNullException(nameof(userId)); | |||
Channel channel = null; | |||
if (user != null) | |||
channel = user.PrivateChannel; | |||
if (channel == null) | |||
{ | |||
var response = await _api.CreatePMChannel(CurrentUserId, userId).ConfigureAwait(false); | |||
user = _users.GetOrAdd(response.Recipient?.Id); | |||
user.Update(response.Recipient); | |||
channel = _channels.GetOrAdd(response.Id, response.GuildId, response.Recipient?.Id); | |||
channel.Update(response); | |||
} | |||
return channel; | |||
} | |||
/// <summary> Edits the provided channel, changing only non-null attributes. </summary> | |||
public Task EditChannel(string channelId, string name = null, string topic = null, int? position = null) | |||
=> EditChannel(_channels[channelId], name: name, topic: topic, position: position); | |||
/// <summary> Edits the provided channel, changing only non-null attributes. </summary> | |||
public async Task EditChannel(Channel channel, string name = null, string topic = null, int? position = null) | |||
{ | |||
CheckReady(); | |||
if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||
await _api.EditChannel(channel.Id, name: name, topic: topic); | |||
if (position != null) | |||
{ | |||
int oldPos = channel.Position; | |||
int newPos = position.Value; | |||
int minPos; | |||
Channel[] channels = channel.Server.Channels.OrderBy(x => x.Position).ToArray(); | |||
if (oldPos < newPos) //Moving Down | |||
{ | |||
minPos = oldPos; | |||
for (int i = oldPos; i < newPos; i++) | |||
channels[i] = channels[i + 1]; | |||
channels[newPos] = channel; | |||
} | |||
else //(oldPos > newPos) Moving Up | |||
{ | |||
minPos = newPos; | |||
for (int i = oldPos; i > newPos; i--) | |||
channels[i] = channels[i - 1]; | |||
channels[newPos] = channel; | |||
} | |||
await _api.ReorderChannels(channel.ServerId, channels.Skip(minPos).Select(x => x.Id), minPos); | |||
} | |||
} | |||
public Task ReorderChannels(Server server, IEnumerable<object> channels, int startPos = 0) | |||
=> ReorderChannels(server.Id, channels, startPos); | |||
public Task ReorderChannels(string serverId, IEnumerable<object> channels, int startPos = 0) | |||
{ | |||
if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
if (channels == null) throw new ArgumentNullException(nameof(channels)); | |||
if (startPos < 0) throw new ArgumentOutOfRangeException(nameof(startPos), "startPos must be a positive integer."); | |||
var channelIds = CollectionHelper.FlattenChannels(channels); | |||
return _api.ReorderChannels(serverId, channelIds, startPos); | |||
} | |||
/// <summary> Destroys the provided channel. </summary> | |||
public Task<Channel> DestroyChannel(Channel channel) | |||
=> DestroyChannel(channel?.Id); | |||
/// <summary> Destroys the provided channel. </summary> | |||
public async Task<Channel> DestroyChannel(string channelId) | |||
{ | |||
CheckReady(); | |||
if (channelId == null) throw new ArgumentNullException(nameof(channelId)); | |||
try { await _api.DestroyChannel(channelId).ConfigureAwait(false); } | |||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||
return _channels.TryRemove(channelId); | |||
} | |||
//Invites | |||
/// <summary> Creates a new invite to the default channel of the provided server. </summary> | |||
/// <param name="maxAge"> Time (in seconds) until the invite expires. Set to 0 to never expire. </param> | |||
/// <param name="isTemporary"> If true, a user accepting this invite will be kicked from the server after closing their client. </param> | |||
/// <param name="hasXkcdPass"> If true, creates a human-readable link. Not supported if maxAge is set to 0. </param> | |||
/// <param name="maxUses"> The max amount of times this invite may be used. </param> | |||
public Task<Invite> CreateInvite(Server server, int maxAge, int maxUses, bool isTemporary, bool hasXkcdPass) | |||
=> CreateInvite(server?.DefaultChannelId, maxAge, maxUses, isTemporary, hasXkcdPass); | |||
/// <summary> Creates a new invite to the provided channel. </summary> | |||
/// <param name="maxAge"> Time (in seconds) until the invite expires. Set to 0 to never expire. </param> | |||
/// <param name="isTemporary"> If true, a user accepting this invite will be kicked from the server after closing their client. </param> | |||
/// <param name="hasXkcdPass"> If true, creates a human-readable link. Not supported if maxAge is set to 0. </param> | |||
/// <param name="maxUses"> The max amount of times this invite may be used. </param> | |||
public Task<Invite> CreateInvite(Channel channel, int maxAge, int maxUses, bool isTemporary, bool hasXkcdPass) | |||
=> CreateInvite(channel?.Id, maxAge, maxUses, isTemporary, hasXkcdPass); | |||
/// <summary> Creates a new invite to the provided channel. </summary> | |||
/// <param name="maxAge"> Time (in seconds) until the invite expires. Set to 0 to never expire. </param> | |||
/// <param name="isTemporary"> If true, a user accepting this invite will be kicked from the server after closing their client. </param> | |||
/// <param name="hasXkcdPass"> If true, creates a human-readable link. Not supported if maxAge is set to 0. </param> | |||
/// <param name="maxUses"> The max amount of times this invite may be used. </param> | |||
public async Task<Invite> CreateInvite(string channelId, int maxAge, int maxUses, bool isTemporary, bool hasXkcdPass) | |||
{ | |||
CheckReady(); | |||
if (channelId == null) throw new ArgumentNullException(nameof(channelId)); | |||
if (maxAge <= 0) throw new ArgumentOutOfRangeException(nameof(maxAge)); | |||
if (maxUses <= 0) throw new ArgumentOutOfRangeException(nameof(maxUses)); | |||
var response = await _api.CreateInvite(channelId, maxAge, maxUses, isTemporary, hasXkcdPass).ConfigureAwait(false); | |||
var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id); | |||
invite.Update(response); | |||
return invite; | |||
} | |||
/// <summary> Deletes the provided invite. </summary> | |||
public async Task DestroyInvite(string inviteId) | |||
{ | |||
CheckReady(); | |||
if (inviteId == null) throw new ArgumentNullException(nameof(inviteId)); | |||
try | |||
{ | |||
//Check if this is a human-readable link and get its ID | |||
var response = await _api.GetInvite(inviteId).ConfigureAwait(false); | |||
await _api.DeleteInvite(response.Code).ConfigureAwait(false); | |||
} | |||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||
} | |||
/// <summary> Gets more info about the provided invite code. </summary> | |||
/// <remarks> Supported formats: inviteCode, xkcdCode, https://discord.gg/inviteCode, https://discord.gg/xkcdCode </remarks> | |||
public async Task<Invite> GetInvite(string inviteIdOrXkcd) | |||
{ | |||
CheckReady(); | |||
if (inviteIdOrXkcd == null) throw new ArgumentNullException(nameof(inviteIdOrXkcd)); | |||
var response = await _api.GetInvite(inviteIdOrXkcd).ConfigureAwait(false); | |||
var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id); | |||
invite.Update(response); | |||
return invite; | |||
} | |||
/// <summary> Accepts the provided invite. </summary> | |||
public Task AcceptInvite(Invite invite) | |||
{ | |||
CheckReady(); | |||
if (invite == null) throw new ArgumentNullException(nameof(invite)); | |||
return _api.AcceptInvite(invite.Id); | |||
} | |||
/// <summary> Accepts the provided invite. </summary> | |||
public async Task AcceptInvite(string inviteId) | |||
{ | |||
CheckReady(); | |||
if (inviteId == null) throw new ArgumentNullException(nameof(inviteId)); | |||
//Remove trailing slash and any non-code url parts | |||
if (inviteId.Length > 0 && inviteId[inviteId.Length - 1] == '/') | |||
inviteId = inviteId.Substring(0, inviteId.Length - 1); | |||
int index = inviteId.LastIndexOf('/'); | |||
if (index >= 0) | |||
inviteId = inviteId.Substring(index + 1); | |||
//Check if this is a human-readable link and get its ID | |||
var invite = await GetInvite(inviteId).ConfigureAwait(false); | |||
await _api.AcceptInvite(invite.Id).ConfigureAwait(false); | |||
} | |||
//Members | |||
public Task EditMember(Member member, bool? mute = null, bool? deaf = null, IEnumerable<object> roles = null) | |||
=> EditMember(member?.ServerId, member?.UserId, mute, deaf, roles); | |||
public Task EditMember(Server server, User user, bool? mute = null, bool? deaf = null, IEnumerable<object> roles = null) | |||
=> EditMember(server?.Id, user?.Id, mute, deaf, roles); | |||
public Task EditMember(Server server, string userId, bool? mute = null, bool? deaf = null, IEnumerable<string> roles = null) | |||
=> EditMember(server?.Id, userId, mute, deaf, roles); | |||
public Task EditMember(string serverId, User user, bool? mute = null, bool? deaf = null, IEnumerable<object> roles = null) | |||
=> EditMember(serverId, user?.Id, mute, deaf, roles); | |||
public Task EditMember(string serverId, string userId, bool? mute = null, bool? deaf = null, IEnumerable<object> roles = null) | |||
{ | |||
CheckReady(); | |||
if (serverId == null) throw new NullReferenceException(nameof(serverId)); | |||
if (userId == null) throw new NullReferenceException(nameof(userId)); | |||
var newRoles = CollectionHelper.FlattenRoles(roles); | |||
return _api.EditMember(serverId, userId, mute: mute, deaf: deaf, roles: newRoles); | |||
} | |||
//Messages | |||
/// <summary> Sends a message to the provided channel, optionally mentioning certain users. </summary> | |||
/// <remarks> While not required, it is recommended to include a mention reference in the text (see User.Mention). </remarks> | |||
public Task<Message[]> SendMessage(Channel channel, string text, IEnumerable<object> mentionedUsers = null) | |||
=> SendMessage(channel, text, mentionedUsers, false); | |||
/// <summary> Sends a message to the provided channel, optionally mentioning certain users. </summary> | |||
/// <remarks> While not required, it is recommended to include a mention reference in the text (see User.Mention). </remarks> | |||
public Task<Message[]> SendMessage(string channelId, string text, IEnumerable<object> mentionedUsers = null) | |||
=> SendMessage(_channels[channelId], text, mentionedUsers, false); | |||
/// <summary> Sends a message to the provided channel, optionally mentioning certain users. </summary> | |||
/// <remarks> While not required, it is recommended to include a mention reference in the text (see User.Mention). </remarks> | |||
public Task<Message[]> SendTTSMessage(Channel channel, string text, IEnumerable<object> mentionedUsers = null) | |||
=> SendMessage(channel, text, mentionedUsers, true); | |||
/// <summary> Sends a message to the provided channel, optionally mentioning certain users. </summary> | |||
/// <remarks> While not required, it is recommended to include a mention reference in the text (see User.Mention). </remarks> | |||
public Task<Message[]> SendTTSMessage(string channelId, string text, IEnumerable<object> mentionedUsers = null) | |||
=> SendMessage(_channels[channelId], text, mentionedUsers, true); | |||
/// <summary> Sends a message to the provided channel, optionally mentioning certain users. </summary> | |||
/// <remarks> While not required, it is recommended to include a mention reference in the text (see User.Mention). </remarks> | |||
private async Task<Message[]> SendMessage(Channel channel, string text, IEnumerable<object> mentionedUsers = null, bool isTextToSpeech = false) | |||
{ | |||
CheckReady(); | |||
if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||
if (text == null) throw new ArgumentNullException(nameof(text)); | |||
var mentionedUserIds = CollectionHelper.FlattenUsers(mentionedUsers); | |||
int blockCount = (int)Math.Ceiling(text.Length / (double)MaxMessageSize); | |||
Message[] result = new Message[blockCount]; | |||
for (int i = 0; i < blockCount; i++) | |||
{ | |||
int index = i * MaxMessageSize; | |||
string blockText = text.Substring(index, Math.Min(2000, text.Length - index)); | |||
var nonce = GenerateNonce(); | |||
if (Config.UseMessageQueue) | |||
{ | |||
var msg = _messages.GetOrAdd("nonce_" + nonce, channel.Id, CurrentUserId); | |||
var currentMember = _members[msg.UserId, channel.ServerId]; | |||
msg.Update(new API.Message | |||
{ | |||
Content = blockText, | |||
Timestamp = DateTime.UtcNow, | |||
Author = new UserReference { Avatar = currentMember.AvatarId, Discriminator = currentMember.Discriminator, Id = CurrentUserId, Username = currentMember.Name }, | |||
ChannelId = channel.Id, | |||
IsTextToSpeech = isTextToSpeech | |||
}); | |||
msg.IsQueued = true; | |||
msg.Nonce = nonce; | |||
result[i] = msg; | |||
_pendingMessages.Enqueue(msg); | |||
} | |||
else | |||
{ | |||
var model = await _api.SendMessage(channel.Id, blockText, mentionedUserIds, nonce, isTextToSpeech).ConfigureAwait(false); | |||
var msg = _messages.GetOrAdd(model.Id, channel.Id, model.Author.Id); | |||
msg.Update(model); | |||
RaiseMessageSent(msg); | |||
result[i] = msg; | |||
} | |||
await Task.Delay(1000).ConfigureAwait(false); | |||
} | |||
return result; | |||
} | |||
/// <summary> Sends a private message to the provided user. </summary> | |||
public Task<Message[]> SendPrivateMessage(Member member, string text) | |||
=> SendPrivateMessage(member?.UserId, text); | |||
/// <summary> Sends a private message to the provided user. </summary> | |||
public Task<Message[]> SendPrivateMessage(User user, string text) | |||
=> SendPrivateMessage(user?.Id, text); | |||
/// <summary> Sends a private message to the provided user. </summary> | |||
public async Task<Message[]> SendPrivateMessage(string userId, string text) | |||
{ | |||
var channel = await CreatePMChannel(userId).ConfigureAwait(false); | |||
return await SendMessage(channel, text, new string[0]).ConfigureAwait(false); | |||
} | |||
/// <summary> Sends a file to the provided channel. </summary> | |||
public Task SendFile(Channel channel, string filePath) | |||
=> SendFile(channel?.Id, filePath); | |||
/// <summary> Sends a file to the provided channel. </summary> | |||
public Task SendFile(string channelId, string filePath) | |||
{ | |||
CheckReady(); | |||
if (channelId == null) throw new ArgumentNullException(nameof(channelId)); | |||
if (filePath == null) throw new ArgumentNullException(nameof(filePath)); | |||
return _api.SendFile(channelId, filePath); | |||
} | |||
/// <summary> Edits the provided message, changing only non-null attributes. </summary> | |||
/// <remarks> While not required, it is recommended to include a mention reference in the text (see Mention.User). </remarks> | |||
public Task EditMessage(Message message, string text = null, IEnumerable<object> mentionedUsers = null) | |||
=> EditMessage(message?.ChannelId, message?.Id, text, mentionedUsers); | |||
/// <summary> Edits the provided message, changing only non-null attributes. </summary> | |||
/// <remarks> While not required, it is recommended to include a mention reference in the text (see Mention.User). </remarks> | |||
public Task EditMessage(Channel channel, string messageId, string text = null, IEnumerable<object> mentionedUsers = null) | |||
=> EditMessage(channel?.Id, messageId, text, mentionedUsers); | |||
/// <summary> Edits the provided message, changing only non-null attributes. </summary> | |||
/// <remarks> While not required, it is recommended to include a mention reference in the text (see Mention.User). </remarks> | |||
public async Task EditMessage(string channelId, string messageId, string text = null, IEnumerable<object> mentionedUsers = null) | |||
{ | |||
CheckReady(); | |||
if (channelId == null) throw new ArgumentNullException(nameof(channelId)); | |||
if (messageId == null) throw new ArgumentNullException(nameof(messageId)); | |||
var mentionedUserIds = CollectionHelper.FlattenUsers(mentionedUsers); | |||
if (text != null && text.Length > MaxMessageSize) | |||
text = text.Substring(0, MaxMessageSize); | |||
var model = await _api.EditMessage(messageId, channelId, text, mentionedUserIds).ConfigureAwait(false); | |||
var msg = _messages[messageId]; | |||
if (msg != null) | |||
msg.Update(model); | |||
} | |||
/// <summary> Deletes the provided message. </summary> | |||
public Task DeleteMessage(Message msg) | |||
=> DeleteMessage(msg?.ChannelId, msg?.Id); | |||
/// <summary> Deletes the provided message. </summary> | |||
public async Task DeleteMessage(string channelId, string msgId) | |||
{ | |||
CheckReady(); | |||
if (channelId == null) throw new ArgumentNullException(nameof(channelId)); | |||
if (msgId == null) throw new ArgumentNullException(nameof(msgId)); | |||
try | |||
{ | |||
await _api.DeleteMessage(msgId, channelId).ConfigureAwait(false); | |||
_messages.TryRemove(msgId); | |||
} | |||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||
} | |||
public async Task DeleteMessages(IEnumerable<Message> msgs) | |||
{ | |||
CheckReady(); | |||
if (msgs == null) throw new ArgumentNullException(nameof(msgs)); | |||
foreach (var msg in msgs) | |||
{ | |||
try | |||
{ | |||
await _api.DeleteMessage(msg.Id, msg.ChannelId).ConfigureAwait(false); | |||
} | |||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||
} | |||
} | |||
public async Task DeleteMessages(string channelId, IEnumerable<string> msgIds) | |||
{ | |||
CheckReady(); | |||
if (msgIds == null) throw new ArgumentNullException(nameof(msgIds)); | |||
foreach (var msgId in msgIds) | |||
{ | |||
try | |||
{ | |||
await _api.DeleteMessage(msgId, channelId).ConfigureAwait(false); | |||
} | |||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||
} | |||
} | |||
/// <summary> Downloads last count messages from the server, starting at beforeMessageId if it's provided. </summary> | |||
public Task<Message[]> DownloadMessages(Channel channel, int count, string beforeMessageId = null, bool cache = true) | |||
=> DownloadMessages(channel.Id, count, beforeMessageId, cache); | |||
/// <summary> Downloads last count messages from the server, starting at beforeMessageId if it's provided. </summary> | |||
public async Task<Message[]> DownloadMessages(string channelId, int count, string beforeMessageId = null, bool cache = true) | |||
{ | |||
CheckReady(); | |||
if (channelId == null) throw new NullReferenceException(nameof(channelId)); | |||
if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); | |||
if (count == 0) return new Message[0]; | |||
Channel channel = _channels[channelId]; | |||
if (channel != null && channel.Type == ChannelTypes.Text) | |||
{ | |||
try | |||
{ | |||
var msgs = await _api.GetMessages(channel.Id, count).ConfigureAwait(false); | |||
return msgs.Select(x => | |||
{ | |||
Message msg; | |||
if (cache) | |||
msg = _messages.GetOrAdd(x.Id, x.ChannelId, x.Author.Id); | |||
else | |||
msg = _messages[x.Id] ?? new Message(this, x.Id, x.ChannelId, x.Author.Id); | |||
if (msg != null) | |||
{ | |||
msg.Update(x); | |||
if (Config.TrackActivity) | |||
{ | |||
if (channel.IsPrivate) | |||
{ | |||
var user = msg.User; | |||
if (user != null) | |||
user.UpdateActivity(msg.EditedTimestamp ?? msg.Timestamp); | |||
} | |||
else | |||
{ | |||
var member = msg.Member; | |||
if (member != null) | |||
member.UpdateActivity(msg.EditedTimestamp ?? msg.Timestamp); | |||
} | |||
} | |||
} | |||
return msg; | |||
}) | |||
.ToArray(); | |||
} | |||
catch (HttpException) { } //Bad Permissions? | |||
} | |||
return null; | |||
} | |||
//Permissions | |||
public Task SetChannelUserPermissions(Channel channel, Member member, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) | |||
=> SetChannelPermissions(channel, member?.UserId, PermissionTarget.Member, allow, deny); | |||
public Task SetChannelUserPermissions(string channelId, Member member, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) | |||
=> SetChannelPermissions(_channels[channelId], member?.UserId, PermissionTarget.Member, allow, deny); | |||
public Task SetChannelUserPermissions(Channel channel, User user, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) | |||
=> SetChannelPermissions(channel, user?.Id, PermissionTarget.Member, allow, deny); | |||
public Task SetChannelUserPermissions(string channelId, User user, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) | |||
=> SetChannelPermissions(_channels[channelId], user?.Id, PermissionTarget.Member, allow, deny); | |||
public Task SetChannelUserPermissions(Channel channel, string userId, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) | |||
=> SetChannelPermissions(channel, userId, PermissionTarget.Member, allow, deny); | |||
public Task SetChannelUserPermissions(string channelId, string userId, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) | |||
=> SetChannelPermissions(_channels[channelId], userId, PermissionTarget.Member, allow, deny); | |||
public Task SetChannelRolePermissions(Channel channel, Role role, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) | |||
=> SetChannelPermissions(channel, role?.Id, PermissionTarget.Role, allow, deny); | |||
public Task SetChannelRolePermissions(string channelId, Role role, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) | |||
=> SetChannelPermissions(_channels[channelId], role?.Id, PermissionTarget.Role, allow, deny); | |||
public Task SetChannelRolePermissions(Channel channel, string userId, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) | |||
=> SetChannelPermissions(channel, userId, PermissionTarget.Role, allow, deny); | |||
public Task SetChannelRolePermissions(string channelId, string userId, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) | |||
=> SetChannelPermissions(_channels[channelId], userId, PermissionTarget.Role, allow, deny); | |||
private async Task SetChannelPermissions(Channel channel, string targetId, string targetType, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) | |||
{ | |||
CheckReady(); | |||
if (channel == null) throw new NullReferenceException(nameof(channel)); | |||
if (targetId == null) throw new NullReferenceException(nameof(targetId)); | |||
if (targetType == null) throw new NullReferenceException(nameof(targetType)); | |||
uint allowValue = allow?.RawValue ?? 0; | |||
uint denyValue = deny?.RawValue ?? 0; | |||
bool changed = false; | |||
var perms = channel.PermissionOverwrites.Where(x => x.TargetType != targetType || x.TargetId != targetId).FirstOrDefault(); | |||
if (allowValue != 0 || denyValue != 0) | |||
{ | |||
await _api.SetChannelPermissions(channel.Id, targetId, targetType, allowValue, denyValue); | |||
if (perms != null) | |||
{ | |||
perms.Allow.SetRawValueInternal(allowValue); | |||
perms.Deny.SetRawValueInternal(denyValue); | |||
} | |||
else | |||
{ | |||
var oldPerms = channel._permissionOverwrites; | |||
var newPerms = new Channel.PermissionOverwrite[oldPerms.Length + 1]; | |||
Array.Copy(oldPerms, newPerms, oldPerms.Length); | |||
newPerms[oldPerms.Length] = new Channel.PermissionOverwrite(targetType, targetId, allowValue, denyValue); | |||
channel._permissionOverwrites = newPerms; | |||
} | |||
changed = true; | |||
} | |||
else | |||
{ | |||
try | |||
{ | |||
await _api.DeleteChannelPermissions(channel.Id, targetId); | |||
if (perms != null) | |||
{ | |||
channel._permissionOverwrites = channel.PermissionOverwrites.Where(x => x.TargetType != targetType || x.TargetId != targetId).ToArray(); | |||
changed = true; | |||
} | |||
} | |||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||
} | |||
if (changed) | |||
{ | |||
if (targetType == PermissionTarget.Role) | |||
channel.InvalidatePermissionsCache(); | |||
else if (targetType == PermissionTarget.Member) | |||
channel.InvalidatePermissionsCache(targetId); | |||
} | |||
} | |||
public Task RemoveChannelUserPermissions(Channel channel, Member member) | |||
=> RemoveChannelPermissions(channel, member?.UserId, PermissionTarget.Member); | |||
public Task RemoveChannelUserPermissions(string channelId, Member member) | |||
=> RemoveChannelPermissions(_channels[channelId], member?.UserId, PermissionTarget.Member); | |||
public Task RemoveChannelUserPermissions(Channel channel, User user) | |||
=> RemoveChannelPermissions(channel, user?.Id, PermissionTarget.Member); | |||
public Task RemoveChannelUserPermissions(string channelId, User user) | |||
=> RemoveChannelPermissions(_channels[channelId], user?.Id, PermissionTarget.Member); | |||
public Task RemoveChannelUserPermissions(Channel channel, string userId) | |||
=> RemoveChannelPermissions(channel, userId, PermissionTarget.Member); | |||
public Task RemoveChannelUserPermissions(string channelId, string userId) | |||
=> RemoveChannelPermissions(_channels[channelId], userId, PermissionTarget.Member); | |||
public Task RemoveChannelRolePermissions(Channel channel, Role role) | |||
=> RemoveChannelPermissions(channel, role?.Id, PermissionTarget.Role); | |||
public Task RemoveChannelRolePermissions(string channelId, Role role) | |||
=> RemoveChannelPermissions(_channels[channelId], role?.Id, PermissionTarget.Role); | |||
public Task RemoveChannelRolePermissions(Channel channel, string roleId) | |||
=> RemoveChannelPermissions(channel, roleId, PermissionTarget.Role); | |||
public Task RemoveChannelRolePermissions(string channelId, string roleId) | |||
=> RemoveChannelPermissions(_channels[channelId], roleId, PermissionTarget.Role); | |||
private async Task RemoveChannelPermissions(Channel channel, string userOrRoleId, string idType) | |||
{ | |||
CheckReady(); | |||
if (channel == null) throw new NullReferenceException(nameof(channel)); | |||
if (userOrRoleId == null) throw new NullReferenceException(nameof(userOrRoleId)); | |||
if (idType == null) throw new NullReferenceException(nameof(idType)); | |||
try | |||
{ | |||
var perms = channel.PermissionOverwrites.Where(x => x.TargetType != idType || x.TargetId != userOrRoleId).FirstOrDefault(); | |||
await _api.DeleteChannelPermissions(channel.Id, userOrRoleId).ConfigureAwait(false); | |||
if (perms != null) | |||
{ | |||
channel.PermissionOverwrites.Where(x => x.TargetType != idType || x.TargetId != userOrRoleId).ToArray(); | |||
if (idType == PermissionTarget.Role) | |||
channel.InvalidatePermissionsCache(); | |||
else if (idType == PermissionTarget.Member) | |||
channel.InvalidatePermissionsCache(userOrRoleId); | |||
} | |||
} | |||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||
} | |||
//Profile | |||
public Task<EditProfileResponse> EditProfile(string currentPassword = "", | |||
string username = null, string email = null, string password = null, | |||
AvatarImageType avatarType = AvatarImageType.Png, byte[] avatar = null) | |||
{ | |||
if (currentPassword == null) throw new ArgumentNullException(nameof(currentPassword)); | |||
return _api.EditProfile(currentPassword: currentPassword, username: username ?? _currentUser?.Name, email: email ?? _currentUser?.Email, password: password, | |||
avatarType: avatarType, avatar: avatar); | |||
} | |||
public Task SetStatus(string status) | |||
{ | |||
switch (status) | |||
{ | |||
case UserStatus.Online: | |||
case UserStatus.Away: | |||
_status = status; | |||
break; | |||
default: | |||
throw new ArgumentException($"Invalid status, must be {UserStatus.Online} or {UserStatus.Away}"); | |||
} | |||
return SendStatus(); | |||
} | |||
public Task SetGame(int? gameId) | |||
{ | |||
_gameId = gameId; | |||
return SendStatus(); | |||
} | |||
private Task SendStatus() | |||
{ | |||
_dataSocket.SendStatus(_status == UserStatus.Away ? EpochTime.GetMilliseconds() - (10 * 60 * 1000) : (ulong?)null, _gameId); | |||
return TaskHelper.CompletedTask; | |||
} | |||
//Roles | |||
/// <summary> Note: due to current API limitations, the created role cannot be returned. </summary> | |||
public Task<Role> CreateRole(Server server, string name) | |||
=> CreateRole(server?.Id, name); | |||
/// <summary> Note: due to current API limitations, the created role cannot be returned. </summary> | |||
public async Task<Role> CreateRole(string serverId, string name) | |||
{ | |||
CheckReady(); | |||
if (serverId == null) throw new NullReferenceException(nameof(serverId)); | |||
var response = await _api.CreateRole(serverId).ConfigureAwait(false); | |||
var role = _roles.GetOrAdd(response.Id, serverId, false); | |||
role.Update(response); | |||
await EditRole(role, name: name); | |||
return role; | |||
} | |||
public Task EditRole(Role role, string name = null, PackedServerPermissions permissions = null, PackedColor color = null, bool? hoist = null, int? position = null) | |||
=> EditRole(role.ServerId, role.Id, name: name, permissions: permissions, color: color, hoist: hoist, position: position); | |||
public async Task EditRole(string serverId, string roleId, string name = null, PackedServerPermissions permissions = null, PackedColor color = null, bool? hoist = null, int? position = null) | |||
{ | |||
CheckReady(); | |||
if (serverId == null) throw new NullReferenceException(nameof(serverId)); | |||
if (roleId == null) throw new NullReferenceException(nameof(roleId)); | |||
var response = await _api.EditRole(serverId, roleId, name: name, | |||
permissions: permissions?.RawValue, color: color?.RawValue, hoist: hoist); | |||
var role = _roles[response.Id]; | |||
if (role != null) | |||
role.Update(response); | |||
if (position != null) | |||
{ | |||
int oldPos = role.Position; | |||
int newPos = position.Value; | |||
int minPos; | |||
Role[] roles = role.Server.Roles.OrderBy(x => x.Position).ToArray(); | |||
if (oldPos < newPos) //Moving Down | |||
{ | |||
minPos = oldPos; | |||
for (int i = oldPos; i < newPos; i++) | |||
roles[i] = roles[i + 1]; | |||
roles[newPos] = role; | |||
} | |||
else //(oldPos > newPos) Moving Up | |||
{ | |||
minPos = newPos; | |||
for (int i = oldPos; i > newPos; i--) | |||
roles[i] = roles[i - 1]; | |||
roles[newPos] = role; | |||
} | |||
await _api.ReorderRoles(role.ServerId, roles.Skip(minPos).Select(x => x.Id), minPos); | |||
} | |||
} | |||
public Task DeleteRole(Role role) | |||
=> DeleteRole(role?.ServerId, role?.Id); | |||
public Task DeleteRole(string serverId, string roleId) | |||
{ | |||
CheckReady(); | |||
if (serverId == null) throw new NullReferenceException(nameof(serverId)); | |||
if (roleId == null) throw new NullReferenceException(nameof(roleId)); | |||
return _api.DeleteRole(serverId, roleId); | |||
} | |||
public Task ReorderRoles(Server server, IEnumerable<object> roles, int startPos = 0) | |||
=> ReorderChannels(server.Id, roles, startPos); | |||
public Task ReorderRoles(string serverId, IEnumerable<object> roles, int startPos = 0) | |||
{ | |||
if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
if (roles == null) throw new ArgumentNullException(nameof(roles)); | |||
if (startPos < 0) throw new ArgumentOutOfRangeException(nameof(startPos), "startPos must be a positive integer."); | |||
var roleIds = roles.Select(x => | |||
{ | |||
if (x is string) | |||
return x as string; | |||
else if (x is Role) | |||
return (x as Role).Id; | |||
else | |||
throw new ArgumentException("Channels must be a collection of string or Role.", nameof(roles)); | |||
}); | |||
return _api.ReorderRoles(serverId, roleIds, startPos); | |||
} | |||
//Servers | |||
/// <summary> Creates a new server with the provided name and region (see Regions). </summary> | |||
public async Task<Server> CreateServer(string name, string region) | |||
{ | |||
CheckReady(); | |||
if (name == null) throw new ArgumentNullException(nameof(name)); | |||
if (region == null) throw new ArgumentNullException(nameof(region)); | |||
var response = await _api.CreateServer(name, region).ConfigureAwait(false); | |||
var server = _servers.GetOrAdd(response.Id); | |||
server.Update(response); | |||
return server; | |||
} | |||
/// <summary> Edits the provided server, changing only non-null attributes. </summary> | |||
public Task EditServer(Server server) | |||
=> EditServer(server?.Id); | |||
/// <summary> Edits the provided server, changing only non-null attributes. </summary> | |||
public async Task EditServer(string serverId, string name = null, string region = null) | |||
{ | |||
CheckReady(); | |||
if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
var response = await _api.EditServer(serverId, name: name, region: region); | |||
var server = _servers[response.Id]; | |||
if (server != null) | |||
server.Update(response); | |||
} | |||
/// <summary> Leaves the provided server, destroying it if you are the owner. </summary> | |||
public Task<Server> LeaveServer(Server server) | |||
=> LeaveServer(server?.Id); | |||
/// <summary> Leaves the provided server, destroying it if you are the owner. </summary> | |||
public async Task<Server> LeaveServer(string serverId) | |||
{ | |||
CheckReady(); | |||
if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
try { await _api.LeaveServer(serverId).ConfigureAwait(false); } | |||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||
return _servers.TryRemove(serverId); | |||
} | |||
} | |||
} |
@@ -0,0 +1,55 @@ | |||
using System; | |||
using System.Net; | |||
using System.Threading.Tasks; | |||
namespace Discord | |||
{ | |||
public class BanEventArgs : EventArgs | |||
{ | |||
public string UserId { get; } | |||
public Server Server { get; } | |||
internal BanEventArgs(string userId, Server server) | |||
{ | |||
UserId = userId; | |||
Server = server; | |||
} | |||
} | |||
public partial class DiscordClient | |||
{ | |||
public event EventHandler<BanEventArgs> UserBanned; | |||
private void RaiseUserBanned(string userId, Server server) | |||
{ | |||
if (UserBanned != null) | |||
RaiseEvent(nameof(UserBanned), () => UserBanned(this, new BanEventArgs(userId, server))); | |||
} | |||
public event EventHandler<BanEventArgs> UserUnbanned; | |||
private void RaiseUserUnbanned(string userId, Server server) | |||
{ | |||
if (UserUnbanned != null) | |||
RaiseEvent(nameof(UserUnbanned), () => UserUnbanned(this, new BanEventArgs(userId, server))); | |||
} | |||
/// <summary> Bans a user from the provided server. </summary> | |||
public Task Ban(User user) | |||
{ | |||
if (user == null) throw new ArgumentNullException(nameof(user)); | |||
if (user.Server == null) throw new ArgumentException("Unable to ban a user in a private chat."); | |||
CheckReady(); | |||
return _api.Ban(user.Server.Id, user.Id); | |||
} | |||
/// <summary> Unbans a user from the provided server. </summary> | |||
public async Task Unban(Server server, string userId) | |||
{ | |||
if (server == null) throw new ArgumentNullException(nameof(server)); | |||
if (userId == null) throw new ArgumentNullException(nameof(userId)); | |||
CheckReady(); | |||
try { await _api.Unban(server.Id, userId).ConfigureAwait(false); } | |||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||
} | |||
} | |||
} |
@@ -1,59 +0,0 @@ | |||
using System.Collections.Generic; | |||
namespace Discord | |||
{ | |||
public partial class DiscordClient | |||
{ | |||
/// <summary> Returns the channel with the specified id, or null if none was found. </summary> | |||
public Channel GetChannel(string id) => _channels[id]; | |||
/// <summary> Returns all channels with the specified server and name. </summary> | |||
/// <remarks> Name formats supported: Name and #Name. Search is case-insensitive. </remarks> | |||
public IEnumerable<Channel> FindChannels(Server server, string name, string type = null) => _channels.Find(server?.Id, name, type); | |||
/// <summary> Returns all channels with the specified server and name. </summary> | |||
/// <remarks> Name formats supported: Name and #Name. Search is case-insensitive. </remarks> | |||
public IEnumerable<Channel> FindChannels(string serverId, string name, string type = null) => _channels.Find(serverId, name, type); | |||
/// <summary> Returns the user with the specified id, along with their server-specific data, or null if none was found. </summary> | |||
public Member GetMember(string serverId, User user) => _members[user?.Id, serverId]; | |||
/// <summary> Returns the user with the specified id, along with their server-specific data, or null if none was found. </summary> | |||
public Member GetMember(Server server, User user) => _members[user?.Id, server?.Id]; | |||
/// <summary> Returns the user with the specified id, along with their server-specific data, or null if none was found. </summary> | |||
public Member GetMember(Server server, string userId) => _members[userId, server?.Id]; | |||
/// <summary> Returns the user with the specified id, along with their server-specific data, or null if none was found. </summary> | |||
public Member GetMember(string serverId, string userId) => _members[userId, serverId]; | |||
/// <summary> Returns all users in with the specified server and name, along with their server-specific data. </summary> | |||
/// <remarks> Name formats supported: Name and @Name. Search is case-insensitive.</remarks> | |||
public IEnumerable<Member> FindMembers(Server server, string name) => _members.Find(server, name); | |||
/// <summary> Returns all users in with the specified server and name, along with their server-specific data. </summary> | |||
/// <remarks> Name formats supported: Name and @Name. Search is case-insensitive.</remarks> | |||
public IEnumerable<Member> FindMembers(string serverId, string name) => _members.Find(_servers[serverId], name); | |||
/// <summary> Returns the message with the specified id, or null if none was found. </summary> | |||
public Message GetMessage(string id) => _messages[id]; | |||
/// <summary> Returns the role with the specified id, or null if none was found. </summary> | |||
public Role GetRole(string id) => _roles[id]; | |||
/// <summary> Returns all roles with the specified server and name. </summary> | |||
/// <remarks> Name formats supported: Name and @Name. Search is case-insensitive. </remarks> | |||
public IEnumerable<Role> FindRoles(Server server, string name) => _roles.Find(server?.Id, name); | |||
/// <summary> Returns all roles with the specified server and name. </summary> | |||
/// <remarks> Name formats supported: Name and @Name. Search is case-insensitive. </remarks> | |||
public IEnumerable<Role> FindRoles(string serverId, string name) => _roles.Find(serverId, name); | |||
/// <summary> Returns the server with the specified id, or null if none was found. </summary> | |||
public Server GetServer(string id) => _servers[id]; | |||
/// <summary> Returns all servers with the specified name. </summary> | |||
/// <remarks> Search is case-insensitive. </remarks> | |||
public IEnumerable<Server> FindServers(string name) => _servers.Find(name); | |||
/// <summary> Returns the user with the specified id, or null if none was found. </summary> | |||
public User GetUser(string id) => _users[id]; | |||
/// <summary> Returns the user with the specified name and discriminator, or null if none was found. </summary> | |||
/// <remarks> Name formats supported: Name and @Name. Search is case-insensitive. </remarks> | |||
public User GetUser(string name, string discriminator) => _members[name, discriminator]?.User; | |||
/// <summary> Returns all users with the specified name across all servers. </summary> | |||
/// <remarks> Name formats supported: Name and @Name. Search is case-insensitive. </remarks> | |||
public IEnumerable<User> FindUsers(string name) => _users.Find(name); | |||
} | |||
} |
@@ -0,0 +1,175 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Net; | |||
using System.Threading.Tasks; | |||
namespace Discord | |||
{ | |||
internal sealed class Channels : AsyncCollection<Channel> | |||
{ | |||
public Channels(DiscordClient client, object writerLock) | |||
: base(client, writerLock) { } | |||
public Channel GetOrAdd(string id, string serverId, string recipientId = null) | |||
=> GetOrAdd(id, () => new Channel(_client, id, serverId, recipientId)); | |||
} | |||
public class ChannelEventArgs : EventArgs | |||
{ | |||
public Channel Channel { get; } | |||
public Server Server => Channel.Server; | |||
internal ChannelEventArgs(Channel channel) { Channel = channel; } | |||
} | |||
public partial class DiscordClient | |||
{ | |||
internal Channels Channels => _channels; | |||
private readonly Channels _channels; | |||
public event EventHandler<ChannelEventArgs> ChannelCreated; | |||
private void RaiseChannelCreated(Channel channel) | |||
{ | |||
if (ChannelCreated != null) | |||
RaiseEvent(nameof(ChannelCreated), () => ChannelCreated(this, new ChannelEventArgs(channel))); | |||
} | |||
public event EventHandler<ChannelEventArgs> ChannelDestroyed; | |||
private void RaiseChannelDestroyed(Channel channel) | |||
{ | |||
if (ChannelDestroyed != null) | |||
RaiseEvent(nameof(ChannelDestroyed), () => ChannelDestroyed(this, new ChannelEventArgs(channel))); | |||
} | |||
public event EventHandler<ChannelEventArgs> ChannelUpdated; | |||
private void RaiseChannelUpdated(Channel channel) | |||
{ | |||
if (ChannelUpdated != null) | |||
RaiseEvent(nameof(ChannelUpdated), () => ChannelUpdated(this, new ChannelEventArgs(channel))); | |||
} | |||
/// <summary> Returns the channel with the specified id, or null if none was found. </summary> | |||
public Channel GetChannel(string id) | |||
{ | |||
if (id == null) throw new ArgumentNullException(nameof(id)); | |||
CheckReady(); | |||
return _channels[id]; | |||
} | |||
/// <summary> Returns all channels with the specified server and name. </summary> | |||
/// <remarks> Name formats supported: Name and #Name. Search is case-insensitive. </remarks> | |||
public IEnumerable<Channel> FindChannels(Server server, string name, ChannelType type = null, bool exactMatch = false) | |||
{ | |||
if (server == null) throw new ArgumentNullException(nameof(server)); | |||
CheckReady(); | |||
IEnumerable<Channel> result; | |||
if (!exactMatch && name.StartsWith("#")) | |||
{ | |||
string name2 = name.Substring(1); | |||
result = _channels.Where(x => x.Server == server && | |||
string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) || | |||
string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); | |||
} | |||
else | |||
{ | |||
result = _channels.Where(x => x.Server == server && | |||
string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); | |||
} | |||
if (type != (string)null) | |||
result = result.Where(x => x.Type == type); | |||
return result; | |||
} | |||
/// <summary> Creates a new channel with the provided name and type. </summary> | |||
public async Task<Channel> CreateChannel(Server server, string name, ChannelType type) | |||
{ | |||
if (server == null) throw new ArgumentNullException(nameof(server)); | |||
if (name == null) throw new ArgumentNullException(nameof(name)); | |||
if (type == (string)null) throw new ArgumentNullException(nameof(type)); | |||
CheckReady(); | |||
var response = await _api.CreateChannel(server.Id, name, type.Value).ConfigureAwait(false); | |||
var channel = _channels.GetOrAdd(response.Id, response.GuildId, response.Recipient?.Id); | |||
channel.Update(response); | |||
return channel; | |||
} | |||
/// <summary> Returns the private channel with the provided user, creating one if it does not currently exist. </summary> | |||
public async Task<Channel> CreatePMChannel(User user) | |||
{ | |||
if (user == null) throw new ArgumentNullException(nameof(user)); | |||
CheckReady(); | |||
Channel channel = null; | |||
if (user != null) | |||
channel = user.GlobalUser.PrivateChannel; | |||
if (channel == null) | |||
{ | |||
var response = await _api.CreatePMChannel(_userId, user.Id).ConfigureAwait(false); | |||
var recipient = _users.GetOrAdd(response.Recipient?.Id, null); | |||
recipient.Update(response.Recipient); | |||
channel = _channels.GetOrAdd(response.Id, response.GuildId, response.Recipient?.Id); | |||
channel.Update(response); | |||
} | |||
return channel; | |||
} | |||
/// <summary> Edits the provided channel, changing only non-null attributes. </summary> | |||
public async Task EditChannel(Channel channel, string name = null, string topic = null, int? position = null) | |||
{ | |||
if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||
CheckReady(); | |||
await _api.EditChannel(channel.Id, name: name, topic: topic).ConfigureAwait(false); | |||
if (position != null) | |||
{ | |||
int oldPos = channel.Position; | |||
int newPos = position.Value; | |||
int minPos; | |||
Channel[] channels = channel.Server.Channels.OrderBy(x => x.Position).ToArray(); | |||
if (oldPos < newPos) //Moving Down | |||
{ | |||
minPos = oldPos; | |||
for (int i = oldPos; i < newPos; i++) | |||
channels[i] = channels[i + 1]; | |||
channels[newPos] = channel; | |||
} | |||
else //(oldPos > newPos) Moving Up | |||
{ | |||
minPos = newPos; | |||
for (int i = oldPos; i > newPos; i--) | |||
channels[i] = channels[i - 1]; | |||
channels[newPos] = channel; | |||
} | |||
Channel after = minPos > 0 ? channels.Skip(minPos - 1).FirstOrDefault() : null; | |||
await ReorderChannels(channel.Server, channels.Skip(minPos), after).ConfigureAwait(false); | |||
} | |||
} | |||
/// <summary> Reorders the provided channels in the server's channel list and places them after a certain channel. </summary> | |||
public Task ReorderChannels(Server server, IEnumerable<Channel> channels, Channel after = null) | |||
{ | |||
if (server == null) throw new ArgumentNullException(nameof(server)); | |||
if (channels == null) throw new ArgumentNullException(nameof(channels)); | |||
CheckReady(); | |||
return _api.ReorderChannels(server.Id, channels.Select(x => x.Id), after.Position); | |||
} | |||
/// <summary> Destroys the provided channel. </summary> | |||
public async Task DestroyChannel(Channel channel) | |||
{ | |||
if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||
CheckReady(); | |||
try { await _api.DestroyChannel(channel.Id).ConfigureAwait(false); } | |||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||
//return _channels.TryRemove(channel.Id); | |||
} | |||
} | |||
} |
@@ -1,278 +0,0 @@ | |||
using System; | |||
namespace Discord | |||
{ | |||
public sealed class ServerEventArgs : EventArgs | |||
{ | |||
public Server Server { get; } | |||
public string ServerId => Server.Id; | |||
internal ServerEventArgs(Server server) { Server = server; } | |||
} | |||
public sealed class ChannelEventArgs : EventArgs | |||
{ | |||
public Channel Channel { get; } | |||
public string ChannelId => Channel.Id; | |||
public Server Server => Channel.Server; | |||
public string ServerId => Channel.ServerId; | |||
internal ChannelEventArgs(Channel channel) { Channel = channel; } | |||
} | |||
public sealed class UserEventArgs : EventArgs | |||
{ | |||
public User User { get; } | |||
public string UserId => User.Id; | |||
internal UserEventArgs(User user) { User = user; } | |||
} | |||
public sealed class MessageEventArgs : EventArgs | |||
{ | |||
public Message Message { get; } | |||
public string MessageId => Message.Id; | |||
public Member Member => Message.Member; | |||
public Channel Channel => Message.Channel; | |||
public string ChannelId => Message.ChannelId; | |||
public Server Server => Message.Server; | |||
public string ServerId => Message.ServerId; | |||
public User User => Member.User; | |||
public string UserId => Message.UserId; | |||
internal MessageEventArgs(Message msg) { Message = msg; } | |||
} | |||
public sealed class RoleEventArgs : EventArgs | |||
{ | |||
public Role Role { get; } | |||
public string RoleId => Role.Id; | |||
public Server Server => Role.Server; | |||
public string ServerId => Role.ServerId; | |||
internal RoleEventArgs(Role role) { Role = role; } | |||
} | |||
public sealed class BanEventArgs : EventArgs | |||
{ | |||
public User User { get; } | |||
public string UserId { get; } | |||
public Server Server { get; } | |||
public string ServerId => Server.Id; | |||
internal BanEventArgs(User user, string userId, Server server) | |||
{ | |||
User = user; | |||
UserId = userId; | |||
Server = server; | |||
} | |||
} | |||
public sealed class MemberEventArgs : EventArgs | |||
{ | |||
public Member Member { get; } | |||
public User User => Member.User; | |||
public string UserId => Member.UserId; | |||
public Server Server => Member.Server; | |||
public string ServerId => Member.ServerId; | |||
internal MemberEventArgs(Member member) { Member = member; } | |||
} | |||
public sealed class UserTypingEventArgs : EventArgs | |||
{ | |||
public Channel Channel { get; } | |||
public string ChannelId => Channel.Id; | |||
public Server Server => Channel.Server; | |||
public string ServerId => Channel.ServerId; | |||
public User User { get; } | |||
public string UserId => User.Id; | |||
internal UserTypingEventArgs(User user, Channel channel) | |||
{ | |||
User = user; | |||
Channel = channel; | |||
} | |||
} | |||
public sealed class UserIsSpeakingEventArgs : EventArgs | |||
{ | |||
public Channel Channel => Member.VoiceChannel; | |||
public string ChannelId => Member.VoiceChannelId; | |||
public Server Server => Member.Server; | |||
public string ServerId => Member.ServerId; | |||
public User User => Member.User; | |||
public string UserId => Member.UserId; | |||
public Member Member { get; } | |||
public bool IsSpeaking { get; } | |||
internal UserIsSpeakingEventArgs(Member member, bool isSpeaking) | |||
{ | |||
Member = member; | |||
IsSpeaking = isSpeaking; | |||
} | |||
} | |||
public partial class DiscordClient | |||
{ | |||
//Server | |||
public event EventHandler<ServerEventArgs> ServerCreated; | |||
private void RaiseServerCreated(Server server) | |||
{ | |||
if (ServerCreated != null) | |||
RaiseEvent(nameof(ServerCreated), () => ServerCreated(this, new ServerEventArgs(server))); | |||
} | |||
public event EventHandler<ServerEventArgs> ServerDestroyed; | |||
private void RaiseServerDestroyed(Server server) | |||
{ | |||
if (ServerDestroyed != null) | |||
RaiseEvent(nameof(ServerDestroyed), () => ServerDestroyed(this, new ServerEventArgs(server))); | |||
} | |||
public event EventHandler<ServerEventArgs> ServerUpdated; | |||
private void RaiseServerUpdated(Server server) | |||
{ | |||
if (ServerUpdated != null) | |||
RaiseEvent(nameof(ServerUpdated), () => ServerUpdated(this, new ServerEventArgs(server))); | |||
} | |||
public event EventHandler<ServerEventArgs> ServerUnavailable; | |||
private void RaiseServerUnavailable(Server server) | |||
{ | |||
if (ServerUnavailable != null) | |||
RaiseEvent(nameof(ServerUnavailable), () => ServerUnavailable(this, new ServerEventArgs(server))); | |||
} | |||
public event EventHandler<ServerEventArgs> ServerAvailable; | |||
private void RaiseServerAvailable(Server server) | |||
{ | |||
if (ServerAvailable != null) | |||
RaiseEvent(nameof(ServerAvailable), () => ServerAvailable(this, new ServerEventArgs(server))); | |||
} | |||
//Channel | |||
public event EventHandler<ChannelEventArgs> ChannelCreated; | |||
private void RaiseChannelCreated(Channel channel) | |||
{ | |||
if (ChannelCreated != null) | |||
RaiseEvent(nameof(ChannelCreated), () => ChannelCreated(this, new ChannelEventArgs(channel))); | |||
} | |||
public event EventHandler<ChannelEventArgs> ChannelDestroyed; | |||
private void RaiseChannelDestroyed(Channel channel) | |||
{ | |||
if (ChannelDestroyed != null) | |||
RaiseEvent(nameof(ChannelDestroyed), () => ChannelDestroyed(this, new ChannelEventArgs(channel))); | |||
} | |||
public event EventHandler<ChannelEventArgs> ChannelUpdated; | |||
private void RaiseChannelUpdated(Channel channel) | |||
{ | |||
if (ChannelUpdated != null) | |||
RaiseEvent(nameof(ChannelUpdated), () => ChannelUpdated(this, new ChannelEventArgs(channel))); | |||
} | |||
//Message | |||
public event EventHandler<MessageEventArgs> MessageCreated; | |||
private void RaiseMessageCreated(Message msg) | |||
{ | |||
if (MessageCreated != null) | |||
RaiseEvent(nameof(MessageCreated), () => MessageCreated(this, new MessageEventArgs(msg))); | |||
} | |||
public event EventHandler<MessageEventArgs> MessageDeleted; | |||
private void RaiseMessageDeleted(Message msg) | |||
{ | |||
if (MessageDeleted != null) | |||
RaiseEvent(nameof(MessageDeleted), () => MessageDeleted(this, new MessageEventArgs(msg))); | |||
} | |||
public event EventHandler<MessageEventArgs> MessageUpdated; | |||
private void RaiseMessageUpdated(Message msg) | |||
{ | |||
if (MessageUpdated != null) | |||
RaiseEvent(nameof(MessageUpdated), () => MessageUpdated(this, new MessageEventArgs(msg))); | |||
} | |||
public event EventHandler<MessageEventArgs> MessageReadRemotely; | |||
private void RaiseMessageReadRemotely(Message msg) | |||
{ | |||
if (MessageReadRemotely != null) | |||
RaiseEvent(nameof(MessageReadRemotely), () => MessageReadRemotely(this, new MessageEventArgs(msg))); | |||
} | |||
public event EventHandler<MessageEventArgs> MessageSent; | |||
private void RaiseMessageSent(Message msg) | |||
{ | |||
if (MessageSent != null) | |||
RaiseEvent(nameof(MessageSent), () => MessageSent(this, new MessageEventArgs(msg))); | |||
} | |||
//Role | |||
public event EventHandler<RoleEventArgs> RoleCreated; | |||
private void RaiseRoleCreated(Role role) | |||
{ | |||
if (RoleCreated != null) | |||
RaiseEvent(nameof(RoleCreated), () => RoleCreated(this, new RoleEventArgs(role))); | |||
} | |||
public event EventHandler<RoleEventArgs> RoleUpdated; | |||
private void RaiseRoleDeleted(Role role) | |||
{ | |||
if (RoleDeleted != null) | |||
RaiseEvent(nameof(RoleDeleted), () => RoleDeleted(this, new RoleEventArgs(role))); | |||
} | |||
public event EventHandler<RoleEventArgs> RoleDeleted; | |||
private void RaiseRoleUpdated(Role role) | |||
{ | |||
if (RoleUpdated != null) | |||
RaiseEvent(nameof(RoleUpdated), () => RoleUpdated(this, new RoleEventArgs(role))); | |||
} | |||
//Ban | |||
public event EventHandler<BanEventArgs> BanAdded; | |||
private void RaiseBanAdded(string userId, Server server) | |||
{ | |||
if (BanAdded != null) | |||
RaiseEvent(nameof(BanAdded), () => BanAdded(this, new BanEventArgs(_users[userId], userId, server))); | |||
} | |||
public event EventHandler<BanEventArgs> BanRemoved; | |||
private void RaiseBanRemoved(string userId, Server server) | |||
{ | |||
if (BanRemoved != null) | |||
RaiseEvent(nameof(BanRemoved), () => BanRemoved(this, new BanEventArgs(_users[userId], userId, server))); | |||
} | |||
//User | |||
public event EventHandler<MemberEventArgs> UserAdded; | |||
private void RaiseUserAdded(Member member) | |||
{ | |||
if (UserAdded != null) | |||
RaiseEvent(nameof(UserAdded), () => UserAdded(this, new MemberEventArgs(member))); | |||
} | |||
public event EventHandler<MemberEventArgs> UserRemoved; | |||
private void RaiseUserRemoved(Member member) | |||
{ | |||
if (UserRemoved != null) | |||
RaiseEvent(nameof(UserRemoved), () => UserRemoved(this, new MemberEventArgs(member))); | |||
} | |||
public event EventHandler<UserEventArgs> UserUpdated; | |||
private void RaiseUserUpdated(User user) | |||
{ | |||
if (UserUpdated != null) | |||
RaiseEvent(nameof(UserUpdated), () => UserUpdated(this, new UserEventArgs(user))); | |||
} | |||
public event EventHandler<MemberEventArgs> MemberUpdated; | |||
private void RaiseMemberUpdated(Member member) | |||
{ | |||
if (MemberUpdated != null) | |||
RaiseEvent(nameof(MemberUpdated), () => MemberUpdated(this, new MemberEventArgs(member))); | |||
} | |||
public event EventHandler<MemberEventArgs> UserPresenceUpdated; | |||
private void RaiseUserPresenceUpdated(Member member) | |||
{ | |||
if (UserPresenceUpdated != null) | |||
RaiseEvent(nameof(UserPresenceUpdated), () => UserPresenceUpdated(this, new MemberEventArgs(member))); | |||
} | |||
public event EventHandler<MemberEventArgs> UserVoiceStateUpdated; | |||
private void RaiseUserVoiceStateUpdated(Member member) | |||
{ | |||
if (UserVoiceStateUpdated != null) | |||
RaiseEvent(nameof(UserVoiceStateUpdated), () => UserVoiceStateUpdated(this, new MemberEventArgs(member))); | |||
} | |||
public event EventHandler<UserTypingEventArgs> UserIsTyping; | |||
private void RaiseUserIsTyping(User user, Channel channel) | |||
{ | |||
if (UserIsTyping != null) | |||
RaiseEvent(nameof(UserIsTyping), () => UserIsTyping(this, new UserTypingEventArgs(user, channel))); | |||
} | |||
public event EventHandler<UserIsSpeakingEventArgs> UserIsSpeaking; | |||
private void RaiseUserIsSpeaking(Member member, bool isSpeaking) | |||
{ | |||
if (UserIsSpeaking != null) | |||
RaiseEvent(nameof(UserIsSpeaking), () => UserIsSpeaking(this, new UserIsSpeakingEventArgs(member, isSpeaking))); | |||
} | |||
} | |||
} |
@@ -0,0 +1,82 @@ | |||
using System; | |||
using System.Net; | |||
using System.Threading.Tasks; | |||
namespace Discord | |||
{ | |||
public partial class DiscordClient | |||
{ | |||
/// <summary> Gets more info about the provided invite code. </summary> | |||
/// <remarks> Supported formats: inviteCode, xkcdCode, https://discord.gg/inviteCode, https://discord.gg/xkcdCode </remarks> | |||
public async Task<Invite> GetInvite(string inviteIdOrXkcd) | |||
{ | |||
//This doesn't work well if it's an invite to a different server! | |||
if (inviteIdOrXkcd == null) throw new ArgumentNullException(nameof(inviteIdOrXkcd)); | |||
CheckReady(); | |||
//Remove trailing slash | |||
if (inviteIdOrXkcd.Length > 0 && inviteIdOrXkcd[inviteIdOrXkcd.Length - 1] == '/') | |||
inviteIdOrXkcd = inviteIdOrXkcd.Substring(0, inviteIdOrXkcd.Length - 1); | |||
//Remove leading URL | |||
int index = inviteIdOrXkcd.LastIndexOf('/'); | |||
if (index >= 0) | |||
inviteIdOrXkcd = inviteIdOrXkcd.Substring(index + 1); | |||
var response = await _api.GetInvite(inviteIdOrXkcd).ConfigureAwait(false); | |||
var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id, response.Inviter?.Id, response.Channel?.Id); | |||
invite.Cache(); //Builds references | |||
return invite; | |||
} | |||
/// <summary> Creates a new invite to the default channel of the provided server. </summary> | |||
/// <param name="maxAge"> Time (in seconds) until the invite expires. Set to 0 to never expire. </param> | |||
/// <param name="tempMembership"> If true, a user accepting this invite will be kicked from the server after closing their client. </param> | |||
/// <param name="hasXkcd"> If true, creates a human-readable link. Not supported if maxAge is set to 0. </param> | |||
/// <param name="maxUses"> The max amount of times this invite may be used. Set to 0 to have unlimited uses. </param> | |||
public Task<Invite> CreateInvite(Server server, int maxAge = 1800, int maxUses = 0, bool tempMembership = false, bool hasXkcd = false) | |||
{ | |||
if (server == null) throw new ArgumentNullException(nameof(server)); | |||
CheckReady(); | |||
return CreateInvite(server.DefaultChannel, maxAge, maxUses, tempMembership, hasXkcd); | |||
} | |||
/// <summary> Creates a new invite to the provided channel. </summary> | |||
/// <param name="maxAge"> Time (in seconds) until the invite expires. Set to 0 to never expire. </param> | |||
/// <param name="tempMembership"> If true, a user accepting this invite will be kicked from the server after closing their client. </param> | |||
/// <param name="hasXkcd"> If true, creates a human-readable link. Not supported if maxAge is set to 0. </param> | |||
/// <param name="maxUses"> The max amount of times this invite may be used. Set to 0 to have unlimited uses. </param> | |||
public async Task<Invite> CreateInvite(Channel channel, int maxAge = 1800, int maxUses = 0, bool tempMembership = false, bool hasXkcd = false) | |||
{ | |||
if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||
if (maxAge <= 0) throw new ArgumentOutOfRangeException(nameof(maxAge)); | |||
if (maxUses <= 0) throw new ArgumentOutOfRangeException(nameof(maxUses)); | |||
CheckReady(); | |||
var response = await _api.CreateInvite(channel.Id, maxAge: maxAge, maxUses: maxUses, | |||
tempMembership: tempMembership, hasXkcd: hasXkcd).ConfigureAwait(false); | |||
var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id, response.Inviter?.Id, response.Channel?.Id); | |||
invite.Cache(); //Builds references | |||
return invite; | |||
} | |||
/// <summary> Deletes the provided invite. </summary> | |||
public async Task DestroyInvite(Invite invite) | |||
{ | |||
if (invite == null) throw new ArgumentNullException(nameof(invite)); | |||
CheckReady(); | |||
try { await _api.DeleteInvite(invite.Id).ConfigureAwait(false); } | |||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||
} | |||
/// <summary> Accepts the provided invite. </summary> | |||
public Task AcceptInvite(Invite invite) | |||
{ | |||
if (invite == null) throw new ArgumentNullException(nameof(invite)); | |||
CheckReady(); | |||
return _api.AcceptInvite(invite.Id); | |||
} | |||
} | |||
} |
@@ -0,0 +1,281 @@ | |||
using Discord.API; | |||
using Discord.Net; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Net; | |||
using System.Threading.Tasks; | |||
namespace Discord | |||
{ | |||
internal sealed class Messages : AsyncCollection<Message> | |||
{ | |||
private bool _isEnabled; | |||
public Messages(DiscordClient client, object writerLock, bool isEnabled) | |||
: base(client, writerLock) | |||
{ | |||
_isEnabled = isEnabled; | |||
} | |||
public Message GetOrAdd(string id, string channelId, string userId) | |||
{ | |||
if (_isEnabled) | |||
return GetOrAdd(id, () => new Message(_client, id, channelId, userId)); | |||
else | |||
{ | |||
var msg = new Message(_client, id, channelId, userId); | |||
msg.Cache(); //Builds references | |||
return msg; | |||
} | |||
} | |||
} | |||
public class MessageEventArgs : EventArgs | |||
{ | |||
public Message Message { get; } | |||
public User User => Message.User; | |||
public Channel Channel => Message.Channel; | |||
public Server Server => Message.Server; | |||
internal MessageEventArgs(Message msg) { Message = msg; } | |||
} | |||
public partial class DiscordClient | |||
{ | |||
public const int MaxMessageSize = 2000; | |||
public event EventHandler<MessageEventArgs> MessageReceived; | |||
private void RaiseMessageCreated(Message msg) | |||
{ | |||
if (MessageReceived != null) | |||
RaiseEvent(nameof(MessageReceived), () => MessageReceived(this, new MessageEventArgs(msg))); | |||
} | |||
public event EventHandler<MessageEventArgs> MessageSent; | |||
private void RaiseMessageSent(Message msg) | |||
{ | |||
if (MessageSent != null) | |||
RaiseEvent(nameof(MessageSent), () => MessageSent(this, new MessageEventArgs(msg))); | |||
} | |||
public event EventHandler<MessageEventArgs> MessageDeleted; | |||
private void RaiseMessageDeleted(Message msg) | |||
{ | |||
if (MessageDeleted != null) | |||
RaiseEvent(nameof(MessageDeleted), () => MessageDeleted(this, new MessageEventArgs(msg))); | |||
} | |||
public event EventHandler<MessageEventArgs> MessageUpdated; | |||
private void RaiseMessageUpdated(Message msg) | |||
{ | |||
if (MessageUpdated != null) | |||
RaiseEvent(nameof(MessageUpdated), () => MessageUpdated(this, new MessageEventArgs(msg))); | |||
} | |||
public event EventHandler<MessageEventArgs> MessageReadRemotely; | |||
private void RaiseMessageReadRemotely(Message msg) | |||
{ | |||
if (MessageReadRemotely != null) | |||
RaiseEvent(nameof(MessageReadRemotely), () => MessageReadRemotely(this, new MessageEventArgs(msg))); | |||
} | |||
internal Messages Messages => _messages; | |||
private readonly Messages _messages; | |||
/// <summary> Returns the message with the specified id, or null if none was found. </summary> | |||
public Message GetMessage(string id) | |||
{ | |||
if (id == null) throw new ArgumentNullException(nameof(id)); | |||
CheckReady(); | |||
return _messages[id]; | |||
} | |||
/// <summary> Sends a message to the provided channel. To include a mention, see the Mention static helper class. </summary> | |||
public Task<Message> SendMessage(Channel channel, string text) | |||
{ | |||
if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||
if (text == null) throw new ArgumentNullException(nameof(text)); | |||
CheckReady(); | |||
return SendMessage(channel, text, false); | |||
} | |||
/// <summary> Sends a text-to-speech message to the provided channel. To include a mention, see the Mention static helper class. </summary> | |||
public Task<Message> SendTTSMessage(Channel channel, string text) | |||
{ | |||
if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||
if (text == null) throw new ArgumentNullException(nameof(text)); | |||
CheckReady(); | |||
return SendMessage(channel, text, false); | |||
} | |||
/// <summary> Sends a private message to the provided user. </summary> | |||
public async Task<Message> SendPrivateMessage(User user, string text) | |||
{ | |||
if (user == null) throw new ArgumentNullException(nameof(user)); | |||
if (text == null) throw new ArgumentNullException(nameof(text)); | |||
CheckReady(); | |||
var channel = await CreatePMChannel(user).ConfigureAwait(false); | |||
return await SendMessage(channel, text).ConfigureAwait(false); | |||
} | |||
private async Task<Message> SendMessage(Channel channel, string text, bool isTextToSpeech) | |||
{ | |||
Message msg; | |||
var userIds = !channel.IsPrivate ? Mention.GetUserIds(text) : new string[0]; | |||
if (Config.UseMessageQueue) | |||
{ | |||
var nonce = GenerateNonce(); | |||
msg = _messages.GetOrAdd("nonce_" + nonce, channel.Id, _userId); | |||
var currentUser = msg.User; | |||
msg.Update(new MessageInfo | |||
{ | |||
Content = text, | |||
Timestamp = DateTime.UtcNow, | |||
Author = new UserReference { Avatar = currentUser.AvatarId, Discriminator = currentUser.Discriminator, Id = _userId, Username = currentUser.Name }, | |||
ChannelId = channel.Id, | |||
IsTextToSpeech = isTextToSpeech | |||
}); | |||
msg.Mentions = userIds.Select(x => _users[x, channel.Server.Id]).Where(x => x != null).ToArray(); | |||
msg.IsQueued = true; | |||
msg.Nonce = nonce; | |||
_pendingMessages.Enqueue(msg); | |||
} | |||
else | |||
{ | |||
var model = await _api.SendMessage(channel.Id, text, userIds, null, isTextToSpeech).ConfigureAwait(false); | |||
msg = _messages.GetOrAdd(model.Id, channel.Id, model.Author.Id); | |||
msg.Update(model); | |||
RaiseMessageSent(msg); | |||
} | |||
return msg; | |||
} | |||
/// <summary> Sends a file to the provided channel. </summary> | |||
public Task SendFile(Channel channel, string filePath) | |||
{ | |||
if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||
if (filePath == null) throw new ArgumentNullException(nameof(filePath)); | |||
CheckReady(); | |||
return _api.SendFile(channel.Id, filePath); | |||
} | |||
/// <summary> Edits the provided message, changing only non-null attributes. </summary> | |||
/// <remarks> While not required, it is recommended to include a mention reference in the text (see Mention.User). </remarks> | |||
public Task EditMessage(Message message, string text) | |||
{ | |||
if (message == null) throw new ArgumentNullException(nameof(message)); | |||
CheckReady(); | |||
if (text != null && text.Length > MaxMessageSize) | |||
text = text.Substring(0, MaxMessageSize); | |||
return _api.EditMessage(message.Id, message.Channel.Id, text, Mention.GetUserIds(text)); | |||
} | |||
/// <summary> Deletes the provided message. </summary> | |||
public Task DeleteMessage(Message message) | |||
{ | |||
if (message == null) throw new ArgumentNullException(nameof(message)); | |||
CheckReady(); | |||
return DeleteMessageInternal(message); | |||
} | |||
public async Task DeleteMessages(IEnumerable<Message> messages) | |||
{ | |||
if (messages == null) throw new ArgumentNullException(nameof(messages)); | |||
CheckReady(); | |||
foreach (var message in messages) | |||
await DeleteMessageInternal(message).ConfigureAwait(false); | |||
} | |||
private async Task DeleteMessageInternal(Message message) | |||
{ | |||
try | |||
{ | |||
await _api.DeleteMessage(message.Id, message.Channel.Id).ConfigureAwait(false); | |||
_messages.TryRemove(message.Id); | |||
} | |||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||
} | |||
/// <summary> Downloads last count messages from the server, starting at beforeMessageId if it's provided. </summary> | |||
public async Task<Message[]> DownloadMessages(Channel channel, int count, string beforeMessageId = null, bool cache = true) | |||
{ | |||
if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||
if (count < 0) throw new ArgumentNullException(nameof(count)); | |||
CheckReady(); | |||
if (count == 0) return new Message[0]; | |||
if (channel != null && channel.Type == ChannelType.Text) | |||
{ | |||
try | |||
{ | |||
var msgs = await _api.GetMessages(channel.Id, count).ConfigureAwait(false); | |||
return msgs.Select(x => | |||
{ | |||
Message msg = null; | |||
if (cache) | |||
msg = _messages.GetOrAdd(x.Id, x.ChannelId, x.Author.Id); | |||
else | |||
msg = _messages[x.Id] ?? new Message(this, x.Id, x.ChannelId, x.Author.Id); | |||
//msg.Update(x); | |||
if (Config.TrackActivity) | |||
{ | |||
if (!channel.IsPrivate) | |||
{ | |||
var user = msg.User; | |||
if (user != null) | |||
user.UpdateActivity(msg.EditedTimestamp ?? msg.Timestamp); | |||
} | |||
} | |||
return msg; | |||
}) | |||
.ToArray(); | |||
} | |||
catch (HttpException) { } //Bad Permissions? | |||
} | |||
return null; | |||
} | |||
private Task MessageQueueLoop() | |||
{ | |||
var cancelToken = CancelToken; | |||
int interval = Config.MessageQueueInterval; | |||
return Task.Run(async () => | |||
{ | |||
Message msg; | |||
while (!cancelToken.IsCancellationRequested) | |||
{ | |||
while (_pendingMessages.TryDequeue(out msg)) | |||
{ | |||
bool hasFailed = false; | |||
SendMessageResponse response = null; | |||
try | |||
{ | |||
response = await _api.SendMessage(msg.Channel.Id, msg.RawText, msg.Mentions.Select(x => x.Id), msg.Nonce, msg.IsTTS).ConfigureAwait(false); | |||
} | |||
catch (WebException) { break; } | |||
catch (HttpException) { hasFailed = true; } | |||
if (!hasFailed) | |||
{ | |||
_messages.Remap(msg.Id, response.Id); | |||
msg.Id = response.Id; | |||
msg.Update(response); | |||
} | |||
msg.IsQueued = false; | |||
msg.HasFailed = hasFailed; | |||
RaiseMessageSent(msg); | |||
} | |||
await Task.Delay(interval).ConfigureAwait(false); | |||
} | |||
}); | |||
} | |||
private string GenerateNonce() | |||
{ | |||
lock (_rand) | |||
return _rand.Next().ToString(); | |||
} | |||
} | |||
} |
@@ -0,0 +1,71 @@ | |||
using System; | |||
using System.Linq; | |||
using System.Net; | |||
using System.Threading.Tasks; | |||
namespace Discord | |||
{ | |||
public partial class DiscordClient | |||
{ | |||
public Task SetChannelUserPermissions(Channel channel, User user, ChannelPermissions allow = null, ChannelPermissions deny = null) | |||
{ | |||
if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||
if (user == null) throw new ArgumentNullException(nameof(user)); | |||
CheckReady(); | |||
return SetChannelPermissions(channel, user?.Id, PermissionTarget.User, allow, deny); | |||
} | |||
public Task SetChannelUserPermissions(Channel channel, User user, DualChannelPermissions permissions = null) | |||
{ | |||
if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||
if (user == null) throw new ArgumentNullException(nameof(user)); | |||
CheckReady(); | |||
return SetChannelPermissions(channel, user?.Id, PermissionTarget.User, permissions?.Allow, permissions?.Deny); | |||
} | |||
public Task SetChannelRolePermissions(Channel channel, Role role, ChannelPermissions allow = null, ChannelPermissions deny = null) | |||
{ | |||
if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||
if (role == null) throw new ArgumentNullException(nameof(role)); | |||
CheckReady(); | |||
return SetChannelPermissions(channel, role?.Id, PermissionTarget.Role, allow, deny); | |||
} | |||
public Task SetChannelRolePermissions(Channel channel, Role role, DualChannelPermissions permissions = null) | |||
{ | |||
if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||
if (role == null) throw new ArgumentNullException(nameof(role)); | |||
CheckReady(); | |||
return SetChannelPermissions(channel, role?.Id, PermissionTarget.Role, permissions?.Allow, permissions?.Deny); | |||
} | |||
private Task SetChannelPermissions(Channel channel, string targetId, PermissionTarget targetType, ChannelPermissions allow = null, ChannelPermissions deny = null) | |||
=> _api.SetChannelPermissions(channel.Id, targetId, targetType.Value, allow?.RawValue ?? 0, deny?.RawValue ?? 0); | |||
public Task RemoveChannelUserPermissions(Channel channel, User user) | |||
{ | |||
if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||
if (user == null) throw new ArgumentNullException(nameof(user)); | |||
CheckReady(); | |||
return RemoveChannelPermissions(channel, user?.Id, PermissionTarget.User); | |||
} | |||
public Task RemoveChannelRolePermissions(Channel channel, Role role) | |||
{ | |||
if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||
if (role == null) throw new ArgumentNullException(nameof(role)); | |||
CheckReady(); | |||
return RemoveChannelPermissions(channel, role?.Id, PermissionTarget.Role); | |||
} | |||
private async Task RemoveChannelPermissions(Channel channel, string userOrRoleId, PermissionTarget targetType) | |||
{ | |||
try | |||
{ | |||
var perms = channel.PermissionOverwrites.Where(x => x.TargetType != targetType || x.TargetId != userOrRoleId).FirstOrDefault(); | |||
await _api.DeleteChannelPermissions(channel.Id, userOrRoleId).ConfigureAwait(false); | |||
} | |||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||
} | |||
} | |||
} |
@@ -0,0 +1,152 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Net; | |||
using System.Threading.Tasks; | |||
namespace Discord | |||
{ | |||
internal sealed class Roles : AsyncCollection<Role> | |||
{ | |||
public Roles(DiscordClient client, object writerLock) | |||
: base(client, writerLock) { } | |||
public Role GetOrAdd(string id, string serverId) | |||
=> GetOrAdd(id, () => new Role(_client, id, serverId)); | |||
} | |||
public class RoleEventArgs : EventArgs | |||
{ | |||
public Role Role { get; } | |||
public Server Server => Role.Server; | |||
internal RoleEventArgs(Role role) { Role = role; } | |||
} | |||
public partial class DiscordClient | |||
{ | |||
public event EventHandler<RoleEventArgs> RoleCreated; | |||
private void RaiseRoleCreated(Role role) | |||
{ | |||
if (RoleCreated != null) | |||
RaiseEvent(nameof(RoleCreated), () => RoleCreated(this, new RoleEventArgs(role))); | |||
} | |||
public event EventHandler<RoleEventArgs> RoleUpdated; | |||
private void RaiseRoleDeleted(Role role) | |||
{ | |||
if (RoleDeleted != null) | |||
RaiseEvent(nameof(RoleDeleted), () => RoleDeleted(this, new RoleEventArgs(role))); | |||
} | |||
public event EventHandler<RoleEventArgs> RoleDeleted; | |||
private void RaiseRoleUpdated(Role role) | |||
{ | |||
if (RoleUpdated != null) | |||
RaiseEvent(nameof(RoleUpdated), () => RoleUpdated(this, new RoleEventArgs(role))); | |||
} | |||
internal Roles Roles => _roles; | |||
private readonly Roles _roles; | |||
/// <summary> Returns the role with the specified id, or null if none was found. </summary> | |||
public Role GetRole(string id) | |||
{ | |||
if (id == null) throw new ArgumentNullException(nameof(id)); | |||
CheckReady(); | |||
return _roles[id]; | |||
} | |||
/// <summary> Returns all roles with the specified server and name. </summary> | |||
/// <remarks> Name formats supported: Name and @Name. Search is case-insensitive. </remarks> | |||
public IEnumerable<Role> FindRoles(Server server, string name) | |||
{ | |||
if (server == null) throw new ArgumentNullException(nameof(server)); | |||
if (name == null) throw new ArgumentNullException(nameof(name)); | |||
CheckReady(); | |||
/*if (name.StartsWith("@")) | |||
{ | |||
string name2 = name.Substring(1); | |||
return _roles.Where(x => x.Server.Id == server.Id && | |||
string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) || | |||
string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); | |||
} | |||
else | |||
{*/ | |||
return _roles.Where(x => x.Server.Id == server.Id && | |||
string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); | |||
//} | |||
} | |||
/// <summary> Note: due to current API limitations, the created role cannot be returned. </summary> | |||
public async Task<Role> CreateRole(Server server, string name) | |||
{ | |||
if (server == null) throw new ArgumentNullException(nameof(server)); | |||
if (name == null) throw new ArgumentNullException(nameof(name)); | |||
CheckReady(); | |||
var response = await _api.CreateRole(server.Id).ConfigureAwait(false); | |||
var role = _roles.GetOrAdd(response.Id, server.Id); | |||
await _api.EditRole(server.Id, role.Id, name: name).ConfigureAwait(false); | |||
response.Name = name; | |||
role.Update(response); | |||
return role; | |||
} | |||
public async Task EditRole(Role role, string name = null, ServerPermissions permissions = null, Color color = null, bool? hoist = null, int? position = null) | |||
{ | |||
if (role == null) throw new ArgumentNullException(nameof(role)); | |||
CheckReady(); | |||
//TODO: check this null workaround later, should be fixed on Discord's end soon | |||
var response = await _api.EditRole(role.Server.Id, role.Id, | |||
name: name ?? role.Name, | |||
permissions: permissions?.RawValue ?? role.Permissions.RawValue, | |||
color: color?.RawValue, | |||
hoist: hoist).ConfigureAwait(false); | |||
if (position != null) | |||
{ | |||
int oldPos = role.Position; | |||
int newPos = position.Value; | |||
int minPos; | |||
Role[] roles = role.Server.Roles.OrderBy(x => x.Position).ToArray(); | |||
if (oldPos < newPos) //Moving Down | |||
{ | |||
minPos = oldPos; | |||
for (int i = oldPos; i < newPos; i++) | |||
roles[i] = roles[i + 1]; | |||
roles[newPos] = role; | |||
} | |||
else //(oldPos > newPos) Moving Up | |||
{ | |||
minPos = newPos; | |||
for (int i = oldPos; i > newPos; i--) | |||
roles[i] = roles[i - 1]; | |||
roles[newPos] = role; | |||
} | |||
await _api.ReorderRoles(role.Server.Id, roles.Skip(minPos).Select(x => x.Id), minPos).ConfigureAwait(false); | |||
} | |||
} | |||
public async Task DeleteRole(Role role) | |||
{ | |||
if (role == null) throw new ArgumentNullException(nameof(role)); | |||
CheckReady(); | |||
try { await _api.DeleteRole(role.Server.Id, role.Id); } | |||
catch (HttpException ex) when(ex.StatusCode == HttpStatusCode.NotFound) { } | |||
} | |||
public Task ReorderRoles(Server server, IEnumerable<Role> roles, int startPos = 0) | |||
{ | |||
if (server == null) throw new ArgumentNullException(nameof(server)); | |||
if (roles == null) throw new ArgumentNullException(nameof(roles)); | |||
if (startPos < 0) throw new ArgumentOutOfRangeException(nameof(startPos), "startPos must be a positive integer."); | |||
CheckReady(); | |||
return _api.ReorderRoles(server.Id, roles.Select(x => x.Id), startPos); | |||
} | |||
} | |||
} |
@@ -0,0 +1,117 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Net; | |||
using System.Threading.Tasks; | |||
namespace Discord | |||
{ | |||
internal sealed class Servers : AsyncCollection<Server> | |||
{ | |||
public Servers(DiscordClient client, object writerLock) | |||
: base(client, writerLock) { } | |||
public Server GetOrAdd(string id) | |||
=> GetOrAdd(id, () => new Server(_client, id)); | |||
} | |||
public class ServerEventArgs : EventArgs | |||
{ | |||
public Server Server { get; } | |||
public string ServerId => Server.Id; | |||
internal ServerEventArgs(Server server) { Server = server; } | |||
} | |||
public partial class DiscordClient | |||
{ | |||
public event EventHandler<ServerEventArgs> ServerCreated; | |||
private void RaiseServerCreated(Server server) | |||
{ | |||
if (ServerCreated != null) | |||
RaiseEvent(nameof(ServerCreated), () => ServerCreated(this, new ServerEventArgs(server))); | |||
} | |||
public event EventHandler<ServerEventArgs> ServerDestroyed; | |||
private void RaiseServerDestroyed(Server server) | |||
{ | |||
if (ServerDestroyed != null) | |||
RaiseEvent(nameof(ServerDestroyed), () => ServerDestroyed(this, new ServerEventArgs(server))); | |||
} | |||
public event EventHandler<ServerEventArgs> ServerUpdated; | |||
private void RaiseServerUpdated(Server server) | |||
{ | |||
if (ServerUpdated != null) | |||
RaiseEvent(nameof(ServerUpdated), () => ServerUpdated(this, new ServerEventArgs(server))); | |||
} | |||
public event EventHandler<ServerEventArgs> ServerUnavailable; | |||
private void RaiseServerUnavailable(Server server) | |||
{ | |||
if (ServerUnavailable != null) | |||
RaiseEvent(nameof(ServerUnavailable), () => ServerUnavailable(this, new ServerEventArgs(server))); | |||
} | |||
public event EventHandler<ServerEventArgs> ServerAvailable; | |||
private void RaiseServerAvailable(Server server) | |||
{ | |||
if (ServerAvailable != null) | |||
RaiseEvent(nameof(ServerAvailable), () => ServerAvailable(this, new ServerEventArgs(server))); | |||
} | |||
/// <summary> Returns a collection of all servers this client is a member of. </summary> | |||
public IEnumerable<Server> AllServers => _servers; | |||
internal Servers Servers => _servers; | |||
private readonly Servers _servers; | |||
/// <summary> Returns the server with the specified id, or null if none was found. </summary> | |||
public Server GetServer(string id) | |||
{ | |||
if (id == null) throw new ArgumentNullException(nameof(id)); | |||
CheckReady(); | |||
return _servers[id]; | |||
} | |||
/// <summary> Returns all servers with the specified name. </summary> | |||
/// <remarks> Search is case-insensitive. </remarks> | |||
public IEnumerable<Server> FindServers(string name) | |||
{ | |||
if (name == null) throw new ArgumentNullException(nameof(name)); | |||
CheckReady(); | |||
return _servers.Where(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); | |||
} | |||
/// <summary> Creates a new server with the provided name and region (see Regions). </summary> | |||
public async Task<Server> CreateServer(string name, Region region) | |||
{ | |||
if (name == null) throw new ArgumentNullException(nameof(name)); | |||
if (region == (string)null) throw new ArgumentNullException(nameof(region)); | |||
CheckReady(); | |||
var response = await _api.CreateServer(name, region.Value).ConfigureAwait(false); | |||
var server = _servers.GetOrAdd(response.Id); | |||
server.Update(response); | |||
return server; | |||
} | |||
/// <summary> Edits the provided server, changing only non-null attributes. </summary> | |||
public async Task EditServer(Server server, string name = null, Region region = null, ImageType iconType = ImageType.Png, byte[] icon = null) | |||
{ | |||
if (server == null) throw new ArgumentNullException(nameof(server)); | |||
CheckReady(); | |||
var response = await _api.EditServer(server.Id, name: name ?? server.Name, region: region.Value, iconType: iconType, icon: icon).ConfigureAwait(false); | |||
server.Update(response); | |||
} | |||
/// <summary> Leaves the provided server, destroying it if you are the owner. </summary> | |||
public async Task LeaveServer(Server server) | |||
{ | |||
if (server == null) throw new ArgumentNullException(nameof(server)); | |||
CheckReady(); | |||
try { await _api.LeaveServer(server.Id).ConfigureAwait(false); } | |||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||
//return _servers.TryRemove(server.Id); | |||
} | |||
} | |||
} |
@@ -0,0 +1,211 @@ | |||
using Discord.API; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
namespace Discord | |||
{ | |||
internal sealed class GlobalUsers : AsyncCollection<GlobalUser> | |||
{ | |||
public GlobalUsers(DiscordClient client, object writerLock) | |||
: base(client, writerLock) { } | |||
public GlobalUser GetOrAdd(string id) => GetOrAdd(id, () => new GlobalUser(_client, id)); | |||
} | |||
internal sealed class Users : AsyncCollection<User> | |||
{ | |||
public Users(DiscordClient client, object writerLock) | |||
: base(client, writerLock) | |||
{ } | |||
private string GetKey(string userId, string serverId) | |||
=> User.GetId(userId, serverId); | |||
public User this[string userId, string serverId] | |||
=> this[GetKey(userId, serverId)]; | |||
public User GetOrAdd(string userId, string serverId) | |||
=> GetOrAdd(GetKey(userId, serverId), () => new User(_client, userId, serverId)); | |||
public User TryRemove(string userId, string serverId) | |||
=> TryRemove(GetKey(userId, serverId)); | |||
} | |||
public class UserEventArgs : EventArgs | |||
{ | |||
public User User { get; } | |||
public Server Server => User.Server; | |||
internal UserEventArgs(User user) { User = user; } | |||
} | |||
public class UserChannelEventArgs : UserEventArgs | |||
{ | |||
public Channel Channel { get; } | |||
public string ChannelId => Channel.Id; | |||
internal UserChannelEventArgs(User user, Channel channel) | |||
: base(user) | |||
{ | |||
Channel = channel; | |||
} | |||
} | |||
public class UserIsSpeakingEventArgs : UserChannelEventArgs | |||
{ | |||
public bool IsSpeaking { get; } | |||
internal UserIsSpeakingEventArgs(User user, Channel channel, bool isSpeaking) | |||
: base(user, channel) | |||
{ | |||
IsSpeaking = isSpeaking; | |||
} | |||
} | |||
public partial class DiscordClient | |||
{ | |||
public event EventHandler<UserChannelEventArgs> UserIsTypingUpdated; | |||
private void RaiseUserIsTyping(User user, Channel channel) | |||
{ | |||
if (UserIsTypingUpdated != null) | |||
RaiseEvent(nameof(UserIsTypingUpdated), () => UserIsTypingUpdated(this, new UserChannelEventArgs(user, channel))); | |||
} | |||
public event EventHandler<UserIsSpeakingEventArgs> UserIsSpeakingUpdated; | |||
private void RaiseUserIsSpeaking(User user, Channel channel, bool isSpeaking) | |||
{ | |||
if (UserIsSpeakingUpdated != null) | |||
RaiseEvent(nameof(UserIsSpeakingUpdated), () => UserIsSpeakingUpdated(this, new UserIsSpeakingEventArgs(user, channel, isSpeaking))); | |||
} | |||
public event EventHandler<UserEventArgs> UserAdded; | |||
private void RaiseUserAdded(User user) | |||
{ | |||
if (UserAdded != null) | |||
RaiseEvent(nameof(UserAdded), () => UserAdded(this, new UserEventArgs(user))); | |||
} | |||
public event EventHandler<UserEventArgs> UserRemoved; | |||
private void RaiseUserRemoved(User user) | |||
{ | |||
if (UserRemoved != null) | |||
RaiseEvent(nameof(UserRemoved), () => UserRemoved(this, new UserEventArgs(user))); | |||
} | |||
public event EventHandler<UserEventArgs> UserUpdated; | |||
private void RaiseMemberUpdated(User user) | |||
{ | |||
if (UserUpdated != null) | |||
RaiseEvent(nameof(UserUpdated), () => UserUpdated(this, new UserEventArgs(user))); | |||
} | |||
public event EventHandler<UserEventArgs> UserPresenceUpdated; | |||
private void RaiseUserPresenceUpdated(User user) | |||
{ | |||
if (UserPresenceUpdated != null) | |||
RaiseEvent(nameof(UserPresenceUpdated), () => UserPresenceUpdated(this, new UserEventArgs(user))); | |||
} | |||
public event EventHandler<UserEventArgs> UserVoiceStateUpdated; | |||
private void RaiseUserVoiceStateUpdated(User user) | |||
{ | |||
if (UserVoiceStateUpdated != null) | |||
RaiseEvent(nameof(UserVoiceStateUpdated), () => UserVoiceStateUpdated(this, new UserEventArgs(user))); | |||
} | |||
public event EventHandler ProfileUpdated; | |||
private void RaiseProfileUpdated() | |||
{ | |||
if (ProfileUpdated != null) | |||
RaiseEvent(nameof(ProfileUpdated), () => ProfileUpdated(this, EventArgs.Empty)); | |||
} | |||
/// <summary> Returns the current logged-in user. </summary> | |||
public User CurrentUser => _currentUser; | |||
private User _currentUser; | |||
/// <summary> Returns a collection of all users this client can currently see. </summary> | |||
internal GlobalUsers GlobalUsers => _globalUsers; | |||
private readonly GlobalUsers _globalUsers; | |||
internal Users Users => _users; | |||
private readonly Users _users; | |||
/// <summary> Returns the user with the specified id, along with their server-specific data, or null if none was found. </summary> | |||
public User GetUser(Server server, string userId) | |||
{ | |||
if (server == null) throw new ArgumentNullException(nameof(server)); | |||
if (userId == null) throw new ArgumentNullException(nameof(userId)); | |||
CheckReady(); | |||
return _users[userId, server.Id]; | |||
} | |||
/// <summary> Returns the user with the specified name and discriminator, along withtheir server-specific data, or null if they couldn't be found. </summary> | |||
/// <remarks> Name formats supported: Name and @Name. Search is case-insensitive. </remarks> | |||
public User GetUser(Server server, string username, string discriminator) | |||
{ | |||
if (server == null) throw new ArgumentNullException(nameof(server)); | |||
if (username == null) throw new ArgumentNullException(nameof(username)); | |||
if (discriminator == null) throw new ArgumentNullException(nameof(discriminator)); | |||
CheckReady(); | |||
User user = FindUsers(server, username, discriminator, true).FirstOrDefault(); | |||
return _users[user?.Id, server.Id]; | |||
} | |||
/// <summary> Returns all users in with the specified server and name, along with their server-specific data. </summary> | |||
/// <remarks> Name formats supported: Name and @Name. Search is case-insensitive.</remarks> | |||
public IEnumerable<User> FindUsers(Server server, string name, string discriminator = null, bool exactMatch = false) | |||
{ | |||
if (server == null) throw new ArgumentNullException(nameof(server)); | |||
if (name == null) throw new ArgumentNullException(nameof(name)); | |||
CheckReady(); | |||
IEnumerable<User> query; | |||
if (!exactMatch && name.StartsWith("@")) | |||
{ | |||
string name2 = name.Substring(1); | |||
query = server.Members.Where(x => | |||
string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) || | |||
string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); | |||
} | |||
else | |||
query = server.Members.Where(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); | |||
if (discriminator != null) | |||
query = query.Where(x => x.Discriminator == discriminator); | |||
return query; | |||
} | |||
public Task EditUser(User user, bool? mute = null, bool? deaf = null, IEnumerable<Role> roles = null) | |||
{ | |||
if (user == null) throw new ArgumentNullException(nameof(user)); | |||
CheckReady(); | |||
return _api.EditUser(user.Server?.Id, user.Id, mute: mute, deaf: deaf, roles: roles.Select(x => x.Id)); | |||
} | |||
public Task<EditUserResponse> EditProfile(string currentPassword = "", | |||
string username = null, string email = null, string password = null, | |||
ImageType avatarType = ImageType.Png, byte[] avatar = null) | |||
{ | |||
if (currentPassword == null) throw new ArgumentNullException(nameof(currentPassword)); | |||
CheckReady(); | |||
return _api.EditUser(currentPassword: currentPassword, | |||
username: username ?? _currentUser?.Name, email: email ?? _currentUser?.GlobalUser.Email, password: password, | |||
avatarType: avatarType, avatar: avatar); | |||
} | |||
public Task SetStatus(UserStatus status) | |||
{ | |||
if (status == (string)null) throw new ArgumentNullException(nameof(status)); | |||
if (status != UserStatus.Online && status != UserStatus.Idle) | |||
throw new ArgumentException($"Invalid status, must be {UserStatus.Online} or {UserStatus.Idle}", nameof(status)); | |||
CheckReady(); | |||
_status = status; | |||
return SendStatus(); | |||
} | |||
public Task SetGame(int? gameId) | |||
{ | |||
CheckReady(); | |||
_gameId = gameId; | |||
return SendStatus(); | |||
} | |||
private Task SendStatus() | |||
{ | |||
_dataSocket.SendStatus(_status == UserStatus.Idle ? EpochTime.GetMilliseconds() - (10 * 60 * 1000) : (ulong?)null, _gameId); | |||
return TaskHelper.CompletedTask; | |||
} | |||
} | |||
} |
@@ -0,0 +1,76 @@ | |||
using Discord.Audio; | |||
using System; | |||
using System.Threading.Tasks; | |||
namespace Discord | |||
{ | |||
public partial class DiscordClient | |||
{ | |||
public IDiscordVoiceClient GetVoiceClient(Server server) | |||
{ | |||
if (server.Id == null) throw new ArgumentNullException(nameof(server.Id)); | |||
if (!Config.EnableVoiceMultiserver) | |||
{ | |||
if (server.Id == _voiceServerId) | |||
return this; | |||
else | |||
return null; | |||
} | |||
DiscordWSClient client; | |||
if (_voiceClients.TryGetValue(server.Id, out client)) | |||
return client; | |||
else | |||
return null; | |||
} | |||
private async Task<IDiscordVoiceClient> CreateVoiceClient(Server server) | |||
{ | |||
if (!Config.EnableVoiceMultiserver) | |||
{ | |||
_voiceServerId = server.Id; | |||
return this; | |||
} | |||
var client = _voiceClients.GetOrAdd(server.Id, _ => | |||
{ | |||
var config = _config.Clone(); | |||
config.LogLevel = _config.LogLevel;// (LogMessageSeverity)Math.Min((int)_config.LogLevel, (int)LogMessageSeverity.Warning); | |||
config.VoiceOnly = true; | |||
config.VoiceClientId = unchecked(++_nextVoiceClientId); | |||
return new DiscordWSClient(config, server.Id); | |||
}); | |||
client.LogMessage += (s, e) => RaiseOnLog(e.Severity, e.Source, $"(#{client.Config.VoiceClientId}) {e.Message}"); | |||
await client.Connect(_gateway, _token).ConfigureAwait(false); | |||
return client; | |||
} | |||
public async Task<IDiscordVoiceClient> JoinVoiceServer(Channel channel) | |||
{ | |||
if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||
CheckReady(); //checkVoice is done inside the voice client | |||
var client = await CreateVoiceClient(channel.Server).ConfigureAwait(false); | |||
await client.JoinChannel(channel.Id).ConfigureAwait(false); | |||
return client; | |||
} | |||
public async Task LeaveVoiceServer(Server server) | |||
{ | |||
if (server == null) throw new ArgumentNullException(nameof(server)); | |||
CheckReady(checkVoice: true); | |||
if (Config.EnableVoiceMultiserver) | |||
{ | |||
DiscordWSClient client; | |||
if (_voiceClients.TryRemove(server.Id, out client)) | |||
await client.Disconnect().ConfigureAwait(false); | |||
} | |||
else | |||
{ | |||
await _voiceSocket.Disconnect().ConfigureAwait(false); | |||
_dataSocket.SendLeaveVoice(server.Id); | |||
} | |||
} | |||
} | |||
} |
@@ -1,180 +1,135 @@ | |||
using Discord.API; | |||
using Discord.Collections; | |||
using Discord.WebSockets; | |||
using Discord.WebSockets.Data; | |||
using Discord.Net.WebSockets; | |||
using Newtonsoft.Json; | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Net; | |||
using System.Threading.Tasks; | |||
using VoiceWebSocket = Discord.WebSockets.Voice.VoiceWebSocket; | |||
namespace Discord | |||
{ | |||
/// <summary> Provides a connection to the DiscordApp service. </summary> | |||
public partial class DiscordClient : DiscordSimpleClient | |||
public sealed partial class DiscordClient : DiscordWSClient | |||
{ | |||
protected readonly DiscordAPIClient _api; | |||
private readonly DiscordAPIClient _api; | |||
private readonly Random _rand; | |||
private readonly JsonSerializer _serializer; | |||
private readonly ConcurrentQueue<Message> _pendingMessages; | |||
private readonly ConcurrentDictionary<string, DiscordSimpleClient> _voiceClients; | |||
private readonly ConcurrentDictionary<string, DiscordWSClient> _voiceClients; | |||
private bool _sentInitialLog; | |||
private uint _nextVoiceClientId; | |||
private string _status; | |||
private UserStatus _status; | |||
private int? _gameId; | |||
/// <summary> Returns the configuration object used to make this client. Note that this object cannot be edited directly - to change the configuration of this client, use the DiscordClient(DiscordClientConfig config) constructor. </summary> | |||
public new DiscordClientConfig Config => _config as DiscordClientConfig; | |||
/// <summary> Returns the current logged-in user. </summary> | |||
public User CurrentUser => _currentUser; | |||
private User _currentUser; | |||
/// <summary> Returns a collection of all channels this client is a member of. </summary> | |||
public Channels Channels => _channels; | |||
private readonly Channels _channels; | |||
/// <summary> Returns a collection of all user-server pairs this client can currently see. </summary> | |||
public Members Members => _members; | |||
private readonly Members _members; | |||
/// <summary> Returns a collection of all messages this client has seen since logging in and currently has in cache. </summary> | |||
public Messages Messages => _messages; | |||
private readonly Messages _messages; | |||
//TODO: Do we need the roles cache? | |||
/// <summary> Returns a collection of all role-server pairs this client can currently see. </summary> | |||
public Roles Roles => _roles; | |||
private readonly Roles _roles; | |||
/// <summary> Returns a collection of all servers this client is a member of. </summary> | |||
public Servers Servers => _servers; | |||
private readonly Servers _servers; | |||
/// <summary> Returns a collection of all users this client can currently see. </summary> | |||
public Users Users => _users; | |||
private readonly Users _users; | |||
/// <summary> Gives direct access to the underlying DiscordAPIClient. This can be used to modify objects not in cache. </summary> | |||
public DiscordAPIClient API => _api; | |||
/// <summary> Initializes a new instance of the DiscordClient class. </summary> | |||
public DiscordClient(DiscordClientConfig config = null) | |||
: base(config ?? new DiscordClientConfig()) | |||
{ | |||
_rand = new Random(); | |||
_api = new DiscordAPIClient(_config.LogLevel, _config.UserAgent, _config.APITimeout); | |||
_api = new DiscordAPIClient(_config); | |||
if (Config.UseMessageQueue) | |||
_pendingMessages = new ConcurrentQueue<Message>(); | |||
if (Config.EnableVoiceMultiserver) | |||
_voiceClients = new ConcurrentDictionary<string, DiscordSimpleClient>(); | |||
_voiceClients = new ConcurrentDictionary<string, DiscordWSClient>(); | |||
object cacheLock = new object(); | |||
_channels = new Channels(this, cacheLock); | |||
_members = new Members(this, cacheLock); | |||
_messages = new Messages(this, cacheLock); | |||
_users = new Users(this, cacheLock); | |||
_messages = new Messages(this, cacheLock, Config.MessageCacheLength > 0); | |||
_roles = new Roles(this, cacheLock); | |||
_servers = new Servers(this, cacheLock); | |||
_users = new Users(this, cacheLock); | |||
_globalUsers = new GlobalUsers(this, cacheLock); | |||
_status = UserStatus.Online; | |||
this.Connected += async (s, e) => | |||
{ | |||
_api.CancelToken = CancelToken; | |||
await SendStatus(); | |||
await SendStatus().ConfigureAwait(false); | |||
}; | |||
VoiceDisconnected += (s, e) => | |||
{ | |||
foreach (var member in _members) | |||
var server = _servers[e.ServerId]; | |||
if (server != null) | |||
{ | |||
if (member.ServerId == e.ServerId && member.IsSpeaking) | |||
foreach (var member in server.Members) | |||
{ | |||
member.IsSpeaking = false; | |||
RaiseUserIsSpeaking(member, false); | |||
if (member.IsSpeaking) | |||
{ | |||
member.IsSpeaking = false; | |||
RaiseUserIsSpeaking(member, _channels[_voiceSocket.CurrentChannelId], false); | |||
} | |||
} | |||
} | |||
}; | |||
bool showIDs = _config.LogLevel > LogMessageSeverity.Debug; //Hide this for now | |||
if (_config.LogLevel >= LogMessageSeverity.Info) | |||
{ | |||
ServerCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
$"Created Server: {e.Server?.Name}" + | |||
(showIDs ? $" ({e.ServerId})" : "")); | |||
$"Server Created: {e.Server?.Name ?? "[Private]"}"); | |||
ServerDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
$"Destroyed Server: {e.Server?.Name}" + | |||
(showIDs ? $" ({e.ServerId})" : "")); | |||
$"Server Destroyed: {e.Server?.Name ?? "[Private]"}"); | |||
ServerUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
$"Updated Server: {e.Server?.Name}" + | |||
(showIDs ? $" ({e.ServerId})" : "")); | |||
$"Server Updated: {e.Server?.Name ?? "[Private]"}"); | |||
ServerAvailable += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
$"Server Unavailable: {e.Server?.Name}" + | |||
(showIDs ? $" ({e.ServerId})" : "")); | |||
$"Server Available: {e.Server?.Name ?? "[Private]"}"); | |||
ServerUnavailable += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
$"Server Unavailable: {e.Server?.Name}" + | |||
(showIDs ? $" ({e.ServerId})" : "")); | |||
$"Server Unavailable: {e.Server?.Name ?? "[Private]"}"); | |||
ChannelCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
$"Created Channel: {e.Server?.Name ?? "[Private]"}/{e.Channel.Name}" + | |||
(showIDs ? $" ({e.ServerId ?? "[Private]"}/{e.ChannelId})" : "")); | |||
$"Channel Created: {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}"); | |||
ChannelDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
$"Destroyed Channel: {e.Server?.Name ?? "[Private]"}/{e.Channel.Name}" + | |||
(showIDs ? $" ({e.ServerId ?? "[Private]"}/{e.ChannelId})" : "")); | |||
$"Channel Destroyed: {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}"); | |||
ChannelUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
$"Updated Channel: {e.Server?.Name ?? "[Private]"}/{e.Channel.Name}" + | |||
(showIDs ? $" ({e.ServerId ?? "[Private]"}/{e.ChannelId})" : "")); | |||
MessageCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
$"Created Message: {e.Server?.Name ?? "[Private]"}/{e.Channel.Name}/{e.MessageId}" + | |||
(showIDs ? $" ({e.ServerId ?? "[Private]"}/{e.ChannelId}/{e.MessageId})" : "")); | |||
$"Channel Updated: {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}"); | |||
MessageReceived += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
$"Message Created: {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}/{e.Message?.Id}"); | |||
MessageDeleted += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
$"Deleted Message: {e.Server?.Name ?? "[Private]"}/{e.Channel.Name}/{e.MessageId}" + | |||
(showIDs ? $" ({e.ServerId ?? "[Private]"}/{e.ChannelId}/{e.MessageId})" : "")); | |||
$"Message Deleted: {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}/{e.Message?.Id}"); | |||
MessageUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
$"Updated Message: {e.Server?.Name ?? "[Private]"}/{e.Channel.Name}/{e.MessageId}" + | |||
(showIDs ? $" ({e.ServerId ?? "[Private]"}/{e.ChannelId}/{e.MessageId})" : "")); | |||
$"Message Update: {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}/{e.Message?.Id}"); | |||
RoleCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
$"Created Role: {e.Server?.Name ?? "[Private]"}/{e.Role.Name}" + | |||
(showIDs ? $" ({e.ServerId ?? "[Private]"}/{e.RoleId})." : "")); | |||
$"Role Created: {e.Server?.Name ?? "[Private]"}/{e.Role?.Name}"); | |||
RoleUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
$"Updated Role: {e.Server?.Name ?? "[Private]"}/{e.Role.Name}" + | |||
(showIDs ? $" ({e.ServerId ?? "[Private]"}/{e.RoleId})." : "")); | |||
$"Role Updated: {e.Server?.Name ?? "[Private]"}/{e.Role?.Name}"); | |||
RoleDeleted += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
$"Deleted Role: {e.Server?.Name ?? "[Private]"}/{e.Role.Name}" + | |||
(showIDs ? $" ({e.ServerId ?? "[Private]"}/{e.RoleId})." : "")); | |||
BanAdded += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
$"Added Ban: {e.Server?.Name ?? "[Private]"}/{e.User?.Name ?? "Unknown"}" + | |||
(showIDs ? $" ({e.ServerId ?? "[Private]"}/{e.UserId})." : "")); | |||
BanRemoved += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
$"Removed Ban: {e.Server?.Name ?? "[Private]"}/{e.User?.Name ?? "Unknown"}" + | |||
(showIDs ? $" ({e.ServerId ?? "[Private]"}/{e.UserId})." : "")); | |||
$"Role Deleted: {e.Server?.Name ?? "[Private]"}/{e.Role?.Name}"); | |||
UserBanned += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
$"Banned User: {e.Server?.Name ?? "[Private]" }/{e.UserId}"); | |||
UserUnbanned += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
$"Unbanned User: {e.Server?.Name ?? "[Private]"}/{e.UserId}"); | |||
UserAdded += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
$"Added Member: {e.Server?.Name ?? "[Private]"}/{e.User.Name}" + | |||
(showIDs ? $" ({e.ServerId ?? "[Private]"}/{e.UserId})." : "")); | |||
$"User Joined: {e.Server?.Name ?? "[Private]"}/{e.User.Id}"); | |||
UserRemoved += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
$"Removed Member: {e.Server?.Name ?? "[Private]"}/{e.User.Name}" + | |||
(showIDs ? $" ({e.ServerId ?? "[Private]"}/{e.UserId})." : "")); | |||
MemberUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
$"Updated Member: {e.Server?.Name ?? "[Private]"}/{e.User.Name}" + | |||
(showIDs ? $" ({e.ServerId ?? "[Private]"}/{e.UserId})." : "")); | |||
UserVoiceStateUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
$"Updated Member (Voice State): {e.Server?.Name ?? "[Private]"}/{e.User.Name}" + | |||
(showIDs ? $" ({e.ServerId ?? "0"}/{e.UserId})" : "")); | |||
$"User Left: {e.Server?.Name ?? "[Private]"}/{e.User.Id}"); | |||
UserUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
$"Updated User: {e.User.Name}" + | |||
(showIDs ? $" ({e.UserId})." : "")); | |||
$"User Updated: {e.Server?.Name ?? "[Private]"}/{e.User.Id}"); | |||
UserVoiceStateUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
$"User Updated (Voice State): {e.Server?.Name ?? "[Private]"}/{e.User.Id}"); | |||
ProfileUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | |||
"Profile Updated"); | |||
} | |||
if (_config.LogLevel >= LogMessageSeverity.Verbose) | |||
{ | |||
UserIsTyping += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, | |||
$"Updated User (Is Typing): {e.Server?.Name ?? "[Private]"}/{e.Channel.Name}/{e.User.Name}" + | |||
(showIDs ? $" ({e.ServerId ?? "[Private]"}/{e.ChannelId}/{e.UserId})" : "")); | |||
UserIsTypingUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, | |||
$"Updated User (Is Typing): {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}/{e.User?.Name}"); | |||
MessageReadRemotely += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, | |||
$"Read Message (Remotely): {e.Server?.Name ?? "[Private]"}/{e.Channel.Name}/{e.MessageId}" + | |||
(showIDs ? $" ({e.ServerId ?? "[Private]"}/{e.ChannelId}/{e.MessageId})" : "")); | |||
$"Read Message (Remotely): {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}/{e.Message?.Id}"); | |||
MessageSent += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, | |||
$"Sent Message: {e.Server?.Name ?? "[Private]"}/{e.Channel.Name}/{e.MessageId}" + | |||
(showIDs ? $" ({e.ServerId ?? "[Private]"}/{e.ChannelId}/{e.MessageId})" : "")); | |||
$"Sent Message: {e.Server?.Name ?? "[Private]"}/{e.Channel?.Name}/{e.Message?.Id}"); | |||
UserPresenceUpdated += (s, e) => RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, | |||
$"Updated Member (Presence): {e.Server?.Name ?? "[Private]"}/{e.User.Name}" + | |||
(showIDs ? $" ({e.ServerId ?? "[Private]"}/{e.UserId})" : "")); | |||
$"Updated Member (Presence): {e.Server?.Name ?? "[Private]"}/{e.User?.Name}"); | |||
_api.RestClient.OnRequest += (s, e) => | |||
{ | |||
if (e.Payload != null) | |||
if (e.Payload != null) | |||
RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Rest, $"{e.Method.Method} {e.Path}: {Math.Round(e.ElapsedMilliseconds, 2)} ms ({e.Payload})"); | |||
else | |||
RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Rest, $"{e.Method.Method} {e.Path}: {Math.Round(e.ElapsedMilliseconds, 2)} ms"); | |||
@@ -182,25 +137,25 @@ namespace Discord | |||
} | |||
if (_config.LogLevel >= LogMessageSeverity.Debug) | |||
{ | |||
_channels.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Created Channel {e.Item?.ServerId ?? "[Private]"}/{e.Item.Id}"); | |||
_channels.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Destroyed Channel {e.Item.ServerId ?? "[Private]"}/{e.Item.Id}"); | |||
_channels.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Created Channel {e.Item.Server?.Id ?? "[Private]"}/{e.Item.Id}"); | |||
_channels.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Destroyed Channel {e.Item.Server?.Id ?? "[Private]"}/{e.Item.Id}"); | |||
_channels.Cleared += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Cleared Channels"); | |||
_members.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Created Member {e.Item.ServerId ?? "[Private]"}/{e.Item.UserId}"); | |||
_members.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Destroyed Member {e.Item.ServerId ?? "[Private]"}/{e.Item.UserId}"); | |||
_members.Cleared += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Cleared Members"); | |||
_messages.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Created Message {e.Item.ServerId ?? "[Private]"}/{e.Item.ChannelId}/{e.Item.Id}"); | |||
_messages.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Destroyed Message {e.Item.ServerId ?? "[Private]"}/{e.Item.ChannelId}/{e.Item.Id}"); | |||
_messages.ItemRemapped += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Remapped Message {e.Item.ServerId ?? "[Private]"}/{e.Item.ChannelId}/[{e.OldId} -> {e.NewId}]"); | |||
_users.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Created Member {e.Item.Server?.Id ?? "[Private]"}/{e.Item.Id}"); | |||
_users.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Destroyed Member {e.Item.Server?.Id ?? "[Private]"}/{e.Item.Id}"); | |||
_users.Cleared += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Cleared Members"); | |||
_messages.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Created Message {e.Item.Server?.Id ?? "[Private]"}/{e.Item.Channel.Id}/{e.Item.Id}"); | |||
_messages.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Destroyed Message {e.Item.Server?.Id ?? "[Private]"}/{e.Item.Channel.Id}/{e.Item.Id}"); | |||
_messages.ItemRemapped += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Remapped Message {e.Item.Server?.Id ?? "[Private]"}/{e.Item.Channel.Id}/[{e.OldId} -> {e.NewId}]"); | |||
_messages.Cleared += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Cleared Messages"); | |||
_roles.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Created Role {e.Item.ServerId}/{e.Item.Id}"); | |||
_roles.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Destroyed Role {e.Item.ServerId}/{e.Item.Id}"); | |||
_roles.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Created Role {e.Item.Server?.Id ?? "[Private]"}/{e.Item.Id}"); | |||
_roles.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Destroyed Role {e.Item.Server?.Id ?? "[Private]"}/{e.Item.Id}"); | |||
_roles.Cleared += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Cleared Roles"); | |||
_servers.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Created Server {e.Item.Id}"); | |||
_servers.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Destroyed Server {e.Item.Id}"); | |||
_servers.Cleared += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Cleared Servers"); | |||
_users.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Created User {e.Item.Id}"); | |||
_users.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Destroyed User {e.Item.Id}"); | |||
_users.Cleared += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Cleared Users"); | |||
_globalUsers.ItemCreated += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Created User {e.Item.Id}"); | |||
_globalUsers.ItemDestroyed += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Destroyed User {e.Item.Id}"); | |||
_globalUsers.Cleared += (s, e) => RaiseOnLog(LogMessageSeverity.Debug, LogMessageSource.Cache, $"Cleared Users"); | |||
} | |||
if (Config.UseMessageQueue) | |||
@@ -220,14 +175,15 @@ namespace Discord | |||
{ | |||
if (_voiceSocket.State == WebSocketState.Connected) | |||
{ | |||
var member = _members[e.UserId, socket.CurrentServerId]; | |||
var user = _users[e.UserId, socket.CurrentServerId]; | |||
bool value = e.IsSpeaking; | |||
if (member.IsSpeaking != value) | |||
if (user.IsSpeaking != value) | |||
{ | |||
member.IsSpeaking = value; | |||
RaiseUserIsSpeaking(member, value); | |||
user.IsSpeaking = value; | |||
var channel = _channels[_voiceSocket.CurrentChannelId]; | |||
RaiseUserIsSpeaking(user, channel, value); | |||
if (Config.TrackActivity) | |||
member.UpdateActivity(); | |||
user.UpdateActivity(); | |||
} | |||
} | |||
}; | |||
@@ -256,7 +212,7 @@ namespace Discord | |||
} | |||
catch (TaskCanceledException) { throw new TimeoutException(); } | |||
await Connect(token); | |||
await Connect(token).ConfigureAwait(false); | |||
return token; | |||
} | |||
@@ -271,8 +227,9 @@ namespace Discord | |||
_api.Token = token; | |||
string gateway = (await _api.Gateway() | |||
.Timeout(_config.APITimeout) | |||
.ConfigureAwait(false)).Url; | |||
.Timeout(_config.APITimeout) | |||
.ConfigureAwait(false) | |||
).Url; | |||
if (_config.LogLevel >= LogMessageSeverity.Verbose) | |||
RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, $"Websocket endpoint: {gateway}"); | |||
@@ -303,12 +260,14 @@ namespace Discord | |||
while (_pendingMessages.TryDequeue(out ignored)) { } | |||
} | |||
await _api.Logout().ConfigureAwait(false); | |||
_channels.Clear(); | |||
_members.Clear(); | |||
_users.Clear(); | |||
_messages.Clear(); | |||
_roles.Clear(); | |||
_servers.Clear(); | |||
_users.Clear(); | |||
_globalUsers.Clear(); | |||
_currentUser = null; | |||
} | |||
@@ -321,60 +280,18 @@ namespace Discord | |||
return base.GetTasks(); | |||
} | |||
private Task MessageQueueLoop() | |||
{ | |||
var cancelToken = CancelToken; | |||
int interval = Config.MessageQueueInterval; | |||
return Task.Run(async () => | |||
{ | |||
Message msg; | |||
while (!cancelToken.IsCancellationRequested) | |||
{ | |||
while (_pendingMessages.TryDequeue(out msg)) | |||
{ | |||
bool hasFailed = false; | |||
SendMessageResponse response = null; | |||
try | |||
{ | |||
response = await _api.SendMessage(msg.ChannelId, msg.RawText, msg.MentionIds, msg.Nonce, msg.IsTTS).ConfigureAwait(false); | |||
} | |||
catch (WebException) { break; } | |||
catch (HttpException) { hasFailed = true; } | |||
if (!hasFailed) | |||
{ | |||
_messages.Remap(msg.Id, response.Id); | |||
msg.Id = response.Id; | |||
msg.Update(response); | |||
} | |||
msg.IsQueued = false; | |||
msg.HasFailed = hasFailed; | |||
RaiseMessageSent(msg); | |||
} | |||
await Task.Delay(interval).ConfigureAwait(false); | |||
} | |||
}); | |||
} | |||
private string GenerateNonce() | |||
{ | |||
lock (_rand) | |||
return _rand.Next().ToString(); | |||
} | |||
internal override async Task OnReceivedEvent(WebSocketEventEventArgs e) | |||
{ | |||
try | |||
{ | |||
await base.OnReceivedEvent(e); | |||
switch (e.Type) | |||
{ | |||
//Global | |||
case "READY": //Resync | |||
case "READY": //Resync | |||
{ | |||
base.OnReceivedEvent(e).Wait(); //This cannot be an await, or we'll get later messages before we're ready | |||
var data = e.Payload.ToObject<ReadyEvent>(_serializer); | |||
_currentUser = _users.GetOrAdd(data.User.Id); | |||
_currentUser = _users.GetOrAdd(data.User.Id, null); | |||
_currentUser.Update(data.User); | |||
foreach (var model in data.Guilds) | |||
{ | |||
@@ -386,7 +303,7 @@ namespace Discord | |||
} | |||
foreach (var model in data.PrivateChannels) | |||
{ | |||
var user = _users.GetOrAdd(model.Recipient.Id); | |||
var user = _users.GetOrAdd(model.Recipient.Id, null); | |||
user.Update(model.Recipient); | |||
var channel = _channels.GetOrAdd(model.Id, null, user.Id); | |||
channel.Update(model); | |||
@@ -441,7 +358,7 @@ namespace Discord | |||
Channel channel; | |||
if (data.IsPrivate) | |||
{ | |||
var user = _users.GetOrAdd(data.Recipient.Id); | |||
var user = _users.GetOrAdd(data.Recipient.Id, null); | |||
user.Update(data.Recipient); | |||
channel = _channels.GetOrAdd(data.Id, null, user.Id); | |||
} | |||
@@ -474,87 +391,89 @@ namespace Discord | |||
//Members | |||
case "GUILD_MEMBER_ADD": | |||
{ | |||
var data = e.Payload.ToObject<GuildMemberAddEvent>(_serializer); | |||
var user = _users.GetOrAdd(data.User.Id); | |||
user.Update(data.User); | |||
var member = _members.GetOrAdd(data.User.Id, data.GuildId); | |||
member.Update(data); | |||
var data = e.Payload.ToObject<MemberAddEvent>(_serializer); | |||
var user = _users.GetOrAdd(data.User.Id, data.GuildId); | |||
user.Update(data); | |||
if (Config.TrackActivity) | |||
member.UpdateActivity(); | |||
RaiseUserAdded(member); | |||
user.UpdateActivity(); | |||
RaiseUserAdded(user); | |||
} | |||
break; | |||
case "GUILD_MEMBER_UPDATE": | |||
{ | |||
var data = e.Payload.ToObject<GuildMemberUpdateEvent>(_serializer); | |||
var member = _members[data.User.Id, data.GuildId]; | |||
if (member != null) | |||
var data = e.Payload.ToObject<MemberUpdateEvent>(_serializer); | |||
var user = _users[data.User.Id, data.GuildId]; | |||
if (user != null) | |||
{ | |||
member.Update(data); | |||
RaiseMemberUpdated(member); | |||
user.Update(data); | |||
RaiseMemberUpdated(user); | |||
} | |||
} | |||
break; | |||
case "GUILD_MEMBER_REMOVE": | |||
{ | |||
var data = e.Payload.ToObject<GuildMemberRemoveEvent>(_serializer); | |||
var member = _members.TryRemove(data.UserId, data.GuildId); | |||
if (member != null) | |||
RaiseUserRemoved(member); | |||
var data = e.Payload.ToObject<MemberRemoveEvent>(_serializer); | |||
var user = _users.TryRemove(data.UserId, data.GuildId); | |||
if (user != null) | |||
RaiseUserRemoved(user); | |||
} | |||
break; | |||
//Roles | |||
case "GUILD_ROLE_CREATE": | |||
{ | |||
var data = e.Payload.ToObject<GuildRoleCreateEvent>(_serializer); | |||
var role = _roles.GetOrAdd(data.Data.Id, data.GuildId, false); | |||
var data = e.Payload.ToObject<RoleCreateEvent>(_serializer); | |||
var role = _roles.GetOrAdd(data.Data.Id, data.GuildId); | |||
role.Update(data.Data); | |||
var server = _servers[data.GuildId]; | |||
if (server != null) | |||
server.AddRole(data.Data.Id); | |||
server.AddRole(role); | |||
RaiseRoleUpdated(role); | |||
} | |||
break; | |||
case "GUILD_ROLE_UPDATE": | |||
{ | |||
var data = e.Payload.ToObject<GuildRoleUpdateEvent>(_serializer); | |||
var data = e.Payload.ToObject<RoleUpdateEvent>(_serializer); | |||
var role = _roles[data.Data.Id]; | |||
if (role != null) | |||
{ | |||
role.Update(data.Data); | |||
RaiseRoleUpdated(role); | |||
RaiseRoleUpdated(role); | |||
} | |||
} | |||
break; | |||
case "GUILD_ROLE_DELETE": | |||
{ | |||
var data = e.Payload.ToObject<GuildRoleDeleteEvent>(_serializer); | |||
var server = _servers[data.GuildId]; | |||
if (server != null) | |||
server.RemoveRole(data.RoleId); | |||
var data = e.Payload.ToObject<RoleDeleteEvent>(_serializer); | |||
var role = _roles.TryRemove(data.RoleId); | |||
if (role != null) | |||
{ | |||
RaiseRoleDeleted(role); | |||
var server = _servers[data.GuildId]; | |||
if (server != null) | |||
server.RemoveRole(role); | |||
} | |||
} | |||
break; | |||
//Bans | |||
case "GUILD_BAN_ADD": | |||
{ | |||
var data = e.Payload.ToObject<GuildBanAddEvent>(_serializer); | |||
var data = e.Payload.ToObject<BanAddEvent>(_serializer); | |||
var server = _servers[data.GuildId]; | |||
if (server != null) | |||
{ | |||
server.AddBan(data.User?.Id); | |||
RaiseBanAdded(data.User?.Id, server); | |||
RaiseUserBanned(data.User?.Id, server); | |||
} | |||
} | |||
break; | |||
case "GUILD_BAN_REMOVE": | |||
{ | |||
var data = e.Payload.ToObject<GuildBanRemoveEvent>(_serializer); | |||
var data = e.Payload.ToObject<BanRemoveEvent>(_serializer); | |||
var server = _servers[data.GuildId]; | |||
if (server != null && server.RemoveBan(data.User?.Id)) | |||
RaiseBanRemoved(data.User?.Id, server); | |||
RaiseUserUnbanned(data.User?.Id, server); | |||
} | |||
break; | |||
@@ -564,18 +483,7 @@ namespace Discord | |||
var data = e.Payload.ToObject<MessageCreateEvent>(_serializer); | |||
Message msg = null; | |||
bool isAuthor = data.Author.Id == CurrentUserId; | |||
bool hasFinishedSending = false; | |||
if (Config.UseMessageQueue && isAuthor && data.Nonce != null) | |||
{ | |||
msg = _messages.Remap("nonce" + data.Nonce, data.Id); | |||
if (msg != null) | |||
{ | |||
msg.IsQueued = false; | |||
msg.Id = data.Id; | |||
hasFinishedSending = true; | |||
} | |||
} | |||
bool isAuthor = data.Author.Id == _userId; | |||
if (msg == null) | |||
msg = _messages.GetOrAdd(data.Id, data.ChannelId, data.Author.Id); | |||
@@ -583,26 +491,18 @@ namespace Discord | |||
if (Config.TrackActivity) | |||
{ | |||
var channel = msg.Channel; | |||
if (channel == null || channel.IsPrivate) | |||
if (channel?.IsPrivate == false) | |||
{ | |||
var user = msg.User; | |||
if (user != null) | |||
user.UpdateActivity(data.Timestamp); | |||
} | |||
else | |||
{ | |||
var member = msg.Member; | |||
if (member != null) | |||
member.UpdateActivity(data.Timestamp); | |||
} | |||
} | |||
if (Config.AckMessages && isAuthor) | |||
await _api.AckMessage(data.Id, data.ChannelId); | |||
if (hasFinishedSending) | |||
RaiseMessageSent(msg); | |||
RaiseMessageCreated(msg); | |||
if (Config.AckMessages && !isAuthor) | |||
await _api.AckMessage(data.Id, data.ChannelId).ConfigureAwait(false); | |||
} | |||
break; | |||
case "MESSAGE_UPDATE": | |||
@@ -637,17 +537,11 @@ namespace Discord | |||
case "PRESENCE_UPDATE": | |||
{ | |||
var data = e.Payload.ToObject<PresenceUpdateEvent>(_serializer); | |||
var member = _members[data.User.Id, data.GuildId]; | |||
/*if (_config.TrackActivity) | |||
{ | |||
var user = _users[data.User.Id]; | |||
if (user != null) | |||
user.UpdateActivity(DateTime.UtcNow); | |||
}*/ | |||
if (member != null) | |||
var user = _users.GetOrAdd(data.User.Id, data.GuildId); | |||
if (user != null) | |||
{ | |||
member.Update(data); | |||
RaiseUserPresenceUpdated(member); | |||
user.Update(data); | |||
RaiseUserPresenceUpdated(user); | |||
} | |||
} | |||
break; | |||
@@ -655,25 +549,22 @@ namespace Discord | |||
{ | |||
var data = e.Payload.ToObject<TypingStartEvent>(_serializer); | |||
var channel = _channels[data.ChannelId]; | |||
var user = _users[data.UserId]; | |||
if (user != null) | |||
{ | |||
if (channel != null) | |||
RaiseUserIsTyping(user, channel); | |||
} | |||
if (Config.TrackActivity) | |||
if (channel != null) | |||
{ | |||
if (channel.IsPrivate) | |||
var user = _users[data.UserId, channel.Server?.Id]; | |||
if (user != null) | |||
{ | |||
if (user != null) | |||
user.UpdateActivity(); | |||
if (channel != null) | |||
RaiseUserIsTyping(user, channel); | |||
} | |||
else | |||
if (Config.TrackActivity) | |||
{ | |||
var member = _members[data.UserId, channel.ServerId]; | |||
if (member != null) | |||
member.UpdateActivity(); | |||
if (!channel.IsPrivate) | |||
{ | |||
if (user != null) | |||
user.UpdateActivity(); | |||
} | |||
} | |||
} | |||
} | |||
@@ -682,17 +573,18 @@ namespace Discord | |||
//Voice | |||
case "VOICE_STATE_UPDATE": | |||
{ | |||
var data = e.Payload.ToObject<VoiceStateUpdateEvent>(_serializer); | |||
var member = _members[data.UserId, data.GuildId]; | |||
if (member != null) | |||
var data = e.Payload.ToObject<MemberVoiceStateUpdateEvent>(_serializer); | |||
var user = _users[data.UserId, data.GuildId]; | |||
if (user != null) | |||
{ | |||
if (data.ChannelId != member.VoiceChannelId && member.IsSpeaking) | |||
var voiceChannel = user.VoiceChannel; | |||
if (voiceChannel != null && data.ChannelId != voiceChannel.Id && user.IsSpeaking) | |||
{ | |||
member.IsSpeaking = false; | |||
RaiseUserIsSpeaking(member, false); | |||
user.IsSpeaking = false; | |||
RaiseUserIsSpeaking(user, _channels[voiceChannel.Id], false); | |||
} | |||
member.Update(data); | |||
RaiseUserVoiceStateUpdated(member); | |||
user.Update(data); | |||
RaiseUserVoiceStateUpdated(user); | |||
} | |||
} | |||
break; | |||
@@ -701,11 +593,11 @@ namespace Discord | |||
case "USER_UPDATE": | |||
{ | |||
var data = e.Payload.ToObject<UserUpdateEvent>(_serializer); | |||
var user = _users[data.Id]; | |||
var user = _globalUsers[data.Id]; | |||
if (user != null) | |||
{ | |||
user.Update(data); | |||
RaiseUserUpdated(user); | |||
RaiseProfileUpdated(); | |||
} | |||
} | |||
break; | |||
@@ -719,8 +611,9 @@ namespace Discord | |||
case "RESUMED": | |||
break; | |||
//Internal (handled in DiscordSimpleClient) | |||
//Pass to DiscordWSClient | |||
case "VOICE_SERVER_UPDATE": | |||
await base.OnReceivedEvent(e).ConfigureAwait(false); | |||
break; | |||
//Others | |||
@@ -735,83 +628,6 @@ namespace Discord | |||
} | |||
} | |||
public IDiscordVoiceClient GetVoiceClient(Server server) | |||
=> GetVoiceClient(server.Id); | |||
public IDiscordVoiceClient GetVoiceClient(string serverId) | |||
{ | |||
if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
if (!Config.EnableVoiceMultiserver) | |||
{ | |||
if (serverId == _voiceServerId) | |||
return this; | |||
else | |||
return null; | |||
} | |||
DiscordSimpleClient client; | |||
if (_voiceClients.TryGetValue(serverId, out client)) | |||
return client; | |||
else | |||
return null; | |||
} | |||
private async Task<IDiscordVoiceClient> CreateVoiceClient(string serverId) | |||
{ | |||
if (!Config.EnableVoiceMultiserver) | |||
{ | |||
_voiceServerId = serverId; | |||
return this; | |||
} | |||
var client = _voiceClients.GetOrAdd(serverId, _ => | |||
{ | |||
var config = _config.Clone(); | |||
config.LogLevel = _config.LogLevel;// (LogMessageSeverity)Math.Min((int)_config.LogLevel, (int)LogMessageSeverity.Warning); | |||
config.EnableVoiceMultiserver = false; | |||
config.VoiceOnly = true; | |||
config.VoiceClientId = unchecked(++_nextVoiceClientId); | |||
return new DiscordSimpleClient(config, serverId); | |||
}); | |||
client.LogMessage += (s, e) => RaiseOnLog(e.Severity, e.Source, $"(#{client.Config.VoiceClientId}) {e.Message}"); | |||
await client.Connect(_gateway, _token).ConfigureAwait(false); | |||
return client; | |||
} | |||
public Task<IDiscordVoiceClient> JoinVoiceServer(Channel channel) | |||
=> JoinVoiceServer(channel?.ServerId, channel?.Id); | |||
public Task<IDiscordVoiceClient> JoinVoiceServer(Server server, string channelId) | |||
=> JoinVoiceServer(server?.Id, channelId); | |||
public async Task<IDiscordVoiceClient> JoinVoiceServer(string serverId, string channelId) | |||
{ | |||
CheckReady(); //checkVoice is done inside the voice client | |||
if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
if (channelId == null) throw new ArgumentNullException(nameof(channelId)); | |||
var client = await CreateVoiceClient(serverId).ConfigureAwait(false); | |||
await client.JoinChannel(channelId).ConfigureAwait(false); | |||
return client; | |||
} | |||
public Task LeaveVoiceServer(Server server) | |||
=> LeaveVoiceServer(server?.Id); | |||
public async Task LeaveVoiceServer(string serverId) | |||
{ | |||
CheckReady(); //checkVoice is done inside the voice client | |||
if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
if (Config.EnableVoiceMultiserver) | |||
{ | |||
DiscordSimpleClient client; | |||
if (_voiceClients.TryRemove(serverId, out client)) | |||
await client.Disconnect(); | |||
} | |||
else | |||
{ | |||
_dataSocket.SendLeaveVoice(serverId); | |||
await _voiceSocket.Disconnect(); | |||
} | |||
} | |||
private void SendInitialLog() | |||
{ | |||
if (_config.LogLevel >= LogMessageSeverity.Verbose) | |||
@@ -1,10 +1,13 @@ | |||
namespace Discord | |||
{ | |||
public class DiscordClientConfig : DiscordSimpleClientConfig | |||
public class DiscordClientConfig : DiscordWSClientConfig | |||
{ | |||
/// <summary> Gets or sets the time (in milliseconds) to wait when the message queue is empty before checking again. </summary> | |||
public int MessageQueueInterval { get { return _messageQueueInterval; } set { SetValue(ref _messageQueueInterval, value); } } | |||
private int _messageQueueInterval = 100; | |||
/// <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 MessageCacheLength { get { return _messageCacheLength; } set { SetValue(ref _messageCacheLength, value); } } | |||
private int _messageCacheLength = 100; | |||
//Experimental Features | |||
/// <summary> (Experimental) Enables the client to be simultaneously connected to multiple channels at once (Discord still limits you to one channel per server). </summary> | |||
@@ -21,11 +24,11 @@ | |||
private bool _ackMessages = false; | |||
//Internal | |||
internal override bool EnableVoice => base.EnableVoice && !EnableVoiceMultiserver; | |||
internal override bool EnableVoice => (base.EnableVoice && !EnableVoiceMultiserver) || base.VoiceOnly; | |||
public new DiscordClientConfig Clone() | |||
{ | |||
var config = this.MemberwiseClone() as DiscordClientConfig; | |||
var config = MemberwiseClone() as DiscordClientConfig; | |||
config._isLocked = false; | |||
return config; | |||
} | |||
@@ -73,7 +73,7 @@ namespace Discord | |||
} | |||
} | |||
public partial class DiscordSimpleClient | |||
public partial class DiscordWSClient | |||
{ | |||
public event EventHandler Connected; | |||
private void RaiseConnected() |
@@ -1,29 +1,10 @@ | |||
using System; | |||
using Discord.Audio; | |||
using System; | |||
using System.Threading.Tasks; | |||
namespace Discord | |||
{ | |||
public interface IDiscordVoiceBuffer | |||
{ | |||
int FrameSize { get; } | |||
int FrameCount { get; } | |||
ushort ReadPos { get; } | |||
ushort WritePos { get; } | |||
} | |||
public interface IDiscordVoiceClient | |||
{ | |||
IDiscordVoiceBuffer OutputBuffer { get; } | |||
Task JoinChannel(string channelId); | |||
void SendVoicePCM(byte[] data, int count); | |||
void ClearVoicePCM(); | |||
Task WaitVoice(); | |||
} | |||
public partial class DiscordSimpleClient : IDiscordVoiceClient | |||
public partial class DiscordWSClient : IDiscordVoiceClient | |||
{ | |||
IDiscordVoiceBuffer IDiscordVoiceClient.OutputBuffer => _voiceSocket.OutputBuffer; | |||
@@ -36,34 +17,20 @@ namespace Discord | |||
await _voiceSocket.SetChannel(_voiceServerId, channelId).ConfigureAwait(false); | |||
_dataSocket.SendJoinVoice(_voiceServerId, channelId); | |||
await _voiceSocket.WaitForConnection(_config.ConnectionTimeout); | |||
await _voiceSocket.WaitForConnection(_config.ConnectionTimeout).ConfigureAwait(false); | |||
} | |||
/*async Task IDiscordVoiceClient.Disconnect() | |||
{ | |||
CheckReady(checkVoice: true); | |||
if (_voiceSocket.State != WebSocketState.Disconnected) | |||
{ | |||
if (_voiceSocket.CurrentServerId != null) | |||
{ | |||
await _voiceSocket.Disconnect().ConfigureAwait(false); | |||
_dataSocket.SendLeaveVoice(_voiceSocket.CurrentServerId); | |||
} | |||
} | |||
}*/ | |||
/// <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="count">Number of bytes in this frame. </param> | |||
void IDiscordVoiceClient.SendVoicePCM(byte[] data, int count) | |||
{ | |||
CheckReady(checkVoice: true); | |||
if (data == null) throw new ArgumentException(nameof(data)); | |||
if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); | |||
if (count == 0) return; | |||
_voiceSocket.SendPCMFrames(data, count); | |||
CheckReady(checkVoice: true); | |||
if (count != 0) | |||
_voiceSocket.SendPCMFrames(data, count); | |||
} | |||
/// <summary> Clears the PCM buffer. </summary> | |||
void IDiscordVoiceClient.ClearVoicePCM() |
@@ -1,11 +1,11 @@ | |||
using Discord.WebSockets.Data; | |||
using Discord.Net; | |||
using Discord.Net.WebSockets; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Runtime.ExceptionServices; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using VoiceWebSocket = Discord.WebSockets.Voice.VoiceWebSocket; | |||
namespace Discord | |||
{ | |||
@@ -17,30 +17,24 @@ namespace Discord | |||
Disconnecting | |||
} | |||
/// <summary> Provides a barebones connection to the Discord service </summary> | |||
public partial class DiscordSimpleClient | |||
/// <summary> Provides a minimalistic websocket connection to the Discord service. </summary> | |||
public partial class DiscordWSClient | |||
{ | |||
internal readonly DataWebSocket _dataSocket; | |||
internal readonly VoiceWebSocket _voiceSocket; | |||
protected readonly DiscordWSClientConfig _config; | |||
protected readonly ManualResetEvent _disconnectedEvent; | |||
protected readonly ManualResetEventSlim _connectedEvent; | |||
protected readonly bool _enableVoice; | |||
internal readonly DataWebSocket _dataSocket; | |||
internal readonly VoiceWebSocket _voiceSocket; | |||
protected ExceptionDispatchInfo _disconnectReason; | |||
protected string _gateway, _token; | |||
protected string _voiceServerId; | |||
protected string _userId, _voiceServerId; | |||
private Task _runTask; | |||
protected ExceptionDispatchInfo _disconnectReason; | |||
private bool _wasDisconnectUnexpected; | |||
/// <summary> Returns the configuration object used to make this client. Note that this object cannot be edited directly - to change the configuration of this client, use the DiscordClient(DiscordClientConfig config) constructor. </summary> | |||
public DiscordSimpleClientConfig Config => _config; | |||
protected readonly DiscordSimpleClientConfig _config; | |||
public string CurrentUserId => _userId; | |||
/// <summary> Returns the id of the current logged-in user. </summary> | |||
public string CurrentUserId => _currentUserId; | |||
private string _currentUserId; | |||
/*/// <summary> Returns the server this user is currently connected to for voice. </summary> | |||
public string CurrentVoiceServerId => _voiceSocket.CurrentServerId;*/ | |||
/// <summary> Returns the configuration object used to make this client. Note that this object cannot be edited directly - to change the configuration of this client, use the DiscordClient(DiscordClientConfig config) constructor. </summary> | |||
public DiscordWSClientConfig Config => _config; | |||
/// <summary> Returns the current connection state of this client. </summary> | |||
public DiscordClientState State => (DiscordClientState)_state; | |||
@@ -51,23 +45,21 @@ namespace Discord | |||
private CancellationToken _cancelToken; | |||
/// <summary> Initializes a new instance of the DiscordClient class. </summary> | |||
public DiscordSimpleClient(DiscordSimpleClientConfig config = null) | |||
public DiscordWSClient(DiscordWSClientConfig config = null) | |||
{ | |||
_config = config ?? new DiscordSimpleClientConfig(); | |||
_config = config ?? new DiscordWSClientConfig(); | |||
_config.Lock(); | |||
_enableVoice = _config.EnableVoice; | |||
_state = (int)DiscordClientState.Disconnected; | |||
_cancelToken = new CancellationToken(true); | |||
_disconnectedEvent = new ManualResetEvent(true); | |||
_connectedEvent = new ManualResetEventSlim(false); | |||
_dataSocket = CreateDataSocket(); | |||
if (_enableVoice) | |||
if (_config.EnableVoice) | |||
_voiceSocket = CreateVoiceSocket(); | |||
} | |||
internal DiscordSimpleClient(DiscordSimpleClientConfig config = null, string voiceServerId = null) | |||
internal DiscordWSClient(DiscordWSClientConfig config = null, string voiceServerId = null) | |||
: this(config) | |||
{ | |||
_voiceServerId = voiceServerId; | |||
@@ -85,7 +77,7 @@ namespace Discord | |||
{ | |||
RaiseDisconnected(e); | |||
if (e.WasUnexpected) | |||
await socket.Reconnect(_token); | |||
await socket.Reconnect(_token).ConfigureAwait(false); | |||
}; | |||
if (!_config.VoiceOnly) | |||
@@ -98,7 +90,7 @@ namespace Discord | |||
} | |||
} | |||
socket.ReceivedEvent += (s, e) => OnReceivedEvent(e); | |||
socket.ReceivedEvent += async (s, e) => await OnReceivedEvent(e).ConfigureAwait(false); | |||
return socket; | |||
} | |||
internal virtual VoiceWebSocket CreateVoiceSocket() | |||
@@ -110,7 +102,7 @@ namespace Discord | |||
{ | |||
RaiseVoiceDisconnected(socket.CurrentServerId, e); | |||
if (e.WasUnexpected) | |||
await socket.Reconnect(); | |||
await socket.Reconnect().ConfigureAwait(false); | |||
}; | |||
if (_config.LogLevel >= LogMessageSeverity.Info) | |||
{ | |||
@@ -220,7 +212,7 @@ namespace Discord | |||
catch (Exception ex) { await DisconnectInternal(ex: ex, skipAwait: true).ConfigureAwait(false); } | |||
//Ensure all other tasks are signaled to end. | |||
await DisconnectInternal(skipAwait: true); | |||
await DisconnectInternal(skipAwait: true).ConfigureAwait(false); | |||
//Wait for the remaining tasks to complete | |||
try { await allTasks.ConfigureAwait(false); } | |||
@@ -247,7 +239,7 @@ namespace Discord | |||
protected virtual async Task Cleanup() | |||
{ | |||
if (_enableVoice) | |||
if (_config.EnableVoice) | |||
{ | |||
string voiceServerId = _voiceSocket.CurrentServerId; | |||
if (voiceServerId != null) | |||
@@ -256,7 +248,7 @@ namespace Discord | |||
} | |||
await _dataSocket.Disconnect().ConfigureAwait(false); | |||
_currentUserId = null; | |||
_userId = null; | |||
_gateway = null; | |||
_token = null; | |||
} | |||
@@ -286,7 +278,7 @@ namespace Discord | |||
throw new InvalidOperationException("The client is connecting."); | |||
} | |||
if (checkVoice && !_enableVoice) | |||
if (checkVoice && !_config.EnableVoice) | |||
throw new InvalidOperationException("Voice is not enabled for this client."); | |||
} | |||
protected void RaiseEvent(string name, Action action) | |||
@@ -300,24 +292,24 @@ namespace Discord | |||
} | |||
} | |||
internal virtual Task OnReceivedEvent(WebSocketEventEventArgs e) | |||
internal virtual async Task OnReceivedEvent(WebSocketEventEventArgs e) | |||
{ | |||
try | |||
{ | |||
switch (e.Type) | |||
{ | |||
case "READY": | |||
_currentUserId = e.Payload["user"].Value<string>("id"); | |||
_userId = e.Payload["user"].Value<string>("id"); | |||
break; | |||
case "VOICE_SERVER_UPDATE": | |||
{ | |||
string guildId = e.Payload.Value<string>("guild_id"); | |||
if (_enableVoice && guildId == _voiceSocket.CurrentServerId) | |||
if (_config.EnableVoice && guildId == _voiceSocket.CurrentServerId) | |||
{ | |||
string token = e.Payload.Value<string>("token"); | |||
_voiceSocket.Host = "wss://" + e.Payload.Value<string>("endpoint").Split(':')[0]; | |||
return _voiceSocket.Login(_currentUserId, _dataSocket.SessionId, token, CancelToken); | |||
await _voiceSocket.Login(_userId, _dataSocket.SessionId, token, CancelToken).ConfigureAwait(false); | |||
} | |||
} | |||
break; | |||
@@ -327,7 +319,6 @@ namespace Discord | |||
{ | |||
RaiseOnLog(LogMessageSeverity.Error, LogMessageSource.Client, $"Error handling {e.Type} event: {ex.GetBaseException().Message}"); | |||
} | |||
return TaskHelper.CompletedTask; | |||
} | |||
} | |||
} |
@@ -12,12 +12,8 @@ namespace Discord | |||
Both = Outgoing | Incoming | |||
} | |||
public class DiscordSimpleClientConfig | |||
public class DiscordWSClientConfig : DiscordAPIClientConfig | |||
{ | |||
/// <summary> Specifies the minimum log level severity that will be sent to the LogMessage event. Warning: setting this to debug will really hurt performance but should help investigate any internal issues. </summary> | |||
public LogMessageSeverity LogLevel { get { return _logLevel; } set { SetValue(ref _logLevel, value); } } | |||
private LogMessageSeverity _logLevel = LogMessageSeverity.Info; | |||
/// <summary> Max time in milliseconds to wait for DiscordClient to connect and initialize. </summary> | |||
public int ConnectionTimeout { get { return _connectionTimeout; } set { SetValue(ref _connectionTimeout, value); } } | |||
private int _connectionTimeout = 30000; | |||
@@ -27,9 +23,6 @@ namespace Discord | |||
/// <summary> Gets or sets the time (in milliseconds) to wait after an reconnect fails before retrying. </summary> | |||
public int FailedReconnectDelay { get { return _failedReconnectDelay; } set { SetValue(ref _failedReconnectDelay, value); } } | |||
private int _failedReconnectDelay = 10000; | |||
/// <summary> Max time (in milliseconds) to wait for an API request to complete. </summary> | |||
public int APITimeout { get { return _apiTimeout; } set { SetValue(ref _apiTimeout, value); } } | |||
private int _apiTimeout = 10000; | |||
/// <summary> Gets or sets the time (in milliseconds) to wait when the websocket's message queue is empty before checking again. </summary> | |||
public int WebSocketInterval { get { return _webSocketInterval; } set { SetValue(ref _webSocketInterval, value); } } | |||
@@ -45,6 +38,9 @@ namespace Discord | |||
/// <summary> (Experimental) Enables the voice websocket and UDP client. This option requires the libsodium .dll or .so be in the local lib/ folder. </summary> | |||
public bool EnableVoiceEncryption { get { return _enableVoiceEncryption; } set { SetValue(ref _enableVoiceEncryption, value); } } | |||
private bool _enableVoiceEncryption = true; | |||
/// <summary> (Experimental) Instructs Discord to not send send information about offline users, for servers with more than 50 users. </summary> | |||
public bool UseLargeThreshold { get { return _useLargeThreshold; } set { SetValue(ref _useLargeThreshold, value); } } | |||
private bool _useLargeThreshold = false; | |||
//Internals | |||
internal bool VoiceOnly { get { return _voiceOnly; } set { SetValue(ref _voiceOnly, value); } } | |||
@@ -54,28 +50,9 @@ namespace Discord | |||
internal virtual bool EnableVoice => _voiceMode != DiscordVoiceMode.Disabled; | |||
internal string UserAgent | |||
{ | |||
get | |||
{ | |||
string version = typeof(DiscordClientConfig).GetTypeInfo().Assembly.GetName().Version.ToString(2); | |||
return $"Discord.Net/{version} (https://github.com/RogueException/Discord.Net)"; | |||
} | |||
} | |||
//Lock | |||
protected bool _isLocked; | |||
internal void Lock() { _isLocked = true; } | |||
protected void SetValue<T>(ref T storage, T value) | |||
{ | |||
if (_isLocked) | |||
throw new InvalidOperationException("Unable to modify a discord client's configuration after it has been created."); | |||
storage = value; | |||
} | |||
public DiscordClientConfig Clone() | |||
public new DiscordWSClientConfig Clone() | |||
{ | |||
var config = this.MemberwiseClone() as DiscordClientConfig; | |||
var config = MemberwiseClone() as DiscordWSClientConfig; | |||
config._isLocked = false; | |||
return config; | |||
} |
@@ -1,10 +0,0 @@ | |||
namespace Discord | |||
{ | |||
public static class ChannelTypes | |||
{ | |||
/// <summary> A text-only channel. </summary> | |||
public const string Text = "text"; | |||
/// <summary> A voice-only channel. </summary> | |||
public const string Voice = "voice"; | |||
} | |||
} |
@@ -1,8 +0,0 @@ | |||
namespace Discord | |||
{ | |||
public static class PermissionTarget | |||
{ | |||
public const string Role = "role"; | |||
public const string Member = "member"; | |||
} | |||
} |
@@ -1,12 +0,0 @@ | |||
namespace Discord | |||
{ | |||
public static class Regions | |||
{ | |||
public const string US_West = "us-west"; | |||
public const string US_East = "us-east"; | |||
public const string Singapore = "singapore"; | |||
public const string London = "london"; | |||
public const string Sydney = "sydney"; | |||
public const string Amsterdam = "amsterdam"; | |||
} | |||
} |
@@ -1,12 +0,0 @@ | |||
namespace Discord | |||
{ | |||
public static class UserStatus | |||
{ | |||
/// <summary> User is currently online and active. </summary> | |||
public const string Online = "online"; | |||
/// <summary> User is currently online but inactive. </summary> | |||
public const string Away = "away"; | |||
/// <summary> User is offline. </summary> | |||
public const string Offline = "offline"; | |||
} | |||
} |
@@ -4,19 +4,19 @@ using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
namespace Discord.Collections | |||
namespace Discord | |||
{ | |||
public abstract class AsyncCollection<TValue> : IEnumerable<TValue> | |||
where TValue : class | |||
internal abstract class AsyncCollection<TValue> : IEnumerable<TValue> | |||
where TValue : CachedObject | |||
{ | |||
private readonly object _writerLock; | |||
internal class CollectionItemEventArgs : EventArgs | |||
public class CollectionItemEventArgs : EventArgs | |||
{ | |||
public TValue Item { get; } | |||
public CollectionItemEventArgs(TValue item) { Item = item; } | |||
} | |||
internal class CollectionItemRemappedEventArgs : EventArgs | |||
public class CollectionItemRemappedEventArgs : EventArgs | |||
{ | |||
public TValue Item { get; } | |||
public string OldId { get; } | |||
@@ -24,26 +24,26 @@ namespace Discord.Collections | |||
public CollectionItemRemappedEventArgs(TValue item, string oldId, string newId) { Item = item; OldId = oldId; NewId = newId; } | |||
} | |||
internal EventHandler<CollectionItemEventArgs> ItemCreated; | |||
public EventHandler<CollectionItemEventArgs> ItemCreated; | |||
private void RaiseItemCreated(TValue item) | |||
{ | |||
if (ItemCreated != null) | |||
ItemCreated(this, new CollectionItemEventArgs(item)); | |||
} | |||
internal EventHandler<CollectionItemEventArgs> ItemDestroyed; | |||
public EventHandler<CollectionItemEventArgs> ItemDestroyed; | |||
private void RaiseItemDestroyed(TValue item) | |||
{ | |||
if (ItemDestroyed != null) | |||
ItemDestroyed(this, new CollectionItemEventArgs(item)); | |||
} | |||
internal EventHandler<CollectionItemRemappedEventArgs> ItemRemapped; | |||
public EventHandler<CollectionItemRemappedEventArgs> ItemRemapped; | |||
private void RaiseItemRemapped(TValue item, string oldId, string newId) | |||
{ | |||
if (ItemRemapped != null) | |||
ItemRemapped(this, new CollectionItemRemappedEventArgs(item, oldId, newId)); | |||
} | |||
internal EventHandler Cleared; | |||
public EventHandler Cleared; | |||
private void RaiseCleared() | |||
{ | |||
if (Cleared != null) | |||
@@ -58,17 +58,20 @@ namespace Discord.Collections | |||
_client = client; | |||
_writerLock = writerLock; | |||
_dictionary = new ConcurrentDictionary<string, TValue>(); | |||
} | |||
} | |||
protected TValue Get(string key) | |||
public TValue this[string key] | |||
{ | |||
if (key == null) | |||
return null; | |||
get | |||
{ | |||
if (key == null) | |||
return null; | |||
TValue result; | |||
if (!_dictionary.TryGetValue(key, out result)) | |||
return null; | |||
return result; | |||
TValue result; | |||
if (!_dictionary.TryGetValue(key, out result)) | |||
return null; | |||
return result; | |||
} | |||
} | |||
protected TValue GetOrAdd(string key, Func<TValue> createFunc) | |||
{ | |||
@@ -82,13 +85,13 @@ namespace Discord.Collections | |||
result = _dictionary.GetOrAdd(key, newItem); | |||
if (result == newItem) | |||
{ | |||
OnCreated(newItem); | |||
result.Cache(); | |||
RaiseItemCreated(result); | |||
} | |||
} | |||
return result; | |||
} | |||
protected TValue TryRemove(string key) | |||
public TValue TryRemove(string key) | |||
{ | |||
if (_dictionary.ContainsKey(key)) | |||
{ | |||
@@ -97,14 +100,14 @@ namespace Discord.Collections | |||
TValue result; | |||
if (_dictionary.TryRemove(key, out result)) | |||
{ | |||
OnRemoved(result); //TODO: If this object is accessed before OnRemoved finished firing, properties such as Server.Channels will have null elements | |||
result.Uncache(); //TODO: If this object is accessed before OnRemoved finished firing, properties such as Server.Channels will have null elements | |||
return result; | |||
} | |||
} | |||
} | |||
return null; | |||
} | |||
protected TValue Remap(string oldKey, string newKey) | |||
public TValue Remap(string oldKey, string newKey) | |||
{ | |||
if (_dictionary.ContainsKey(oldKey)) | |||
{ | |||
@@ -118,25 +121,16 @@ namespace Discord.Collections | |||
} | |||
return null; | |||
} | |||
protected internal void Clear() | |||
public void Clear() | |||
{ | |||
lock (_writerLock) | |||
{ | |||
_dictionary.Clear(); | |||
RaiseCleared(); | |||
RaiseCleared(); | |||
} | |||
} | |||
protected abstract void OnCreated(TValue item); | |||
protected abstract void OnRemoved(TValue item); | |||
public IEnumerator<TValue> GetEnumerator() | |||
{ | |||
return _dictionary.Select(x => x.Value).GetEnumerator(); | |||
} | |||
IEnumerator IEnumerable.GetEnumerator() | |||
{ | |||
return GetEnumerator(); | |||
} | |||
public IEnumerator<TValue> GetEnumerator() => _dictionary.Select(x => x.Value).GetEnumerator(); | |||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); | |||
} | |||
} |
@@ -0,0 +1,20 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
namespace Discord | |||
{ | |||
internal static class BitHelper | |||
{ | |||
public static bool GetBit(uint value, int pos) => ((value >> (byte)pos) & 1U) == 1; | |||
public static void SetBit(ref uint value, int pos, bool bitValue) | |||
{ | |||
if (bitValue) | |||
value |= (1U << pos); | |||
else | |||
value &= ~(1U << pos); | |||
} | |||
} | |||
} |
@@ -0,0 +1,37 @@ | |||
namespace Discord | |||
{ | |||
public abstract class CachedObject | |||
{ | |||
protected readonly DiscordClient _client; | |||
private bool _isCached; | |||
internal bool IsCached => _isCached; | |||
internal CachedObject(DiscordClient client, string id) | |||
{ | |||
_client = client; | |||
Id = id; | |||
} | |||
/// <summary> Returns the unique identifier for this object. </summary> | |||
public string Id { get; internal set; } | |||
public override string ToString() => $"{this.GetType().Name} {Id}"; | |||
internal void Cache() | |||
{ | |||
LoadReferences(); | |||
_isCached = true; | |||
} | |||
internal void Uncache() | |||
{ | |||
if (_isCached) | |||
{ | |||
UnloadReferences(); | |||
_isCached = false; | |||
} | |||
} | |||
internal abstract void LoadReferences(); | |||
internal abstract void UnloadReferences(); | |||
} | |||
} |
@@ -1,55 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
namespace Discord | |||
{ | |||
internal static class CollectionHelper | |||
{ | |||
public static IEnumerable<string> FlattenChannels(IEnumerable<object> channels) | |||
{ | |||
if (channels == null) | |||
return new string[0]; | |||
return channels.Select(x => | |||
{ | |||
if (x is string) | |||
return x as string; | |||
else if (x is Channel) | |||
return (x as Channel).Id; | |||
else | |||
throw new ArgumentException("Collection may only contain string or Channel.", nameof(channels)); | |||
}); | |||
} | |||
public static IEnumerable<string> FlattenUsers(IEnumerable<object> users) | |||
{ | |||
if (users == null) | |||
return new string[0]; | |||
return users.Select(x => | |||
{ | |||
if (x is string) | |||
return x as string; | |||
else if (x is User) | |||
return (x as User).Id; | |||
else | |||
throw new ArgumentException("Collection may only contain string or User.", nameof(users)); | |||
}); | |||
} | |||
public static IEnumerable<string> FlattenRoles(IEnumerable<object> roles) | |||
{ | |||
if (roles == null) | |||
return new string[0]; | |||
return roles.Select(x => | |||
{ | |||
if (x is string) | |||
return x as string; | |||
else if (x is Role) | |||
return (x as Role).Id; | |||
else | |||
throw new ArgumentException("Collection may only contain string or Role.", nameof(roles)); | |||
}); | |||
} | |||
} | |||
} |
@@ -6,6 +6,53 @@ namespace Discord | |||
{ | |||
internal static class Extensions | |||
{ | |||
public static async Task Timeout(this Task self, int milliseconds) | |||
{ | |||
Task timeoutTask = Task.Delay(milliseconds); | |||
Task finishedTask = await Task.WhenAny(self, timeoutTask).ConfigureAwait(false); | |||
if (finishedTask == timeoutTask) | |||
throw new TimeoutException(); | |||
else | |||
await self.ConfigureAwait(false); | |||
} | |||
public static async Task<T> Timeout<T>(this Task<T> self, int milliseconds) | |||
{ | |||
Task timeoutTask = Task.Delay(milliseconds); | |||
Task finishedTask = await Task.WhenAny(self, timeoutTask).ConfigureAwait(false); | |||
if (finishedTask == timeoutTask) | |||
throw new TimeoutException(); | |||
else | |||
return await self.ConfigureAwait(false); | |||
} | |||
public static async Task Timeout(this Task self, int milliseconds, CancellationTokenSource timeoutToken) | |||
{ | |||
try | |||
{ | |||
timeoutToken.CancelAfter(milliseconds); | |||
await self.ConfigureAwait(false); | |||
} | |||
catch (OperationCanceledException) | |||
{ | |||
if (timeoutToken.IsCancellationRequested) | |||
throw new TimeoutException(); | |||
throw; | |||
} | |||
} | |||
public static async Task<T> Timeout<T>(this Task<T> self, int milliseconds, CancellationTokenSource timeoutToken) | |||
{ | |||
try | |||
{ | |||
timeoutToken.CancelAfter(milliseconds); | |||
return await self.ConfigureAwait(false); | |||
} | |||
catch (OperationCanceledException) | |||
{ | |||
if (timeoutToken.IsCancellationRequested) | |||
throw new TimeoutException(); | |||
throw; | |||
} | |||
} | |||
public static async Task Wait(this CancellationTokenSource tokenSource) | |||
{ | |||
var token = tokenSource.Token; | |||
@@ -102,12 +102,12 @@ namespace Discord | |||
=> escape ? $"~~{Escape(text)}~~" : $"~~{text}~~"; | |||
/// <summary> Returns a markdown-formatted string with strikeout formatting, optionally escaping the contents. </summary> | |||
public static string Code(string text, string language = null, bool escape = true) | |||
public static string Code(string text, string language = null) | |||
{ | |||
if (language != null || text.Contains("\n")) | |||
return $"```{language ?? ""}\n{(escape ? Escape(text) : text)}\n```"; | |||
return $"```{language ?? ""}\n{text}\n```"; | |||
else | |||
return $"`{(escape ? Escape(text) : text)}`"; | |||
return $"`{text}`"; | |||
} | |||
/// <summary> Returns a markdown-formatted string with multiple formatting, optionally escaping the contents. </summary> | |||
@@ -121,7 +121,7 @@ namespace Discord | |||
if (italics) result = Italics(result, false); | |||
if (underline) result = Underline(result, false); | |||
if (strikeout) result = Strikeout(result, false); | |||
if (code) result = Code(result, codeLanguage, false); | |||
if (code) result = Code(result, codeLanguage); | |||
return result; | |||
} | |||
} |
@@ -0,0 +1,53 @@ | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text.RegularExpressions; | |||
namespace Discord | |||
{ | |||
public static class Mention | |||
{ | |||
private static readonly Regex _userRegex = new Regex(@"<@(?:[0-9]+?)>", RegexOptions.Compiled); | |||
private static readonly Regex _channelRegex = new Regex(@"<#(?:[0-9]+?)>", RegexOptions.Compiled); | |||
/// <summary> Returns the string used to create a user mention. </summary> | |||
public static string User(User user) | |||
=> $"<@{user.Id}>"; | |||
/// <summary> Returns the string used to create a channel mention. </summary> | |||
public static string Channel(Channel channel) | |||
=> $"<#{channel.Id}>"; | |||
/// <summary> Returns the string used to create a channel mention. </summary> | |||
public static string Everyone() | |||
=> $"@everyone"; | |||
internal static string ConvertToNames(DiscordClient client, Server server, string text) | |||
{ | |||
text = _userRegex.Replace(text, new MatchEvaluator(e => | |||
{ | |||
string id = e.Value.Substring(2, e.Value.Length - 3); | |||
var user = client.Users[id, server.Id]; | |||
if (user != null) | |||
return '@' + user.Name; | |||
else //User not found | |||
return '@' + e.Value; | |||
})); | |||
text = _channelRegex.Replace(text, new MatchEvaluator(e => | |||
{ | |||
string id = e.Value.Substring(2, e.Value.Length - 3); | |||
var channel = client.Channels[id]; | |||
if (channel != null && channel.Server.Id == server.Id) | |||
return '#' + channel.Name; | |||
else //Channel not found | |||
return '#' + e.Value; | |||
})); | |||
return text; | |||
} | |||
internal static IEnumerable<string> GetUserIds(string text) | |||
{ | |||
return _userRegex.Matches(text) | |||
.OfType<Match>() | |||
.Select(x => x.Groups[1].Value) | |||
.Where(x => x != null); | |||
} | |||
} | |||
} |
@@ -1,43 +0,0 @@ | |||
using System.Text.RegularExpressions; | |||
namespace Discord | |||
{ | |||
//TODO: Better name please? | |||
internal class MessageCleaner | |||
{ | |||
private readonly Regex _userRegex, _channelRegex; | |||
private readonly MatchEvaluator _userRegexEvaluator, _channelRegexEvaluator; | |||
public MessageCleaner(DiscordClient client) | |||
{ | |||
_userRegex = new Regex(@"<@\d+?>", RegexOptions.Compiled); | |||
_userRegexEvaluator = new MatchEvaluator(e => | |||
{ | |||
string id = e.Value.Substring(2, e.Value.Length - 3); | |||
var user = client.Users[id]; | |||
if (user != null) | |||
return '@' + user.Name; | |||
else //User not found | |||
return e.Value; | |||
}); | |||
_channelRegex = new Regex(@"<#\d+?>", RegexOptions.Compiled); | |||
_channelRegexEvaluator = new MatchEvaluator(e => | |||
{ | |||
string id = e.Value.Substring(2, e.Value.Length - 3); | |||
var channel = client.Channels[id]; | |||
if (channel != null) | |||
return channel.Name; | |||
else //Channel not found | |||
return e.Value; | |||
}); | |||
} | |||
public string Clean(string text) | |||
{ | |||
text = _userRegex.Replace(text, _userRegexEvaluator); | |||
text = _channelRegex.Replace(text, _channelRegexEvaluator); | |||
return text; | |||
} | |||
} | |||
} |
@@ -0,0 +1,68 @@ | |||
using System; | |||
namespace Discord | |||
{ | |||
internal class Reference<T> | |||
where T : CachedObject | |||
{ | |||
private Action<T> _onCache, _onUncache; | |||
private Func<string, T> _getItem; | |||
private string _id; | |||
public string Id | |||
{ | |||
get { return _id; } | |||
set | |||
{ | |||
_id = value; | |||
_value = null; | |||
} | |||
} | |||
private T _value; | |||
public T Value | |||
{ | |||
get | |||
{ | |||
var v = _value; //A little trickery to make this threadsafe | |||
if (v != null && !_value.IsCached) | |||
{ | |||
v = null; | |||
_value = null; | |||
} | |||
if (v == null && _id != null) | |||
{ | |||
v = _getItem(_id); | |||
if (v != null && _onCache != null) | |||
_onCache(v); | |||
_value = v; | |||
} | |||
return v; | |||
} | |||
} | |||
public T Load() | |||
{ | |||
return Value; //Used for precaching | |||
} | |||
public void Unload() | |||
{ | |||
if (_onUncache != null) | |||
{ | |||
var v = _value; | |||
if (v != null && _onUncache != null) | |||
_onUncache(v); | |||
} | |||
} | |||
public Reference(Func<string, T> onUpdate, Action<T> onCache = null, Action<T> onUncache = null) | |||
: this(null, onUpdate, onCache, onUncache) { } | |||
public Reference(string id, Func<string, T> getItem, Action<T> onCache = null, Action<T> onUncache = null) | |||
{ | |||
_id = id; | |||
_getItem = getItem; | |||
_onCache = onCache; | |||
_onUncache = onUncache; | |||
} | |||
} | |||
} |
@@ -1,56 +0,0 @@ | |||
using System; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
namespace Discord | |||
{ | |||
public static class TaskExtensions | |||
{ | |||
public static async Task Timeout(this Task self, int milliseconds) | |||
{ | |||
Task timeoutTask = Task.Delay(milliseconds); | |||
Task finishedTask = await Task.WhenAny(self, timeoutTask); | |||
if (finishedTask == timeoutTask) | |||
throw new TimeoutException(); | |||
else | |||
await self; | |||
} | |||
public static async Task<T> Timeout<T>(this Task<T> self, int milliseconds) | |||
{ | |||
Task timeoutTask = Task.Delay(milliseconds); | |||
Task finishedTask = await Task.WhenAny(self, timeoutTask).ConfigureAwait(false); | |||
if (finishedTask == timeoutTask) | |||
throw new TimeoutException(); | |||
else | |||
return await self.ConfigureAwait(false); | |||
} | |||
public static async Task Timeout(this Task self, int milliseconds, CancellationTokenSource timeoutToken) | |||
{ | |||
try | |||
{ | |||
timeoutToken.CancelAfter(milliseconds); | |||
await self.ConfigureAwait(false); | |||
} | |||
catch (OperationCanceledException) | |||
{ | |||
if (timeoutToken.IsCancellationRequested) | |||
throw new TimeoutException(); | |||
throw; | |||
} | |||
} | |||
public static async Task<T> Timeout<T>(this Task<T> self, int milliseconds, CancellationTokenSource timeoutToken) | |||
{ | |||
try | |||
{ | |||
timeoutToken.CancelAfter(milliseconds); | |||
return await self.ConfigureAwait(false); | |||
} | |||
catch (OperationCanceledException) | |||
{ | |||
if (timeoutToken.IsCancellationRequested) | |||
throw new TimeoutException(); | |||
throw; | |||
} | |||
} | |||
} | |||
} |
@@ -2,16 +2,12 @@ | |||
namespace Discord | |||
{ | |||
internal static class TaskHelper | |||
public static class TaskHelper | |||
{ | |||
public static Task CompletedTask { get; } | |||
static TaskHelper() | |||
{ | |||
#if DNXCORE50 | |||
CompletedTask = Task.CompletedTask; | |||
#else | |||
CompletedTask = Task.Delay(0); | |||
#endif | |||
} | |||
} | |||
} |
@@ -1,14 +1,14 @@ | |||
using System; | |||
using System.Net; | |||
namespace Discord.API | |||
namespace Discord | |||
{ | |||
public class HttpException : Exception | |||
{ | |||
public HttpStatusCode StatusCode { get; } | |||
public HttpException(HttpStatusCode statusCode) | |||
: base($"The server responded with error {statusCode}") | |||
: base($"The server responded with error {(int)statusCode} ({statusCode})") | |||
{ | |||
StatusCode = statusCode; | |||
} |
@@ -1,22 +0,0 @@ | |||
namespace Discord | |||
{ | |||
public static class Mention | |||
{ | |||
/// <summary> Returns the string used to create a user mention. </summary> | |||
public static string User(User user) | |||
=> $"<@{user.Id}>"; | |||
/// <summary> Returns the string used to create a user mention. </summary> | |||
public static string User(Member member) | |||
=> $"<@{member.UserId}>"; | |||
/// <summary> Returns the string used to create a user mention. </summary> | |||
public static string User(string userId) | |||
=> $"<@{userId}>"; | |||
/// <summary> Returns the string used to create a channel mention. </summary> | |||
public static string Channel(Channel channel) | |||
=> $"<#{channel.Id}>"; | |||
/// <summary> Returns the string used to create a channel mention. </summary> | |||
public static string Channel(string channelId) | |||
=> $"<#{channelId}>"; | |||
} | |||
} |
@@ -1,114 +1,131 @@ | |||
using Newtonsoft.Json; | |||
using Discord.API; | |||
using Newtonsoft.Json; | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
namespace Discord | |||
{ | |||
public sealed class Channel | |||
public sealed class Channel : CachedObject | |||
{ | |||
public sealed class PermissionOverwrite | |||
{ | |||
public string TargetType { get; } | |||
public PermissionTarget TargetType { get; } | |||
public string TargetId { get; } | |||
public PackedChannelPermissions Allow { get; } | |||
public PackedChannelPermissions Deny { get; } | |||
public ChannelPermissions Allow { get; } | |||
public ChannelPermissions Deny { get; } | |||
internal PermissionOverwrite(string type, string targetId, uint allow, uint deny) | |||
internal PermissionOverwrite(PermissionTarget targetType, string targetId, uint allow, uint deny) | |||
{ | |||
TargetType = type; | |||
TargetType = targetType; | |||
TargetId = targetId; | |||
Allow = new PackedChannelPermissions(allow); | |||
Deny = new PackedChannelPermissions( deny); | |||
Allow = new ChannelPermissions(allow); | |||
Allow.Lock(); | |||
Deny = new ChannelPermissions(deny); | |||
Deny.Lock(); | |||
} | |||
} | |||
private readonly DiscordClient _client; | |||
private readonly ConcurrentDictionary<string, bool> _messages; | |||
private bool _areMembersStale; | |||
/// <summary> Returns the unique identifier for this channel. </summary> | |||
public string Id { get; } | |||
private string _name; | |||
/// <summary> Returns the name of this channel. </summary> | |||
public string Name { get { return !IsPrivate ? $"{_name}" : $"@{Recipient.Name}"; } internal set { _name = value; } } | |||
public string Name { get; private set; } | |||
/// <summary> Returns the topic associated with this channel. </summary> | |||
public string Topic { get; private set; } | |||
/// <summary> Returns the position of this channel in the channel list for this server. </summary> | |||
public int Position { get; private set; } | |||
/// <summary> Returns false is this is a public chat and true if this is a private chat with another user (see Recipient). </summary> | |||
public bool IsPrivate => ServerId == null; | |||
public bool IsPrivate => _recipient.Id != null; | |||
/// <summary> Returns the type of this channel (see ChannelTypes). </summary> | |||
public string Type { get; private set; } | |||
/// <summary> Returns the id of the server containing this channel. </summary> | |||
public string ServerId { get; } | |||
/// <summary> Returns the server containing this channel. </summary> | |||
[JsonIgnore] | |||
public Server Server => _client.Servers[ServerId]; | |||
public Server Server => _server.Value; | |||
private readonly Reference<Server> _server; | |||
/// For private chats, returns the Id of the target user, otherwise null. | |||
public string RecipientId { get; set; } | |||
/// For private chats, returns the target user, otherwise null. | |||
[JsonIgnore] | |||
public User Recipient => _client.Users[RecipientId]; | |||
/// <summary> Returns a collection of the IDs of all users with read access to this channel. </summary> | |||
public IEnumerable<string> UserIds | |||
public User Recipient => _recipient.Value; | |||
private readonly Reference<User> _recipient; | |||
/// <summary> Returns a collection of all users with read access to this channel. </summary> | |||
[JsonIgnore] | |||
public IEnumerable<User> Members | |||
{ | |||
get | |||
{ | |||
if (!_areMembersStale) | |||
return _userIds; | |||
_userIds = Server.Members.Where(x => x.GetPermissions(Id)?.Text_ReadMessages ?? false).Select(x => x.UserId).ToArray(); | |||
_areMembersStale = false; | |||
return _userIds; | |||
} | |||
if (_areMembersStale) | |||
UpdateMembersCache(); | |||
return _members.Select(x => x.Value); | |||
} | |||
} | |||
private string[] _userIds; | |||
/// <summary> Returns a collection of all users with read access to this channel. </summary> | |||
public IEnumerable<Member> Members => UserIds.Select(x => _client.Members[x, ServerId]); | |||
/// <summary> Returns a collection of all users with read access to this channel. </summary> | |||
public IEnumerable<User> Users => UserIds.Select(x => _client.Users[x]); | |||
private Dictionary<string, User> _members; | |||
private bool _areMembersStale; | |||
/// <summary> Returns a collection of the ids of all messages the client has seen posted in this channel. This collection does not guarantee any ordering. </summary> | |||
[JsonIgnore] | |||
public IEnumerable<string> MessageIds => _messages.Select(x => x.Key); | |||
/// <summary> Returns a collection of all messages the client has seen posted in this channel. This collection does not guarantee any ordering. </summary> | |||
[JsonIgnore] | |||
public IEnumerable<Message> Messages => _messages.Select(x => _client.Messages[x.Key]); | |||
public IEnumerable<Message> Messages => _messages.Values; | |||
private readonly ConcurrentDictionary<string, Message> _messages; | |||
/// <summary> Returns a collection of all custom permissions used for this channel. </summary> | |||
private static readonly PermissionOverwrite[] _initialPermissionsOverwrites = new PermissionOverwrite[0]; | |||
internal PermissionOverwrite[] _permissionOverwrites; | |||
public IEnumerable<PermissionOverwrite> PermissionOverwrites => _permissionOverwrites; | |||
private PermissionOverwrite[] _permissionOverwrites; | |||
public IEnumerable<PermissionOverwrite> PermissionOverwrites { get { return _permissionOverwrites; } internal set { _permissionOverwrites = value.ToArray(); } } | |||
internal Channel(DiscordClient client, string id, string serverId, string recipientId) | |||
: base(client, id) | |||
{ | |||
_client = client; | |||
Id = id; | |||
ServerId = serverId; | |||
RecipientId = recipientId; | |||
_messages = new ConcurrentDictionary<string, bool>(); | |||
_server = new Reference<Server>(serverId, | |||
x => _client.Servers[x], | |||
x => x.AddChannel(this), | |||
x => x.RemoveChannel(this)); | |||
_recipient = new Reference<User>(recipientId, | |||
x => _client.Users.GetOrAdd(x, _server.Id), | |||
x => | |||
{ | |||
Name = "@" + x.Name; | |||
if (_server.Id == null) | |||
x.GlobalUser.PrivateChannel = this; | |||
}, | |||
x => | |||
{ | |||
if (_server.Id == null) | |||
x.GlobalUser.PrivateChannel = null; | |||
}); | |||
_permissionOverwrites = _initialPermissionsOverwrites; | |||
_areMembersStale = true; | |||
} | |||
internal void Update(API.ChannelReference model) | |||
//Local Cache | |||
_messages = new ConcurrentDictionary<string, Message>(); | |||
} | |||
internal override void LoadReferences() | |||
{ | |||
if (IsPrivate) | |||
_recipient.Load(); | |||
else | |||
_server.Load(); | |||
} | |||
internal override void UnloadReferences() | |||
{ | |||
_server.Unload(); | |||
_recipient.Unload(); | |||
var globalMessages = _client.Messages; | |||
var messages = _messages; | |||
foreach (var message in messages) | |||
globalMessages.TryRemove(message.Key); | |||
_messages.Clear(); | |||
} | |||
internal void Update(ChannelReference model) | |||
{ | |||
if (model.Name != null) | |||
if (!IsPrivate && model.Name != null) | |||
Name = model.Name; | |||
if (model.Type != null) | |||
Type = model.Type; | |||
} | |||
internal void Update(API.ChannelInfo model) | |||
internal void Update(ChannelInfo model) | |||
{ | |||
Update(model as API.ChannelReference); | |||
Update(model as ChannelReference); | |||
if (model.Position != null) | |||
Position = model.Position.Value; | |||
@@ -118,7 +135,7 @@ namespace Discord | |||
if (model.PermissionOverwrites != null) | |||
{ | |||
_permissionOverwrites = model.PermissionOverwrites | |||
.Select(x => new PermissionOverwrite(x.Type, x.Id, x.Allow, x.Deny)) | |||
.Select(x => new PermissionOverwrite(PermissionTarget.FromString(x.Type), x.Id, x.Allow, x.Deny)) | |||
.ToArray(); | |||
InvalidatePermissionsCache(); | |||
} | |||
@@ -126,32 +143,54 @@ namespace Discord | |||
public override string ToString() => Name; | |||
internal void AddMessage(string messageId) | |||
internal void AddMessage(Message message) | |||
{ | |||
_messages.TryAdd(messageId, true); | |||
var cacheLength = _client.Config.MessageCacheLength; | |||
if (cacheLength > 0) | |||
{ | |||
while (_messages.Count > cacheLength - 1) | |||
{ | |||
var oldest = _messages.Select(x => x.Value.Id).OrderBy(x => x).FirstOrDefault(); | |||
if (oldest != null) | |||
{ | |||
if (_messages.TryRemove(oldest, out message)) | |||
_client.Messages.TryRemove(oldest); | |||
} | |||
} | |||
_messages.TryAdd(message.Id, message); | |||
} | |||
} | |||
internal bool RemoveMessage(string messageId) | |||
internal void RemoveMessage(Message message) => _messages.TryRemove(message.Id, out message); | |||
internal void InvalidateMembersCache() | |||
{ | |||
bool ignored; | |||
return _messages.TryRemove(messageId, out ignored); | |||
_areMembersStale = true; | |||
} | |||
private void UpdateMembersCache() | |||
{ | |||
if (_server.Id != null) | |||
_members = Server.Members.Where(x => x.GetPermissions(this)?.ReadMessages ?? false).ToDictionary(x => x.Id, x => x); | |||
else | |||
_members = new Dictionary<string, User>(); | |||
_areMembersStale = false; | |||
} | |||
internal void InvalidMembersCache() | |||
internal void InvalidatePermissionsCache() | |||
{ | |||
_areMembersStale = true; | |||
UpdateMembersCache(); | |||
foreach (var member in _members) | |||
member.Value.UpdateChannelPermissions(this); | |||
} | |||
internal void InvalidatePermissionsCache() | |||
internal void InvalidatePermissionsCache(Role role) | |||
{ | |||
_areMembersStale = true; | |||
foreach (var member in Members) | |||
member.UpdatePermissions(Id); | |||
foreach (var member in role.Members) | |||
member.UpdateChannelPermissions(this); | |||
} | |||
internal void InvalidatePermissionsCache(string userId) | |||
internal void InvalidatePermissionsCache(User user) | |||
{ | |||
_areMembersStale = true; | |||
var member = _client.Members[userId, ServerId]; | |||
if (member != null) | |||
member.UpdatePermissions(Id); | |||
user.UpdateChannelPermissions(this); | |||
} | |||
} | |||
} |
@@ -0,0 +1,81 @@ | |||
using System; | |||
namespace Discord | |||
{ | |||
public class Color | |||
{ | |||
public static readonly Color Default = PresetColor(0); | |||
public static readonly Color Cyan = PresetColor(0x1abc9c); | |||
public static readonly Color DarkCyan = PresetColor(0x11806a); | |||
public static readonly Color Green = PresetColor(0x2ecc71); | |||
public static readonly Color DarkGreen = PresetColor(0x1f8b4c); | |||
public static readonly Color Blue = PresetColor(0x3498db); | |||
public static readonly Color DarkBlue = PresetColor(0x206694); | |||
public static readonly Color Purple = PresetColor(0x9b59b6); | |||
public static readonly Color DarkPurple = PresetColor(0x71368a); | |||
public static readonly Color Red = PresetColor(0xe74c3c); | |||
public static readonly Color DarkRed = PresetColor(0x992d22); | |||
public static readonly Color Orange = PresetColor(0xe67e22); | |||
public static readonly Color DarkOrange = PresetColor(0xa84300); | |||
public static readonly Color Navy = PresetColor(0x34495e); | |||
public static readonly Color DarkNavy = PresetColor(0x2c3e50); | |||
public static readonly Color Gold = PresetColor(0xf1c40f); | |||
public static readonly Color DarkGold = PresetColor(0xc27c0e); | |||
public static readonly Color LighterGrey = PresetColor(0xbcc0c0); | |||
public static readonly Color LightGrey = PresetColor(0x95a5a6); | |||
public static readonly Color DarkGrey = PresetColor(0x979c9f); | |||
public static readonly Color DarkerGrey = PresetColor(0x7f8c8d); | |||
private static Color PresetColor(uint packedValue) | |||
{ | |||
Color color = new Color(packedValue); | |||
color.Lock(); | |||
return color; | |||
} | |||
private bool _isLocked; | |||
private uint _rawValue; | |||
public uint RawValue | |||
{ | |||
get { return _rawValue; } | |||
set | |||
{ | |||
if (_isLocked) | |||
throw new InvalidOperationException("Unable to edit cached colors directly, use Copy() to make an editable copy."); | |||
_rawValue = value; | |||
} | |||
} | |||
public Color(uint rawValue) { _rawValue = rawValue; } | |||
public Color(byte r, byte g, byte b) : this(((uint)r << 16) | ((uint)g << 8) | b) { } | |||
public Color(float r, float g, float b) : this((byte)(r * 255.0f), (byte)(g * 255.0f), (byte)(b * 255.0f)) { } | |||
/// <summary> Gets or sets the red component for this color. </summary> | |||
public byte R { get { return GetByte(3); } set { SetByte(3, value); } } | |||
/// <summary> Gets or sets the green component for this color. </summary> | |||
public byte G { get { return GetByte(2); } set { SetByte(2, value); } } | |||
/// <summary> Gets or sets the blue component for this color. </summary> | |||
public byte B { get { return GetByte(1); } set { SetByte(1, value); } } | |||
internal void Lock() => _isLocked = true; | |||
internal void SetRawValue(uint rawValue) | |||
{ | |||
//Bypasses isLocked for API changes. | |||
_rawValue = rawValue; | |||
} | |||
protected byte GetByte(int pos) => (byte)((_rawValue >> (8 * (pos - 1))) & 0xFF); | |||
protected void SetByte(int pos, byte value) | |||
{ | |||
if (_isLocked) | |||
throw new InvalidOperationException("Unable to edit cached colors directly, use Copy() to make an editable copy."); | |||
uint original = _rawValue; | |||
int bit = 8 * (pos - 1); | |||
uint mask = (uint)~(0xFF << bit); | |||
_rawValue = (_rawValue & mask) | ((uint)value << bit); | |||
} | |||
} | |||
} |
@@ -0,0 +1,74 @@ | |||
using Discord.API; | |||
using Newtonsoft.Json; | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading; | |||
namespace Discord | |||
{ | |||
internal sealed class GlobalUser : CachedObject | |||
{ | |||
private readonly ConcurrentDictionary<string, User> _users; | |||
/// <summary> Returns the email for this user. </summary> | |||
/// <remarks> This field is only ever populated for the current logged in user. </remarks> | |||
[JsonIgnore] | |||
public string Email { get; private set; } | |||
/// <summary> Returns if the email for this user has been verified. </summary> | |||
/// <remarks> This field is only ever populated for the current logged in user. </remarks> | |||
[JsonIgnore] | |||
public bool? IsVerified { get; private set; } | |||
/// <summary> Returns the private messaging channel with this user, if one exists. </summary> | |||
[JsonIgnore] | |||
public Channel PrivateChannel | |||
{ | |||
get { return _privateChannel; } | |||
internal set | |||
{ | |||
_privateChannel = value; | |||
if (value == null) | |||
CheckUser(); | |||
} | |||
} | |||
private Channel _privateChannel; | |||
/// <summary> Returns a collection of all server-specific data for every server this user is a member of. </summary> | |||
[JsonIgnore] | |||
public IEnumerable<User> Memberships => _users.Select(x => _client.Users[Id, x.Key]); | |||
internal GlobalUser(DiscordClient client, string id) | |||
: base(client, id) | |||
{ | |||
_users = new ConcurrentDictionary<string, User>(); | |||
} | |||
internal override void LoadReferences() { } | |||
internal override void UnloadReferences() | |||
{ | |||
//Don't need to clean _users - they're considered owned by server | |||
} | |||
internal void Update(UserInfo model) | |||
{ | |||
if (model.Email != null) | |||
Email = model.Email; | |||
if (model.IsVerified != null) | |||
IsVerified = model.IsVerified; | |||
} | |||
internal void AddUser(User user) => _users.TryAdd(user.UniqueId, user); | |||
internal void RemoveUser(User user) | |||
{ | |||
if (_users.TryRemove(user.UniqueId, out user)) | |||
CheckUser(); | |||
} | |||
internal void CheckUser() | |||
{ | |||
if (_users.Count == 0 && PrivateChannel == null) | |||
_client.GlobalUsers.TryRemove(Id); | |||
} | |||
public override string ToString() => Id; | |||
} | |||
} |
@@ -1,14 +1,13 @@ | |||
using Newtonsoft.Json; | |||
using System; | |||
using Discord.API; | |||
using Newtonsoft.Json; | |||
namespace Discord | |||
{ | |||
public sealed class Invite | |||
{ | |||
private readonly DiscordClient _client; | |||
/// <summary> Returns the unique identifier for this invite. </summary> | |||
public string Id { get; } | |||
public sealed class Invite : CachedObject | |||
{ | |||
/// <summary> Returns, if enabled, an alternative human-readable code for URLs. </summary> | |||
public string XkcdCode { get; } | |||
/// <summary> Time (in seconds) until the invite expires. Set to 0 to never expire. </summary> | |||
public int MaxAge { get; private set; } | |||
/// <summary> The amount of times this invite has been used. </summary> | |||
@@ -19,51 +18,46 @@ namespace Discord | |||
public bool IsRevoked { get; private set; } | |||
/// <summary> If true, a user accepting this invite will be kicked from the server after closing their client. </summary> | |||
public bool IsTemporary { get; private set; } | |||
/// <summary> Returns, if enabled, an alternative human-readable code for URLs. </summary> | |||
public string XkcdPass { get; } | |||
/// <summary> Returns a URL for this invite using XkcdPass if available or Id if not. </summary> | |||
public string Url => API.Endpoints.InviteUrl(XkcdPass ?? Id); | |||
/// <summary> Returns a URL for this invite using XkcdCode if available or Id if not. </summary> | |||
public string Url => API.Endpoints.InviteUrl(XkcdCode ?? Id); | |||
/// <summary> Returns the id of the user that created this invite. </summary> | |||
public string InviterId { get; private set; } | |||
/// <summary> Returns the user that created this invite. </summary> | |||
[JsonIgnore] | |||
public User Inviter => _client.Users[InviterId]; | |||
public User Inviter => _inviter.Value; | |||
private readonly Reference<User> _inviter; | |||
/// <summary> Returns the id of the server this invite is to. </summary> | |||
public string ServerId { get; } | |||
/// <summary> Returns the server this invite is to. </summary> | |||
[JsonIgnore] | |||
public Server Server => _client.Servers[ServerId]; | |||
public Server Server => _server.Value; | |||
private readonly Reference<Server> _server; | |||
/// <summary> Returns the id of the channel this invite is to. </summary> | |||
public string ChannelId { get; private set; } | |||
/// <summary> Returns the channel this invite is to. </summary> | |||
[JsonIgnore] | |||
public Channel Channel => _client.Channels[ChannelId]; | |||
public Channel Channel => _channel.Value; | |||
private readonly Reference<Channel> _channel; | |||
internal Invite(DiscordClient client, string code, string xkcdPass, string serverId) | |||
internal Invite(DiscordClient client, string code, string xkcdPass, string serverId, string inviterId, string channelId) | |||
: base(client, code) | |||
{ | |||
_client = client; | |||
Id = code; | |||
XkcdPass = xkcdPass; | |||
ServerId = serverId; | |||
XkcdCode = xkcdPass; | |||
_server = new Reference<Server>(serverId, x => _client.Servers[x] ?? new Server(client, x)); | |||
_inviter = new Reference<User>(serverId, x => _client.Users[x, _server.Id] ?? new User(client, x, _server.Id)); | |||
_channel = new Reference<Channel>(serverId, x => _client.Channels[x] ?? new Channel(client, x, _server.Id, null)); | |||
} | |||
public override string ToString() => XkcdPass ?? Id; | |||
internal void Update(API.Invite model) | |||
internal override void LoadReferences() | |||
{ | |||
if (model.Channel != null) | |||
ChannelId = model.Channel.Id; | |||
if (model.Inviter != null) | |||
InviterId = model.Inviter.Id; | |||
_server.Load(); | |||
_inviter.Load(); | |||
_channel.Load(); | |||
} | |||
internal override void UnloadReferences() { } | |||
public override string ToString() => XkcdCode ?? Id; | |||
internal void Update(API.ExtendedInvite model) | |||
internal void Update(InviteInfo model) | |||
{ | |||
Update(model as API.Invite); | |||
if (model.IsRevoked != null) | |||
IsRevoked = model.IsRevoked.Value; | |||
if (model.IsTemporary != null) | |||
@@ -1,229 +0,0 @@ | |||
using Newtonsoft.Json; | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
namespace Discord | |||
{ | |||
public class Member | |||
{ | |||
private readonly DiscordClient _client; | |||
private ConcurrentDictionary<string, PackedChannelPermissions> _permissions; | |||
/// <summary> Returns the name of this user on this server. </summary> | |||
public string Name { get; private set; } | |||
/// <summary> Returns a by-name unique identifier separating this user from others with the same name. </summary> | |||
public string Discriminator { get; private set; } | |||
/// <summary> Returns the unique identifier for this user's current avatar. </summary> | |||
public string AvatarId { get; private set; } | |||
/// <summary> Returns the URL to this user's current avatar. </summary> | |||
public string AvatarUrl => API.Endpoints.UserAvatar(UserId, AvatarId); | |||
/// <summary> Returns the datetime that this user joined this server. </summary> | |||
public DateTime JoinedAt { get; private set; } | |||
public bool IsMuted { get; private set; } | |||
public bool IsDeafened { get; private set; } | |||
public bool IsSelfMuted { get; private set; } | |||
public bool IsSelfDeafened { get; private set; } | |||
public bool IsSuppressed { get; private set; } | |||
public bool IsSpeaking { get; internal set; } | |||
public string SessionId { get; private set; } | |||
public string Token { get; private set; } | |||
/// <summary> Returns the id for the game this user is currently playing. </summary> | |||
public string GameId { get; private set; } | |||
/// <summary> Returns the current status for this user. </summary> | |||
public string Status { get; private set; } | |||
/// <summary> Returns the time this user last sent/edited a message, started typing or sent voice data in this server. </summary> | |||
public DateTime? LastActivityAt { get; private set; } | |||
/// <summary> Returns the time this user was last seen online in this server. </summary> | |||
public DateTime LastOnlineAt => Status != UserStatus.Offline ? DateTime.UtcNow : _lastOnline; | |||
private DateTime _lastOnline; | |||
public string UserId { get; } | |||
[JsonIgnore] | |||
public User User => _client.Users[UserId]; | |||
public string ServerId { get; } | |||
[JsonIgnore] | |||
public Server Server => _client.Servers[ServerId]; | |||
public string VoiceChannelId { get; private set; } | |||
[JsonIgnore] | |||
public Channel VoiceChannel => _client.Channels[VoiceChannelId]; | |||
private static readonly string[] _initialRoleIds = new string[0]; | |||
public string[] RoleIds { get; private set; } | |||
[JsonIgnore] | |||
public IEnumerable<Role> Roles => RoleIds.Select(x => _client.Roles[x]); | |||
/// <summary> Returns a collection of all messages this user has sent on this server that are still in cache. </summary> | |||
public IEnumerable<Message> Messages => _client.Messages.Where(x => x.UserId == UserId && x.ServerId == ServerId); | |||
/// <summary> Returns a collection of all channels this user is a member of. </summary> | |||
public IEnumerable<Channel> Channels => _client.Channels.Where(x => x.ServerId == ServerId && x.UserIds.Contains(UserId)); | |||
internal Member(DiscordClient client, string userId, string serverId) | |||
{ | |||
_client = client; | |||
UserId = userId; | |||
ServerId = serverId; | |||
Status = UserStatus.Offline; | |||
RoleIds = _initialRoleIds; | |||
_permissions = new ConcurrentDictionary<string, PackedChannelPermissions>(); | |||
} | |||
public override string ToString() => UserId; | |||
internal void Update(API.UserReference model) | |||
{ | |||
if (model.Avatar != null) | |||
AvatarId = model.Avatar; | |||
if (model.Discriminator != null) | |||
Discriminator = model.Discriminator; | |||
if (model.Username != null) | |||
Name = model.Username; | |||
} | |||
internal void Update(API.MemberInfo model) | |||
{ | |||
if (model.User != null) | |||
Update(model.User); | |||
if (model.JoinedAt.HasValue) | |||
JoinedAt = model.JoinedAt.Value; | |||
//Set roles, with the everyone role added too | |||
string[] newRoles = new string[model.Roles.Length + 1]; | |||
newRoles[0] = Server.EveryoneRoleId; | |||
for (int i = 0; i < model.Roles.Length; i++) | |||
newRoles[i + 1] = model.Roles[i]; | |||
RoleIds = newRoles; | |||
UpdatePermissions(); | |||
} | |||
internal void Update(API.ExtendedMemberInfo model) | |||
{ | |||
Update(model as API.MemberInfo); | |||
if (model.IsDeafened != null) | |||
IsDeafened = model.IsDeafened.Value; | |||
if (model.IsMuted != null) | |||
IsMuted = model.IsMuted.Value; | |||
} | |||
internal void Update(API.PresenceMemberInfo model) | |||
{ | |||
if (model.User != null) | |||
Update(model.User as API.UserReference); | |||
if (model.Status != null && Status != model.Status) | |||
{ | |||
Status = model.Status; | |||
if (Status == UserStatus.Offline) | |||
_lastOnline = DateTime.UtcNow; | |||
} | |||
//Allows null | |||
GameId = model.GameId; | |||
} | |||
internal void Update(API.VoiceMemberInfo model) | |||
{ | |||
if (model.IsDeafened != null) | |||
IsDeafened = model.IsDeafened.Value; | |||
if (model.IsMuted != null) | |||
IsMuted = model.IsMuted.Value; | |||
if (model.SessionId != null) | |||
SessionId = model.SessionId; | |||
if (model.Token != null) | |||
Token = model.Token; | |||
if (model.ChannelId != null) | |||
VoiceChannelId = model.ChannelId; | |||
if (model.IsSelfDeafened != null) | |||
IsSelfDeafened = model.IsSelfDeafened.Value; | |||
if (model.IsSelfMuted != null) | |||
IsSelfMuted = model.IsSelfMuted.Value; | |||
if (model.IsSuppressed != null) | |||
IsSuppressed = model.IsSuppressed.Value; | |||
} | |||
internal void UpdateActivity(DateTime? activity = null) | |||
{ | |||
if (LastActivityAt == null || activity > LastActivityAt.Value) | |||
LastActivityAt = activity ?? DateTime.UtcNow; | |||
} | |||
internal void AddChannel(string channelId) | |||
{ | |||
var perms = new PackedChannelPermissions(); | |||
perms.Lock(); | |||
_permissions.TryAdd(channelId, perms); | |||
UpdatePermissions(channelId); | |||
} | |||
internal bool RemoveChannel(string channelId) | |||
{ | |||
PackedChannelPermissions ignored; | |||
return _permissions.TryRemove(channelId, out ignored); | |||
} | |||
internal void UpdatePermissions() | |||
{ | |||
foreach (var channel in _permissions) | |||
UpdatePermissions(channel.Key); | |||
} | |||
internal void UpdatePermissions(string channelId) | |||
{ | |||
if (RoleIds == null) return; // We don't have all our data processed yet, this will be called again soon | |||
var server = Server; | |||
if (server == null) return; | |||
var channel = _client.Channels[channelId]; | |||
PackedChannelPermissions permissions; | |||
if (!_permissions.TryGetValue(channelId, out permissions)) return; | |||
uint newPermissions = 0x0; | |||
uint oldPermissions = permissions.RawValue; | |||
if (UserId == server.OwnerId) | |||
newPermissions = PackedChannelPermissions.All.RawValue; | |||
else | |||
{ | |||
if (channel == null) return; | |||
var channelOverwrites = channel.PermissionOverwrites; | |||
//var roles = Roles.OrderBy(x => x.Id); | |||
var roles = Roles; | |||
foreach (var serverRole in roles) | |||
newPermissions |= serverRole.Permissions.RawValue; | |||
foreach (var denyRole in channelOverwrites.Where(x => x.TargetType == PermissionTarget.Role && x.Deny.RawValue != 0 && roles.Any(y => y.Id == x.TargetId))) | |||
newPermissions &= ~denyRole.Deny.RawValue; | |||
foreach (var allowRole in channelOverwrites.Where(x => x.TargetType == PermissionTarget.Role && x.Allow.RawValue != 0 && roles.Any(y => y.Id == x.TargetId))) | |||
newPermissions |= allowRole.Allow.RawValue; | |||
foreach (var denyMembers in channelOverwrites.Where(x => x.TargetType == PermissionTarget.Member && x.TargetId == UserId && x.Deny.RawValue != 0)) | |||
newPermissions &= ~denyMembers.Deny.RawValue; | |||
foreach (var allowMembers in channelOverwrites.Where(x => x.TargetType == PermissionTarget.Member && x.TargetId == UserId && x.Allow.RawValue != 0)) | |||
newPermissions |= allowMembers.Allow.RawValue; | |||
} | |||
permissions.SetRawValueInternal(newPermissions); | |||
if (permissions.General_ManagePermissions) | |||
permissions.SetRawValueInternal(PackedChannelPermissions.All.RawValue); | |||
else if (server.DefaultChannelId == channelId) | |||
permissions.SetBitInternal(PackedPermissions.Text_ReadMessagesBit, true); | |||
if (permissions.RawValue != oldPermissions) | |||
channel.InvalidMembersCache(); | |||
} | |||
//TODO: Add GetServerPermissions | |||
public PackedChannelPermissions GetPermissions(Channel channel) | |||
=> GetPermissions(channel?.Id); | |||
public PackedChannelPermissions GetPermissions(string channelId) | |||
{ | |||
if (channelId == null) throw new ArgumentNullException(nameof(channelId)); | |||
PackedChannelPermissions perms; | |||
if (_permissions.TryGetValue(channelId, out perms)) | |||
return perms; | |||
return null; | |||
} | |||
} | |||
} |
@@ -1,11 +1,12 @@ | |||
using Newtonsoft.Json; | |||
using Discord.API; | |||
using Newtonsoft.Json; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
namespace Discord | |||
{ | |||
public sealed class Message | |||
public sealed class Message : CachedObject | |||
{ | |||
public sealed class Attachment : File | |||
{ | |||
@@ -90,11 +91,8 @@ namespace Discord | |||
} | |||
} | |||
private readonly DiscordClient _client; | |||
private string _cleanText; | |||
/// <summary> Returns the global unique identifier for this message. </summary> | |||
public string Id { get; internal set; } | |||
/// <summary> Returns the local unique identifier for this message. </summary> | |||
public string Nonce { get; internal set; } | |||
@@ -113,7 +111,7 @@ namespace Discord | |||
public string RawText { get; private set; } | |||
/// <summary> Returns the content of this message with any special references such as mentions converted. </summary> | |||
/// <remarks> This value is lazy loaded and only processed on first request. Each subsequent request will pull from cache. </remarks> | |||
public string Text => _cleanText != null ? _cleanText : (_cleanText = _client.Messages.CleanText(RawText)); | |||
public string Text => _cleanText != null ? _cleanText : (_cleanText = Mention.ConvertToNames(_client, Server, RawText)); | |||
/// <summary> Returns the timestamp for when this message was sent. </summary> | |||
public DateTime Timestamp { get; private set; } | |||
/// <summary> Returns the timestamp for when this message was last edited. </summary> | |||
@@ -124,58 +122,57 @@ namespace Discord | |||
/// <summary> Returns a collection of all embeded content in this message. </summary> | |||
public Embed[] Embeds { get; private set; } | |||
private static readonly Embed[] _initialEmbeds = new Embed[0]; | |||
private static readonly string[] _initialMentions = new string[0]; | |||
/// <summary> Returns a collection of all user ids mentioned in this message. </summary> | |||
public string[] MentionIds { get; private set; } | |||
/// <summary> Returns a collection of all users mentioned in this message. </summary> | |||
[JsonIgnore] | |||
public IEnumerable<User> Mentions => MentionIds.Select(x => _client.Users[x]).Where(x => x != null); | |||
/// <summary> Returns the id of the server containing the channel this message was sent to. </summary> | |||
public string ServerId => Channel.ServerId; | |||
public IEnumerable<User> Mentions { get; internal set; } | |||
/// <summary> Returns the server containing the channel this message was sent to. </summary> | |||
[JsonIgnore] | |||
public Server Server => _client.Servers[Channel.ServerId]; | |||
/// <summary> Returns the id of the channel this message was sent to. </summary> | |||
public string ChannelId { get; } | |||
public Server Server => _channel.Value.Server; | |||
/// <summary> Returns the channel this message was sent to. </summary> | |||
[JsonIgnore] | |||
public Channel Channel => _client.Channels[ChannelId]; | |||
public Channel Channel => _channel.Value; | |||
private readonly Reference<Channel> _channel; | |||
/// <summary> Returns true if the current user created this message. </summary> | |||
public bool IsAuthor => _client.CurrentUserId == UserId; | |||
/// <summary> Returns the id of the author of this message. </summary> | |||
public string UserId { get; } | |||
public bool IsAuthor => _client.CurrentUserId == _user.Id; | |||
/// <summary> Returns the author of this message. </summary> | |||
[JsonIgnore] | |||
public User User => _client.Users[UserId]; | |||
/// <summary> Returns the author of this message. </summary> | |||
[JsonIgnore] | |||
public Member Member | |||
{ | |||
get | |||
{ | |||
if (!Channel.IsPrivate) | |||
return _client.Members[UserId, ServerId]; | |||
else | |||
throw new InvalidOperationException("Unable to access Member in a private channel. Use User instead or check for Channel.IsPrivate."); | |||
} | |||
} | |||
public User User => _user.Value; | |||
private readonly Reference<User> _user; | |||
internal Message(DiscordClient client, string id, string channelId, string userId) | |||
: base(client, id) | |||
{ | |||
_client = client; | |||
Id = id; | |||
ChannelId = channelId; | |||
UserId = userId; | |||
_channel = new Reference<Channel>(channelId, | |||
x => _client.Channels[x], | |||
x => x.AddMessage(this), | |||
x => x.RemoveMessage(this)); | |||
_user = new Reference<User>(userId, | |||
x => | |||
{ | |||
var channel = Channel; | |||
if (!channel.IsPrivate) | |||
return _client.Users[x, channel.Server.Id]; | |||
else | |||
return _client.Users[x, null]; | |||
}); | |||
Attachments = _initialAttachments; | |||
Embeds = _initialEmbeds; | |||
MentionIds = _initialMentions; | |||
} | |||
} | |||
internal override void LoadReferences() | |||
{ | |||
_channel.Load(); | |||
_user.Load(); | |||
} | |||
internal override void UnloadReferences() | |||
{ | |||
_channel.Unload(); | |||
_user.Unload(); | |||
} | |||
internal void Update(API.Message model) | |||
internal void Update(MessageInfo model) | |||
{ | |||
if (model.Attachments != null) | |||
{ | |||
@@ -211,8 +208,8 @@ namespace Discord | |||
EditedTimestamp = model.EditedTimestamp; | |||
if (model.Mentions != null) | |||
{ | |||
MentionIds = model.Mentions.Select(x => x.Id)?.ToArray(); | |||
IsMentioningMe = MentionIds.Contains(_client.CurrentUserId); | |||
Mentions = model.Mentions.Select(x => _client.Users[x.Id, Channel.Server?.Id]).ToArray(); | |||
IsMentioningMe = model.Mentions.Any(x => x.Id == _client.CurrentUserId); | |||
} | |||
if (model.Content != null) | |||
{ | |||
@@ -221,6 +218,6 @@ namespace Discord | |||
} | |||
} | |||
public override string ToString() => User.ToString() + ": " + RawText; | |||
public override string ToString() => User.Name + ": " + RawText; | |||
} | |||
} |
@@ -1,81 +0,0 @@ | |||
using System; | |||
namespace Discord | |||
{ | |||
public class PackedColor | |||
{ | |||
public static readonly PackedColor Default = PresetColor(0); | |||
public static readonly PackedColor Cyan = PresetColor(0x1abc9c); | |||
public static readonly PackedColor DarkCyan = PresetColor(0x11806a); | |||
public static readonly PackedColor Green = PresetColor(0x2ecc71); | |||
public static readonly PackedColor DarkGreen = PresetColor(0x1f8b4c); | |||
public static readonly PackedColor Blue = PresetColor(0x3498db); | |||
public static readonly PackedColor DarkBlue = PresetColor(0x206694); | |||
public static readonly PackedColor Purple = PresetColor(0x9b59b6); | |||
public static readonly PackedColor DarkPurple = PresetColor(0x71368a); | |||
public static readonly PackedColor Red = PresetColor(0xe74c3c); | |||
public static readonly PackedColor DarkRed = PresetColor(0x992d22); | |||
public static readonly PackedColor Orange = PresetColor(0xe67e22); | |||
public static readonly PackedColor DarkOrange = PresetColor(0xa84300); | |||
public static readonly PackedColor Navy = PresetColor(0x34495e); | |||
public static readonly PackedColor DarkNavy = PresetColor(0x2c3e50); | |||
public static readonly PackedColor Gold = PresetColor(0xf1c40f); | |||
public static readonly PackedColor DarkGold = PresetColor(0xc27c0e); | |||
public static readonly PackedColor LighterGrey = PresetColor(0xbcc0c0); | |||
public static readonly PackedColor LightGrey = PresetColor(0x95a5a6); | |||
public static readonly PackedColor DarkGrey = PresetColor(0x979c9f); | |||
public static readonly PackedColor DarkerGrey = PresetColor(0x7f8c8d); | |||
private static PackedColor PresetColor(uint packedValue) | |||
{ | |||
PackedColor color = new PackedColor(packedValue); | |||
color.Lock(); | |||
return color; | |||
} | |||
private bool _isLocked; | |||
private uint _rawValue; | |||
public uint RawValue | |||
{ | |||
get { return _rawValue; } | |||
set | |||
{ | |||
if (_isLocked) | |||
throw new InvalidOperationException("Unable to edit cached colors directly, use Copy() to make an editable copy."); | |||
_rawValue = value; | |||
} | |||
} | |||
public PackedColor(uint rawValue) { _rawValue = rawValue; } | |||
public PackedColor(byte r, byte g, byte b) : this(((uint)r << 16) | ((uint)g << 8) | b) { } | |||
public PackedColor(float r, float g, float b) : this((byte)(r * 255.0f), (byte)(g * 255.0f), (byte)(b * 255.0f)) { } | |||
/// <summary> Gets or sets the red component for this color. </summary> | |||
public byte R { get { return GetByte(3); } set { SetByte(3, value); } } | |||
/// <summary> Gets or sets the green component for this color. </summary> | |||
public byte G { get { return GetByte(2); } set { SetByte(2, value); } } | |||
/// <summary> Gets or sets the blue component for this color. </summary> | |||
public byte B { get { return GetByte(1); } set { SetByte(1, value); } } | |||
internal void Lock() => _isLocked = true; | |||
internal void SetRawValue(uint rawValue) | |||
{ | |||
//Bypasses isLocked for API changes. | |||
_rawValue = rawValue; | |||
} | |||
protected byte GetByte(int pos) => (byte)((_rawValue >> (8 * (pos - 1))) & 0xFF); | |||
protected void SetByte(int pos, byte value) | |||
{ | |||
if (_isLocked) | |||
throw new InvalidOperationException("Unable to edit cached colors directly, use Copy() to make an editable copy."); | |||
uint original = _rawValue; | |||
int bit = 8 * (pos - 1); | |||
uint mask = (uint)~(0xFF << bit); | |||
_rawValue = (_rawValue & mask) | ((uint)value << bit); | |||
} | |||
} | |||
} |
@@ -1,151 +0,0 @@ | |||
using System; | |||
namespace Discord | |||
{ | |||
public sealed class PackedServerPermissions : PackedPermissions | |||
{ | |||
public static PackedServerPermissions None { get; } | |||
public static PackedServerPermissions All { get; } | |||
static PackedServerPermissions() | |||
{ | |||
None = new PackedServerPermissions(); | |||
None.Lock(); | |||
All = new PackedServerPermissions(Convert.ToUInt32("00000011111100111111110000111111", 2)); | |||
All.Lock(); | |||
} | |||
public PackedServerPermissions(uint rawValue = 0) : base(rawValue) { } | |||
/// <summary> If True, a user may ban users from the server. </summary> | |||
public bool General_BanMembers { get { return GetBit(General_BanMembersBit); } set { SetBit(General_BanMembersBit, value); } } | |||
/// <summary> If True, a user may kick users from the server. </summary> | |||
public bool General_KickMembers { get { return GetBit(General_KickMembersBit); } set { SetBit(General_KickMembersBit, value); } } | |||
/// <summary> If True, a user may adjust roles. This also implictly grants all other permissions. </summary> | |||
public bool General_ManageRoles { get { return GetBit(General_ManagePermissionsBit); } set { SetBit(General_ManagePermissionsBit, value); } } | |||
/// <summary> If True, a user may create, delete and modify channels. </summary> | |||
public bool General_ManageChannels { get { return GetBit(General_ManageChannelBit); } set { SetBit(General_ManageChannelBit, value); } } | |||
/// <summary> If True, a user may adjust server properties. </summary> | |||
public bool General_ManageServer { get { return GetBit(General_ManageServerBit); } set { SetBit(General_ManageServerBit, value); } } | |||
public PackedServerPermissions Copy() => new PackedServerPermissions(RawValue); | |||
} | |||
public sealed class PackedChannelPermissions : PackedPermissions | |||
{ | |||
public static PackedChannelPermissions None { get; } | |||
public static PackedChannelPermissions All { get; } | |||
static PackedChannelPermissions() | |||
{ | |||
None = new PackedChannelPermissions(); | |||
None.Lock(); | |||
All = new PackedChannelPermissions(Convert.ToUInt32("00000011111100111111110000011001", 2)); | |||
All.Lock(); | |||
} | |||
public PackedChannelPermissions(uint rawValue = 0) : base(rawValue) { } | |||
/// <summary> If True, a user may adjust permissions. This also implictly grants all other permissions. </summary> | |||
public bool General_ManagePermissions { get { return GetBit(General_ManagePermissionsBit); } set { SetBit(General_ManagePermissionsBit, value); } } | |||
/// <summary> If True, a user may create, delete and modify this channel. </summary> | |||
public bool General_ManageChannel { get { return GetBit(General_ManageChannelBit); } set { SetBit(General_ManageChannelBit, value); } } | |||
public PackedChannelPermissions Copy() => new PackedChannelPermissions(RawValue); | |||
} | |||
public abstract class PackedPermissions | |||
{ | |||
internal const byte General_CreateInstantInviteBit = 0; | |||
internal const byte General_BanMembersBit = 1; | |||
internal const byte General_KickMembersBit = 2; | |||
internal const byte General_ManagePermissionsBit = 3; | |||
internal const byte General_ManageChannelBit = 4; | |||
internal const byte General_ManageServerBit = 5; | |||
internal const byte Text_ReadMessagesBit = 10; | |||
internal const byte Text_SendMessagesBit = 11; | |||
internal const byte Text_SendTTSMessagesBit = 12; | |||
internal const byte Text_ManageMessagesBit = 13; | |||
internal const byte Text_EmbedLinksBit = 14; | |||
internal const byte Text_AttachFilesBit = 15; | |||
internal const byte Text_ReadMessageHistoryBit = 16; | |||
internal const byte Text_MentionEveryoneBit = 17; | |||
internal const byte Voice_ConnectBit = 20; | |||
internal const byte Voice_SpeakBit = 21; | |||
internal const byte Voice_MuteMembersBit = 22; | |||
internal const byte Voice_DeafenMembersBit = 23; | |||
internal const byte Voice_MoveMembersBit = 24; | |||
internal const byte Voice_UseVoiceActivationBit = 25; | |||
private bool _isLocked; | |||
private uint _rawValue; | |||
public uint RawValue | |||
{ | |||
get { return _rawValue; } | |||
set | |||
{ | |||
if (_isLocked) | |||
throw new InvalidOperationException("Unable to edit cached permissions directly, use Copy() to make an editable copy."); | |||
_rawValue = value; | |||
} | |||
} | |||
protected PackedPermissions(uint rawValue) { _rawValue = rawValue; } | |||
/// <summary> If True, a user may create invites. </summary> | |||
public bool General_CreateInstantInvite { get { return GetBit(General_CreateInstantInviteBit); } set { SetBit(General_CreateInstantInviteBit, value); } } | |||
/// <summary> If True, a user may join channels. </summary> | |||
public bool Text_ReadMessages { get { return GetBit(Text_ReadMessagesBit); } set { SetBit(Text_ReadMessagesBit, value); } } | |||
/// <summary> If True, a user may send messages. </summary> | |||
public bool Text_SendMessages { get { return GetBit(Text_SendMessagesBit); } set { SetBit(Text_SendMessagesBit, value); } } | |||
/// <summary> If True, a user may send text-to-speech messages. </summary> | |||
public bool Text_SendTTSMessages { get { return GetBit(Text_SendTTSMessagesBit); } set { SetBit(Text_SendTTSMessagesBit, value); } } | |||
/// <summary> If True, a user may delete messages. </summary> | |||
public bool Text_ManageMessages { get { return GetBit(Text_ManageMessagesBit); } set { SetBit(Text_ManageMessagesBit, value); } } | |||
/// <summary> If True, Discord will auto-embed links sent by this user. </summary> | |||
public bool Text_EmbedLinks { get { return GetBit(Text_EmbedLinksBit); } set { SetBit(Text_EmbedLinksBit, value); } } | |||
/// <summary> If True, a user may send files. </summary> | |||
public bool Text_AttachFiles { get { return GetBit(Text_AttachFilesBit); } set { SetBit(Text_AttachFilesBit, value); } } | |||
/// <summary> If True, a user may read previous messages. </summary> | |||
public bool Text_ReadMessageHistory { get { return GetBit(Text_ReadMessageHistoryBit); } set { SetBit(Text_ReadMessageHistoryBit, value); } } | |||
/// <summary> If True, a user may mention @everyone. </summary> | |||
public bool Text_MentionEveryone { get { return GetBit(Text_MentionEveryoneBit); } set { SetBit(Text_MentionEveryoneBit, value); } } | |||
/// <summary> If True, a user may connect to a voice channel. </summary> | |||
public bool Voice_Connect { get { return GetBit(Voice_ConnectBit); } set { SetBit(Voice_ConnectBit, value); } } | |||
/// <summary> If True, a user may speak in a voice channel. </summary> | |||
public bool Voice_Speak { get { return GetBit(Voice_SpeakBit); } set { SetBit(Voice_SpeakBit, value); } } | |||
/// <summary> If True, a user may mute users. </summary> | |||
public bool Voice_MuteMembers { get { return GetBit(Voice_MuteMembersBit); } set { SetBit(Voice_MuteMembersBit, value); } } | |||
/// <summary> If True, a user may deafen users. </summary> | |||
public bool Voice_DeafenMembers { get { return GetBit(Voice_DeafenMembersBit); } set { SetBit(Voice_DeafenMembersBit, value); } } | |||
/// <summary> If True, a user may move other users between voice channels. </summary> | |||
public bool Voice_MoveMembers { get { return GetBit(Voice_MoveMembersBit); } set { SetBit(Voice_MoveMembersBit, value); } } | |||
/// <summary> If True, a user may use voice activation rather than push-to-talk. </summary> | |||
public bool Voice_UseVoiceActivation { get { return GetBit(Voice_UseVoiceActivationBit); } set { SetBit(Voice_UseVoiceActivationBit, value); } } | |||
internal void Lock() => _isLocked = true; | |||
protected bool GetBit(int pos) => ((_rawValue >> pos) & 1U) == 1; | |||
protected void SetBit(int pos, bool value) | |||
{ | |||
if (_isLocked) | |||
throw new InvalidOperationException("Unable to edit cached permissions directly, use Copy() to make an editable copy."); | |||
if (value) | |||
_rawValue |= (1U << pos); | |||
else | |||
_rawValue &= ~(1U << pos); | |||
} | |||
//Bypasses isLocked for API changes. | |||
internal void SetBitInternal(int pos, bool value) | |||
{ | |||
if (value) | |||
_rawValue |= (1U << pos); | |||
else | |||
_rawValue &= ~(1U << pos); | |||
} | |||
internal void SetRawValueInternal(uint rawValue) | |||
{ | |||
_rawValue = rawValue; | |||
} | |||
} | |||
} |
@@ -0,0 +1,218 @@ | |||
using System; | |||
namespace Discord | |||
{ | |||
internal enum PermissionsBits : byte | |||
{ | |||
//General | |||
CreateInstantInvite = 0, | |||
BanMembers = 1, | |||
KickMembers = 2, | |||
ManageRolesOrPermissions = 3, | |||
ManageChannel = 4, | |||
ManageServer = 5, | |||
//Text | |||
ReadMessages = 10, | |||
SendMessages = 11, | |||
SendTTSMessages = 12, | |||
ManageMessages = 13, | |||
EmbedLinks = 14, | |||
AttachFiles = 15, | |||
ReadMessageHistory = 16, | |||
MentionEveryone = 17, | |||
//Voice | |||
Connect = 20, | |||
Speak = 21, | |||
MuteMembers = 22, | |||
DeafenMembers = 23, | |||
MoveMembers = 24, | |||
UseVoiceActivation = 25 | |||
} | |||
public sealed class ServerPermissions : Permissions | |||
{ | |||
public static ServerPermissions None { get; } = new ServerPermissions(); | |||
public static ServerPermissions All { get; } = new ServerPermissions(Convert.ToUInt32("00000011111100111111110000111111", 2)); | |||
public ServerPermissions() : base() { } | |||
public ServerPermissions(uint rawValue) : base(rawValue) { } | |||
public ServerPermissions Copy() => new ServerPermissions(RawValue); | |||
/// <summary> If True, a user may ban users from the server. </summary> | |||
public bool BanMembers { get { return GetBit(PermissionsBits.BanMembers); } set { SetBit(PermissionsBits.BanMembers, value); } } | |||
/// <summary> If True, a user may kick users from the server. </summary> | |||
public bool KickMembers { get { return GetBit(PermissionsBits.KickMembers); } set { SetBit(PermissionsBits.KickMembers, value); } } | |||
/// <summary> If True, a user may adjust roles. This also implictly grants all other permissions. </summary> | |||
public bool ManageRoles { get { return GetBit(PermissionsBits.ManageRolesOrPermissions); } set { SetBit(PermissionsBits.ManageRolesOrPermissions, value); } } | |||
/// <summary> If True, a user may create, delete and modify channels. </summary> | |||
public bool ManageChannels { get { return GetBit(PermissionsBits.ManageChannel); } set { SetBit(PermissionsBits.ManageChannel, value); } } | |||
/// <summary> If True, a user may adjust server properties. </summary> | |||
public bool ManageServer { get { return GetBit(PermissionsBits.ManageServer); } set { SetBit(PermissionsBits.ManageServer, value); } } | |||
} | |||
public sealed class ChannelPermissions : Permissions | |||
{ | |||
public static ChannelPermissions None { get; } = new ChannelPermissions(); | |||
public static ChannelPermissions TextOnly { get; } = new ChannelPermissions(Convert.ToUInt32("00000000000000111111110000011001", 2)); | |||
public static ChannelPermissions PrivateOnly { get; } = new ChannelPermissions(Convert.ToUInt32("00000000000000011100110000000000", 2)); | |||
public static ChannelPermissions VoiceOnly { get; } = new ChannelPermissions(Convert.ToUInt32("00000011111100000000000000011001", 2)); | |||
public static ChannelPermissions All(Channel channel) => All(channel.Type, channel.IsPrivate); | |||
public static ChannelPermissions All(string channelType, bool isPrivate) | |||
{ | |||
if (isPrivate) return PrivateOnly; | |||
else if (channelType == ChannelType.Text) return TextOnly; | |||
else if (channelType == ChannelType.Voice) return VoiceOnly; | |||
else return None; | |||
} | |||
public ChannelPermissions() : base() { } | |||
public ChannelPermissions(uint rawValue) : base(rawValue) { } | |||
public ChannelPermissions Copy() => new ChannelPermissions(RawValue); | |||
/// <summary> If True, a user may adjust permissions. This also implictly grants all other permissions. </summary> | |||
public bool ManagePermissions { get { return GetBit(PermissionsBits.ManageRolesOrPermissions); } set { SetBit(PermissionsBits.ManageRolesOrPermissions, value); } } | |||
/// <summary> If True, a user may create, delete and modify this channel. </summary> | |||
public bool ManageChannel { get { return GetBit(PermissionsBits.ManageChannel); } set { SetBit(PermissionsBits.ManageChannel, value); } } | |||
} | |||
public abstract class Permissions | |||
{ | |||
private bool _isLocked; | |||
private uint _rawValue; | |||
protected Permissions() { } | |||
protected Permissions(uint rawValue) { _rawValue = rawValue; } | |||
/// <summary> If True, a user may create invites. </summary> | |||
public bool CreateInstantInvite { get { return GetBit(PermissionsBits.CreateInstantInvite); } set { SetBit(PermissionsBits.CreateInstantInvite, value); } } | |||
/// <summary> If True, a user may join channels. </summary> | |||
public bool ReadMessages { get { return GetBit(PermissionsBits.ReadMessages); } set { SetBit(PermissionsBits.ReadMessages, value); } } | |||
/// <summary> If True, a user may send messages. </summary> | |||
public bool SendMessages { get { return GetBit(PermissionsBits.SendMessages); } set { SetBit(PermissionsBits.SendMessages, value); } } | |||
/// <summary> If True, a user may send text-to-speech messages. </summary> | |||
public bool SendTTSMessages { get { return GetBit(PermissionsBits.SendTTSMessages); } set { SetBit(PermissionsBits.SendTTSMessages, value); } } | |||
/// <summary> If True, a user may delete messages. </summary> | |||
public bool ManageMessages { get { return GetBit(PermissionsBits.ManageMessages); } set { SetBit(PermissionsBits.ManageMessages, value); } } | |||
/// <summary> If True, Discord will auto-embed links sent by this user. </summary> | |||
public bool EmbedLinks { get { return GetBit(PermissionsBits.EmbedLinks); } set { SetBit(PermissionsBits.EmbedLinks, value); } } | |||
/// <summary> If True, a user may send files. </summary> | |||
public bool AttachFiles { get { return GetBit(PermissionsBits.AttachFiles); } set { SetBit(PermissionsBits.AttachFiles, value); } } | |||
/// <summary> If True, a user may read previous messages. </summary> | |||
public bool ReadMessageHistory { get { return GetBit(PermissionsBits.ReadMessageHistory); } set { SetBit(PermissionsBits.ReadMessageHistory, value); } } | |||
/// <summary> If True, a user may mention @everyone. </summary> | |||
public bool MentionEveryone { get { return GetBit(PermissionsBits.MentionEveryone); } set { SetBit(PermissionsBits.MentionEveryone, value); } } | |||
/// <summary> If True, a user may connect to a voice channel. </summary> | |||
public bool Connect { get { return GetBit(PermissionsBits.Connect); } set { SetBit(PermissionsBits.Connect, value); } } | |||
/// <summary> If True, a user may speak in a voice channel. </summary> | |||
public bool Speak { get { return GetBit(PermissionsBits.Speak); } set { SetBit(PermissionsBits.Speak, value); } } | |||
/// <summary> If True, a user may mute users. </summary> | |||
public bool MuteMembers { get { return GetBit(PermissionsBits.MuteMembers); } set { SetBit(PermissionsBits.MuteMembers, value); } } | |||
/// <summary> If True, a user may deafen users. </summary> | |||
public bool DeafenMembers { get { return GetBit(PermissionsBits.DeafenMembers); } set { SetBit(PermissionsBits.DeafenMembers, value); } } | |||
/// <summary> If True, a user may move other users between voice channels. </summary> | |||
public bool MoveMembers { get { return GetBit(PermissionsBits.MoveMembers); } set { SetBit(PermissionsBits.MoveMembers, value); } } | |||
/// <summary> If True, a user may use voice activation rather than push-to-talk. </summary> | |||
public bool UseVoiceActivation { get { return GetBit(PermissionsBits.UseVoiceActivation); } set { SetBit(PermissionsBits.UseVoiceActivation, value); } } | |||
public uint RawValue | |||
{ | |||
get { return _rawValue; } | |||
set { CheckLock(); _rawValue = value; } | |||
} | |||
internal void SetRawValueInternal(uint rawValue) | |||
{ | |||
_rawValue = rawValue; | |||
} | |||
internal bool GetBit(PermissionsBits pos) => BitHelper.GetBit(_rawValue, (int)pos); | |||
internal void SetBit(PermissionsBits pos, bool value) { CheckLock(); SetBitInternal((byte)pos, value); } | |||
internal void SetBitInternal(int pos, bool value) => BitHelper.SetBit(ref _rawValue, pos, value); | |||
internal void Lock() => _isLocked = true; | |||
protected void CheckLock() | |||
{ | |||
if (_isLocked) | |||
throw new InvalidOperationException("Unable to edit cached permissions directly, use Copy() to make an editable copy."); | |||
} | |||
} | |||
public sealed class DualChannelPermissions | |||
{ | |||
public ChannelPermissions Allow { get; } | |||
public ChannelPermissions Deny { get; } | |||
public DualChannelPermissions(uint allow = 0, uint deny = 0) | |||
{ | |||
Allow = new ChannelPermissions(allow); | |||
Deny = new ChannelPermissions(deny); | |||
} | |||
/// <summary> If True, a user may create invites. </summary> | |||
public bool? CreateInstantInvite { get { return GetBit(PermissionsBits.CreateInstantInvite); } set { SetBit(PermissionsBits.CreateInstantInvite, value); } } | |||
/// <summary> If True, a user may join channels. </summary> | |||
public bool? ReadMessages { get { return GetBit(PermissionsBits.ReadMessages); } set { SetBit(PermissionsBits.ReadMessages, value); } } | |||
/// <summary> If True, a user may send messages. </summary> | |||
public bool? SendMessages { get { return GetBit(PermissionsBits.SendMessages); } set { SetBit(PermissionsBits.SendMessages, value); } } | |||
/// <summary> If True, a user may send text-to-speech messages. </summary> | |||
public bool? SendTTSMessages { get { return GetBit(PermissionsBits.SendTTSMessages); } set { SetBit(PermissionsBits.SendTTSMessages, value); } } | |||
/// <summary> If True, a user may delete messages. </summary> | |||
public bool? ManageMessages { get { return GetBit(PermissionsBits.ManageMessages); } set { SetBit(PermissionsBits.ManageMessages, value); } } | |||
/// <summary> If True, Discord will auto-embed links sent by this user. </summary> | |||
public bool? EmbedLinks { get { return GetBit(PermissionsBits.EmbedLinks); } set { SetBit(PermissionsBits.EmbedLinks, value); } } | |||
/// <summary> If True, a user may send files. </summary> | |||
public bool? AttachFiles { get { return GetBit(PermissionsBits.AttachFiles); } set { SetBit(PermissionsBits.AttachFiles, value); } } | |||
/// <summary> If True, a user may read previous messages. </summary> | |||
public bool? ReadMessageHistory { get { return GetBit(PermissionsBits.ReadMessageHistory); } set { SetBit(PermissionsBits.ReadMessageHistory, value); } } | |||
/// <summary> If True, a user may mention @everyone. </summary> | |||
public bool? MentionEveryone { get { return GetBit(PermissionsBits.MentionEveryone); } set { SetBit(PermissionsBits.MentionEveryone, value); } } | |||
/// <summary> If True, a user may connect to a voice channel. </summary> | |||
public bool? Connect { get { return GetBit(PermissionsBits.Connect); } set { SetBit(PermissionsBits.Connect, value); } } | |||
/// <summary> If True, a user may speak in a voice channel. </summary> | |||
public bool? Speak { get { return GetBit(PermissionsBits.Speak); } set { SetBit(PermissionsBits.Speak, value); } } | |||
/// <summary> If True, a user may mute users. </summary> | |||
public bool? MuteMembers { get { return GetBit(PermissionsBits.MuteMembers); } set { SetBit(PermissionsBits.MuteMembers, value); } } | |||
/// <summary> If True, a user may deafen users. </summary> | |||
public bool? DeafenMembers { get { return GetBit(PermissionsBits.DeafenMembers); } set { SetBit(PermissionsBits.DeafenMembers, value); } } | |||
/// <summary> If True, a user may move other users between voice channels. </summary> | |||
public bool? MoveMembers { get { return GetBit(PermissionsBits.MoveMembers); } set { SetBit(PermissionsBits.MoveMembers, value); } } | |||
/// <summary> If True, a user may use voice activation rather than push-to-talk. </summary> | |||
public bool? UseVoiceActivation { get { return GetBit(PermissionsBits.UseVoiceActivation); } set { SetBit(PermissionsBits.UseVoiceActivation, value); } } | |||
/// <summary> If True, a user may adjust permissions. This also implictly grants all other permissions. </summary> | |||
public bool? ManagePermissions { get { return GetBit(PermissionsBits.ManageRolesOrPermissions); } set { SetBit(PermissionsBits.ManageRolesOrPermissions, value); } } | |||
/// <summary> If True, a user may create, delete and modify this channel. </summary> | |||
public bool? ManageChannel { get { return GetBit(PermissionsBits.ManageChannel); } set { SetBit(PermissionsBits.ManageChannel, value); } } | |||
private bool? GetBit(PermissionsBits pos) | |||
{ | |||
if (Allow.GetBit(pos)) | |||
return true; | |||
else if (Deny.GetBit(pos)) | |||
return true; | |||
else | |||
return false; | |||
} | |||
private void SetBit(PermissionsBits pos, bool? value) | |||
{ | |||
if (value == true) | |||
{ | |||
Allow.SetBit(pos, true); | |||
Deny.SetBit(pos, false); | |||
} | |||
else if (value == false) | |||
{ | |||
Allow.SetBit(pos, false); | |||
Deny.SetBit(pos, true); | |||
} | |||
else | |||
{ | |||
Allow.SetBit(pos, false); | |||
Deny.SetBit(pos, false); | |||
} | |||
} | |||
} | |||
} |
@@ -1,15 +1,12 @@ | |||
using Newtonsoft.Json; | |||
using Discord.API; | |||
using Newtonsoft.Json; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
namespace Discord | |||
{ | |||
public sealed class Role | |||
{ | |||
private readonly DiscordClient _client; | |||
/// <summary> Returns the unique identifier for this role. </summary> | |||
public string Id { get; } | |||
public sealed class Role : CachedObject | |||
{ | |||
/// <summary> Returns the name of this role. </summary> | |||
public string Name { get; private set; } | |||
/// <summary> If true, this role is displayed isolated from other users. </summary> | |||
@@ -17,42 +14,44 @@ namespace Discord | |||
/// <summary> Returns the position of this channel in the role list for this server. </summary> | |||
public int Position { get; private set; } | |||
/// <summary> Returns the color of this role. </summary> | |||
public PackedColor Color { get; private set; } | |||
public Color Color { get; private set; } | |||
/// <summary> Returns whether this role is managed by server (e.g. for Twitch integration) </summary> | |||
public bool IsManaged { get; private set; } | |||
/// <summary> Returns the the permissions contained by this role. </summary> | |||
public PackedServerPermissions Permissions { get; } | |||
public ServerPermissions Permissions { get; } | |||
/// <summary> Returns the id of the server this role is a member of. </summary> | |||
public string ServerId { get; } | |||
/// <summary> Returns the server this role is a member of. </summary> | |||
[JsonIgnore] | |||
public Server Server => _client.Servers[ServerId]; | |||
public Server Server => _server.Value; | |||
private readonly Reference<Server> _server; | |||
/// <summary> Returns true if this is the role representing all users in a server. </summary> | |||
public bool IsEveryone { get; } | |||
/// <summary> Returns a list of the ids of all members in this role. </summary> | |||
public IEnumerable<string> MemberIds => IsEveryone ? Server.UserIds : Server.Members.Where(x => x.RoleIds.Contains(Id)).Select(x => x.UserId); | |||
public bool IsEveryone => _server.Id == null || Id == _server.Id; | |||
/// <summary> Returns a list of all members in this role. </summary> | |||
public IEnumerable<Member> Members => IsEveryone ? Server.Members : Server.Members.Where(x => x.RoleIds.Contains(Id)); | |||
[JsonIgnore] | |||
public IEnumerable<User> Members => _server.Id != null ? (IsEveryone ? Server.Members : Server.Members.Where(x => x.HasRole(this))) : new User[0]; | |||
//TODO: Add local members cache | |||
internal Role(DiscordClient client, string id, string serverId, bool isEveryone) | |||
internal Role(DiscordClient client, string id, string serverId) | |||
: base(client, id) | |||
{ | |||
_client = client; | |||
Id = id; | |||
ServerId = serverId; | |||
IsEveryone = isEveryone; | |||
Permissions = new PackedServerPermissions(0); | |||
_server = new Reference<Server>(serverId, x => _client.Servers[x], x => x.AddRole(this), x => x.RemoveRole(this)); | |||
Permissions = new ServerPermissions(0); | |||
Permissions.Lock(); | |||
Color = new PackedColor(0); | |||
Color = new Color(0); | |||
Color.Lock(); | |||
if (isEveryone) | |||
Position = int.MinValue; | |||
} | |||
internal override void LoadReferences() | |||
{ | |||
_server.Load(); | |||
} | |||
internal override void UnloadReferences() | |||
{ | |||
_server.Unload(); | |||
} | |||
internal void Update(API.RoleInfo model) | |||
internal void Update(RoleInfo model) | |||
{ | |||
if (model.Name != null) | |||
Name = model.Name; | |||
@@ -68,7 +67,7 @@ namespace Discord | |||
Permissions.SetRawValueInternal(model.Permissions.Value); | |||
foreach (var member in Members) | |||
member.UpdatePermissions(); | |||
member.UpdateServerPermissions(); | |||
} | |||
public override string ToString() => Name; | |||
@@ -1,4 +1,5 @@ | |||
using Newtonsoft.Json; | |||
using Discord.API; | |||
using Newtonsoft.Json; | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
@@ -6,17 +7,12 @@ using System.Linq; | |||
namespace Discord | |||
{ | |||
public sealed class Server | |||
{ | |||
private readonly DiscordClient _client; | |||
private readonly ConcurrentDictionary<string, bool> _bans, _channels, _invites, _members, _roles; | |||
/// <summary> Returns the unique identifier for this server. </summary> | |||
public string Id { get; } | |||
public sealed class Server : CachedObject | |||
{ | |||
/// <summary> Returns the name of this channel. </summary> | |||
public string Name { get; private set; } | |||
/// <summary> Returns the current logged-in user's data for this server. </summary> | |||
public Member CurrentMember { get; internal set; } | |||
public User CurrentMember { get; internal set; } | |||
/// <summary> Returns the amount of time (in seconds) a user must be inactive for until they are automatically moved to the AFK channel (see AFKChannel). </summary> | |||
public int AFKTimeout { get; private set; } | |||
@@ -28,133 +24,162 @@ namespace Discord | |||
internal string VoiceServer { get; set; }*/ | |||
/// <summary> Returns true if the current user created this server. </summary> | |||
public bool IsOwner => _client.CurrentUserId == OwnerId; | |||
/// <summary> Returns the id of the user that first created this server. </summary> | |||
public string OwnerId { get; private set; } | |||
public bool IsOwner => _client.CurrentUserId == _ownerId; | |||
/// <summary> Returns the user that first created this server. </summary> | |||
[JsonIgnore] | |||
public User Owner => _client.Users[OwnerId]; | |||
public User Owner { get; private set; } | |||
private string _ownerId; | |||
/// <summary> Returns the id of the AFK voice channel for this server (see AFKTimeout). </summary> | |||
public string AFKChannelId { get; private set; } | |||
/// <summary> Returns the AFK voice channel for this server (see AFKTimeout). </summary> | |||
[JsonIgnore] | |||
public Channel AFKChannel => _client.Channels[AFKChannelId]; | |||
public Channel AFKChannel => _afkChannel.Value; | |||
private Reference<Channel> _afkChannel; | |||
/// <summary> Returns the id of the default channel for this server. </summary> | |||
public string DefaultChannelId => Id; | |||
/// <summary> Returns the default channel for this server. </summary> | |||
[JsonIgnore] | |||
public Channel DefaultChannel => _client.Channels[DefaultChannelId]; | |||
/// <summary> Returns a collection of the ids of all users banned on this server. </summary> | |||
[JsonIgnore] | |||
public IEnumerable<string> BanIds => _bans.Select(x => x.Key); | |||
public Channel DefaultChannel { get; private set; } | |||
/// <summary> Returns a collection of the ids of all channels within this server. </summary> | |||
/// <summary> Returns a collection of the ids of all users banned on this server. </summary> | |||
[JsonIgnore] | |||
public IEnumerable<string> ChannelIds => _channels.Select(x => x.Key); | |||
public IEnumerable<string> Bans => _bans.Select(x => x.Key); | |||
private ConcurrentDictionary<string, bool> _bans; | |||
/// <summary> Returns a collection of all channels within this server. </summary> | |||
[JsonIgnore] | |||
public IEnumerable<Channel> Channels => _channels.Select(x => _client.Channels[x.Key]); | |||
public IEnumerable<Channel> Channels => _channels.Select(x => x.Value); | |||
/// <summary> Returns a collection of all channels within this server. </summary> | |||
[JsonIgnore] | |||
public IEnumerable<Channel> TextChannels => _channels.Select(x => _client.Channels[x.Key]).Where(x => x.Type == ChannelTypes.Text); | |||
public IEnumerable<Channel> TextChannels => _channels.Select(x => x.Value).Where(x => x.Type == ChannelType.Text); | |||
/// <summary> Returns a collection of all channels within this server. </summary> | |||
[JsonIgnore] | |||
public IEnumerable<Channel> VoiceChannels => _channels.Select(x => _client.Channels[x.Key]).Where(x => x.Type == ChannelTypes.Voice); | |||
/// <summary> Returns a collection of all invite codes to this server. </summary> | |||
[JsonIgnore] | |||
public IEnumerable<string> InviteCodes => _invites.Select(x => x.Key); | |||
/*/// <summary> Returns a collection of all invites to this server. </summary> | |||
public IEnumerable<Channel> VoiceChannels => _channels.Select(x => x.Value).Where(x => x.Type == ChannelType.Voice); | |||
private ConcurrentDictionary<string, Channel> _channels; | |||
/// <summary> Returns a collection of all invites to this server. </summary> | |||
[JsonIgnore] | |||
public IEnumerable<Invite> Invites => _invites.Select(x => _client.Invites[x.Key]);*/ | |||
public IEnumerable<Invite> Invites => _invites.Values; | |||
private ConcurrentDictionary<string, Invite> _invites; | |||
/// <summary> Returns a collection of all users within this server with their server-specific data. </summary> | |||
[JsonIgnore] | |||
public IEnumerable<Member> Members => _members.Select(x => _client.Members[x.Key, Id]); | |||
/// <summary> Returns a collection of the ids of all users within this server. </summary> | |||
[JsonIgnore] | |||
public IEnumerable<string> UserIds => _members.Select(x => x.Key); | |||
/// <summary> Returns a collection of all users within this server. </summary> | |||
[JsonIgnore] | |||
public IEnumerable<User> Users => _members.Select(x => _client.Users[x.Key]); | |||
public IEnumerable<User> Members => _members.Select(x => x.Value); | |||
private ConcurrentDictionary<string, User> _members; | |||
/// <summary> Return the id of the role representing all users in a server. </summary> | |||
public string EveryoneRoleId { get; private set; } | |||
/// <summary> Return the the role representing all users in a server. </summary> | |||
public Role EveryoneRole => _client.Roles[EveryoneRoleId]; | |||
/// <summary> Returns a collection of the ids of all roles within this server. </summary> | |||
[JsonIgnore] | |||
public IEnumerable<string> RoleIds => _roles.Select(x => x.Key); | |||
public Role EveryoneRole { get; private set; } | |||
/// <summary> Returns a collection of all roles within this server. </summary> | |||
[JsonIgnore] | |||
public IEnumerable<Role> Roles => _roles.Select(x => _client.Roles[x.Key]); | |||
public IEnumerable<Role> Roles => _roles.Select(x => x.Value); | |||
private ConcurrentDictionary<string, Role> _roles; | |||
internal Server(DiscordClient client, string id) | |||
: base(client, id) | |||
{ | |||
_client = client; | |||
Id = id; | |||
_afkChannel = new Reference<Channel>(x => _client.Channels[x]); | |||
//Global Cache | |||
_channels = new ConcurrentDictionary<string, Channel>(); | |||
_members = new ConcurrentDictionary<string, User>(); | |||
_roles = new ConcurrentDictionary<string, Role>(); | |||
//Local Cache | |||
_bans = new ConcurrentDictionary<string, bool>(); | |||
_channels = new ConcurrentDictionary<string, bool>(); | |||
_invites = new ConcurrentDictionary<string, bool>(); | |||
_members = new ConcurrentDictionary<string, bool>(); | |||
_roles = new ConcurrentDictionary<string, bool>(); | |||
} | |||
_invites = new ConcurrentDictionary<string, Invite>(); | |||
} | |||
internal override void LoadReferences() | |||
{ | |||
_afkChannel.Load(); | |||
} | |||
internal override void UnloadReferences() | |||
{ | |||
//Global Cache | |||
var globalChannels = _client.Channels; | |||
var channels = _channels; | |||
foreach (var channel in channels) | |||
globalChannels.TryRemove(channel.Key); | |||
channels.Clear(); | |||
var globalUsers = _client.Users; | |||
var members = _members; | |||
foreach (var member in members) | |||
globalUsers.TryRemove(member.Key, Id); | |||
members.Clear(); | |||
var globalRoles = _client.Roles; | |||
var roles = _roles; | |||
foreach (var role in roles) | |||
globalRoles.TryRemove(role.Key); | |||
roles.Clear(); | |||
//Local Cache | |||
var invites = _invites; | |||
foreach (var invite in invites) | |||
invite.Value.Uncache(); | |||
invites.Clear(); | |||
_bans.Clear(); | |||
_afkChannel.Unload(); | |||
} | |||
internal void Update(API.GuildInfo model) | |||
internal void Update(GuildInfo model) | |||
{ | |||
AFKChannelId = model.AFKChannelId; | |||
AFKTimeout = model.AFKTimeout; | |||
if (model.JoinedAt.HasValue) | |||
if (model.AFKTimeout != null) | |||
AFKTimeout = model.AFKTimeout.Value; | |||
if (model.AFKChannelId != null) | |||
if (model.JoinedAt != null) | |||
JoinedAt = model.JoinedAt.Value; | |||
Name = model.Name; | |||
OwnerId = model.OwnerId; | |||
Region = model.Region; | |||
var roles = _client.Roles; | |||
bool isEveryone = true; //Assumes first role is always everyone | |||
foreach (var subModel in model.Roles) | |||
if (model.Name != null) | |||
Name = model.Name; | |||
if (model.OwnerId != null && _ownerId != model.OwnerId) | |||
{ | |||
var role = roles.GetOrAdd(subModel.Id, Id, isEveryone); | |||
role.Update(subModel); | |||
if (isEveryone) | |||
EveryoneRoleId = subModel.Id; | |||
isEveryone = false; | |||
_ownerId = model.OwnerId; | |||
Owner = _client.Users[_ownerId, Id]; | |||
} | |||
if (model.Region != null) | |||
Region = model.Region; | |||
if (model.Roles != null) | |||
{ | |||
var roleCache = _client.Roles; | |||
foreach (var x in model.Roles) | |||
{ | |||
var role = roleCache.GetOrAdd(x.Id, Id); | |||
role.Update(x); | |||
} | |||
} | |||
_afkChannel.Id = model.AFKChannelId; //Can be null | |||
} | |||
internal void Update(API.ExtendedGuildInfo model) | |||
internal void Update(ExtendedGuildInfo model) | |||
{ | |||
Update(model as API.GuildInfo); | |||
Update(model as GuildInfo); | |||
var channels = _client.Channels; | |||
foreach (var subModel in model.Channels) | |||
{ | |||
var channel = channels.GetOrAdd(subModel.Id, Id); | |||
channel.Update(subModel); | |||
} | |||
var users = _client.Users; | |||
var members = _client.Members; | |||
var usersCache = _client.Users; | |||
foreach (var subModel in model.Members) | |||
{ | |||
var user = users.GetOrAdd(subModel.User.Id); | |||
user.Update(subModel.User); | |||
var member = members.GetOrAdd(subModel.User.Id, Id); | |||
member.Update(subModel); | |||
var user = usersCache.GetOrAdd(subModel.User.Id, Id); | |||
user.Update(subModel); | |||
} | |||
foreach (var subModel in model.VoiceStates) | |||
{ | |||
var member = members[subModel.UserId, Id]; | |||
if (member != null) | |||
member.Update(subModel); | |||
var user = usersCache[subModel.UserId, Id]; | |||
if (user != null) | |||
user.Update(subModel); | |||
} | |||
foreach (var subModel in model.Presences) | |||
{ | |||
var member = members[subModel.User.Id, Id]; | |||
if (member != null) | |||
member.Update(subModel); | |||
var user = usersCache[subModel.User.Id, Id]; | |||
if (user != null) | |||
user.Update(subModel); | |||
} | |||
} | |||
@@ -170,62 +195,71 @@ namespace Discord | |||
return _bans.TryRemove(banId, out ignored); | |||
} | |||
internal void AddChannel(string channelId) | |||
internal void AddChannel(Channel channel) | |||
{ | |||
_channels.TryAdd(channelId, true); | |||
foreach (var member in Members) | |||
member.AddChannel(channelId); | |||
if (_channels.TryAdd(channel.Id, channel)) | |||
{ | |||
if (channel.Id == Id) | |||
DefaultChannel = channel; | |||
foreach (var member in Members) | |||
member.AddChannel(channel); | |||
} | |||
} | |||
internal bool RemoveChannel(string channelId) | |||
internal void RemoveChannel(Channel channel) | |||
{ | |||
bool ignored; | |||
foreach (var member in Members) | |||
member.RemoveChannel(channelId); | |||
return _channels.TryRemove(channelId, out ignored); | |||
member.RemoveChannel(channel); | |||
_channels.TryRemove(channel.Id, out channel); | |||
} | |||
internal void AddInvite(string inviteId) | |||
{ | |||
_invites.TryAdd(inviteId, true); | |||
} | |||
internal bool RemoveInvite(string inviteId) | |||
{ | |||
bool ignored; | |||
return _invites.TryRemove(inviteId, out ignored); | |||
} | |||
internal void AddInvite(Invite invite) => _invites.TryAdd(invite.Id, invite); | |||
internal void RemoveInvite(Invite invite) => _invites.TryRemove(invite.Id, out invite); | |||
internal void AddMember(Member member) | |||
internal void AddMember(User user) | |||
{ | |||
_members.TryAdd(member.UserId, true); | |||
foreach (var channel in Channels) | |||
if (_members.TryAdd(user.Id, user)) | |||
{ | |||
member.AddChannel(channel.Id); | |||
channel.InvalidatePermissionsCache(member.UserId); | |||
if (user.Id == _ownerId) | |||
Owner = user; | |||
foreach (var channel in Channels) | |||
{ | |||
user.AddChannel(channel); | |||
channel.InvalidatePermissionsCache(user); | |||
} | |||
} | |||
} | |||
internal bool RemoveMember(Member member) | |||
internal void RemoveMember(User user) | |||
{ | |||
bool ignored; | |||
foreach (var channel in Channels) | |||
if (_members.TryRemove(user.Id, out user)) | |||
{ | |||
member.RemoveChannel(channel.Id); | |||
channel.InvalidatePermissionsCache(member.UserId); | |||
if (user.Id == _ownerId) | |||
Owner = null; | |||
foreach (var channel in Channels) | |||
{ | |||
user.RemoveChannel(channel); | |||
channel.InvalidatePermissionsCache(user); | |||
} | |||
} | |||
return _members.TryRemove(member.UserId, out ignored); | |||
} | |||
internal bool HasMember(string userId) | |||
{ | |||
return _members.ContainsKey(userId); | |||
} | |||
internal void HasMember(User user) => _members.ContainsKey(user.Id); | |||
internal void AddRole(string roleId) | |||
internal void AddRole(Role role) | |||
{ | |||
_roles.TryAdd(roleId, true); | |||
if (_roles.TryAdd(role.Id, role)) | |||
{ | |||
if (role.Id == Id) | |||
EveryoneRole = role; | |||
} | |||
} | |||
internal bool RemoveRole(string roleId) | |||
internal void RemoveRole(Role role) | |||
{ | |||
bool ignored; | |||
return _roles.TryRemove(roleId, out ignored); | |||
if (_roles.TryRemove(role.Id, out role)) | |||
{ | |||
if (role.Id == Id) | |||
EveryoneRole = null; | |||
} | |||
} | |||
} | |||
} |
@@ -1,21 +1,22 @@ | |||
using Newtonsoft.Json; | |||
using Discord.API; | |||
using Newtonsoft.Json; | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading; | |||
namespace Discord | |||
{ | |||
public sealed class User | |||
public class User : CachedObject | |||
{ | |||
private readonly DiscordClient _client; | |||
private readonly ConcurrentDictionary<string, bool> _servers; | |||
private int _refCount; | |||
private DateTime? _lastPrivateActivity; | |||
internal static string GetId(string userId, string serverId) => (serverId ?? "Private") + '_' + userId; | |||
/// <summary> Returns the unique identifier for this user. </summary> | |||
public string Id { get; } | |||
private ConcurrentDictionary<string, Channel> _channels; | |||
private ConcurrentDictionary<string, ChannelPermissions> _permissions; | |||
private ServerPermissions _serverPermissions; | |||
/// <summary> Returns a unique identifier combining this user's id with its server's. </summary> | |||
internal string UniqueId => GetId(Id, _server.Id); | |||
/// <summary> Returns the name of this user on this server. </summary> | |||
public string Name { get; private set; } | |||
/// <summary> Returns a by-name unique identifier separating this user from others with the same name. </summary> | |||
@@ -24,60 +25,136 @@ namespace Discord | |||
public string AvatarId { get; private set; } | |||
/// <summary> Returns the URL to this user's current avatar. </summary> | |||
public string AvatarUrl => API.Endpoints.UserAvatar(Id, AvatarId); | |||
/// <summary> Returns the datetime that this user joined this server. </summary> | |||
public DateTime JoinedAt { get; private set; } | |||
public bool IsSelfMuted { get; private set; } | |||
public bool IsSelfDeafened { get; private set; } | |||
public bool IsServerMuted { get; private set; } | |||
public bool IsServerDeafened { get; private set; } | |||
public bool IsServerSuppressed { get; private set; } | |||
public bool IsSpeaking { get; internal set; } | |||
public string SessionId { get; private set; } | |||
public string Token { get; private set; } | |||
/// <summary> Returns the id for the game this user is currently playing. </summary> | |||
public string GameId { get; private set; } | |||
/// <summary> Returns the current status for this user. </summary> | |||
public UserStatus Status { get; private set; } | |||
/// <summary> Returns the time this user last sent/edited a message, started typing or sent voice data in this server. </summary> | |||
public DateTime? LastActivityAt { get; private set; } | |||
/// <summary> Returns the time this user was last seen online in this server. </summary> | |||
public DateTime LastOnlineAt => Status != UserStatus.Offline ? DateTime.UtcNow : _lastOnline; | |||
private DateTime _lastOnline; | |||
/// <summary> Returns the email for this user. </summary> | |||
/// <remarks> This field is only ever populated for the current logged in user. </remarks> | |||
/// <summary> Returns the private messaging channel with this user, if one exists. </summary> | |||
[JsonIgnore] | |||
public string Email { get; private set; } | |||
/// <summary> Returns if the email for this user has been verified. </summary> | |||
/// <remarks> This field is only ever populated for the current logged in user. </remarks> | |||
public Channel PrivateChannel => GlobalUser.PrivateChannel; | |||
[JsonIgnore] | |||
public bool? IsVerified { get; private set; } | |||
internal GlobalUser GlobalUser => _globalUser.Value; | |||
private readonly Reference<GlobalUser> _globalUser; | |||
/// <summary> Returns the Id of the private messaging channel with this user, if one exists. </summary> | |||
public string PrivateChannelId { get; internal set; } | |||
/// <summary> Returns the private messaging channel with this user, if one exists. </summary> | |||
[JsonIgnore] | |||
public Channel PrivateChannel => _client.Channels[PrivateChannelId]; | |||
public Server Server => _server.Value; | |||
private readonly Reference<Server> _server; | |||
/// <summary> Returns a collection of all server-specific data for every server this user is a member of. </summary> | |||
public IEnumerable<Member> Memberships => _servers.Select(x => _client.GetMember(x.Key, Id)); | |||
/// <summary> Returns a collection of all servers this user is a member of. </summary> | |||
public IEnumerable<Server> Servers => _servers.Select(x => _client.GetServer(x.Key)); | |||
/// <summary> Returns a collection of the ids of all servers this user is a member of. </summary> | |||
public IEnumerable<string> ServersIds => _servers.Select(x => x.Key); | |||
/// <summary> Returns a collection of all messages this user has sent that are still in cache. </summary> | |||
public IEnumerable<Message> Messages => _client.Messages.Where(x => x.UserId == Id); | |||
[JsonIgnore] | |||
public Channel VoiceChannel { get; private set; } | |||
/// <summary> Returns the id for the game this user is currently playing. </summary> | |||
/*public string GameId => Memberships.Where(x => x.GameId != null).Select(x => x.GameId).FirstOrDefault(); | |||
/// <summary> Returns the current status for this user. </summary> | |||
public string Status => Memberships.OrderByDescending(x => x.StatusSince).Select(x => x.Status).FirstOrDefault(); | |||
/// <summary> Returns the time this user's status was last changed. </summary> | |||
public DateTime StatusSince => Memberships.OrderByDescending(x => x.StatusSince).Select(x => x.StatusSince).First(); | |||
/// <summary> Returns the time this user last sent/edited a message, started typing or sent voice data. </summary> | |||
public DateTime? LastActivity | |||
[JsonIgnore] | |||
public IEnumerable<Role> Roles => _roles.Select(x => x.Value); | |||
private Dictionary<string, Role> _roles; | |||
/// <summary> Returns a collection of all messages this user has sent on this server that are still in cache. </summary> | |||
[JsonIgnore] | |||
public IEnumerable<Message> Messages | |||
{ | |||
get | |||
{ | |||
var lastServerActivity = Memberships.OrderByDescending(x => x.LastActivity).Select(x => x.LastActivity).FirstOrDefault(); | |||
if (lastServerActivity == null || (_lastPrivateActivity != null && _lastPrivateActivity.Value > lastServerActivity.Value)) | |||
return _lastPrivateActivity; | |||
if (_server.Id != null) | |||
return Server.Channels.SelectMany(x => x.Messages.Where(y => y.User.Id == Id)); | |||
else | |||
return lastServerActivity; | |||
return GlobalUser.PrivateChannel.Messages.Where(x => x.User.Id == Id); | |||
} | |||
} | |||
/// <summary> Returns a collection of all channels this user is a member of. </summary> | |||
[JsonIgnore] | |||
public IEnumerable<Channel> Channels | |||
{ | |||
get | |||
{ | |||
if (_server.Id != null) | |||
{ | |||
return _permissions | |||
.Where(x => x.Value.ReadMessages) | |||
.Select(x => | |||
{ | |||
Channel channel = null; | |||
_channels.TryGetValue(x.Key, out channel); | |||
return channel; | |||
}) | |||
.Where(x => x != null); | |||
} | |||
else | |||
{ | |||
var privateChannel = PrivateChannel; | |||
if (privateChannel != null) | |||
return new Channel[] { privateChannel }; | |||
else | |||
return new Channel[0]; | |||
} | |||
} | |||
} | |||
/// <summary> Returns the time this user was last seen online. </summary> | |||
public DateTime? LastOnline => Memberships.OrderByDescending(x => x.LastOnline).Select(x => x.LastOnline).FirstOrDefault();*/ | |||
internal User(DiscordClient client, string id) | |||
internal User(DiscordClient client, string id, string serverId) | |||
: base(client, id) | |||
{ | |||
_client = client; | |||
Id = id; | |||
_servers = new ConcurrentDictionary<string, bool>(); | |||
} | |||
_globalUser = new Reference<GlobalUser>(id, | |||
x => _client.GlobalUsers.GetOrAdd(x), | |||
x => x.AddUser(this), | |||
x => x.RemoveUser(this)); | |||
_server = new Reference<Server>(serverId, | |||
x => _client.Servers[x], | |||
x => | |||
{ | |||
x.AddMember(this); | |||
if (x.Id == _client.CurrentUserId) | |||
x.CurrentMember = this; | |||
}, | |||
x => | |||
{ | |||
x.RemoveMember(this); | |||
if (x.Id == _client.CurrentUserId) | |||
x.CurrentMember = null; | |||
}); | |||
Status = UserStatus.Offline; | |||
_channels = new ConcurrentDictionary<string, Channel>(); | |||
if (serverId != null) | |||
{ | |||
_permissions = new ConcurrentDictionary<string, ChannelPermissions>(); | |||
_serverPermissions = new ServerPermissions(); | |||
} | |||
if (serverId == null) | |||
UpdateRoles(null); | |||
} | |||
internal override void LoadReferences() | |||
{ | |||
_globalUser.Load(); | |||
_server.Load(); | |||
} | |||
internal override void UnloadReferences() | |||
{ | |||
_globalUser.Unload(); | |||
_server.Unload(); | |||
} | |||
internal void Update(API.UserReference model) | |||
public override string ToString() => Id; | |||
internal void Update(UserReference model) | |||
{ | |||
if (model.Avatar != null) | |||
AvatarId = model.Avatar; | |||
@@ -86,38 +163,201 @@ namespace Discord | |||
if (model.Username != null) | |||
Name = model.Username; | |||
} | |||
internal void Update(API.SelfUserInfo model) | |||
internal void Update(MemberInfo model) | |||
{ | |||
if (model.User != null) | |||
Update(model.User); | |||
if (model.JoinedAt.HasValue) | |||
JoinedAt = model.JoinedAt.Value; | |||
if (model.Roles != null) | |||
UpdateRoles(model.Roles.Select(x => _client.Roles[x])); | |||
UpdateServerPermissions(); | |||
} | |||
internal void Update(ExtendedMemberInfo model) | |||
{ | |||
Update(model as API.MemberInfo); | |||
if (model.IsServerDeafened != null) | |||
IsServerDeafened = model.IsServerDeafened.Value; | |||
if (model.IsServerMuted != null) | |||
IsServerMuted = model.IsServerMuted.Value; | |||
} | |||
internal void Update(PresenceInfo model) | |||
{ | |||
if (model.User != null) | |||
Update(model.User as UserReference); | |||
if (model.Roles != null) | |||
UpdateRoles(model.Roles.Select(x => _client.Roles[x])); | |||
if (model.Status != null && Status != model.Status) | |||
{ | |||
Status = UserStatus.FromString(model.Status); | |||
if (Status == UserStatus.Offline) | |||
_lastOnline = DateTime.UtcNow; | |||
} | |||
GameId = model.GameId; //Allows null | |||
} | |||
internal void Update(VoiceMemberInfo model) | |||
{ | |||
Update(model as API.UserReference); | |||
Email = model.Email; | |||
IsVerified = model.IsVerified; | |||
if (model.IsServerDeafened != null) | |||
IsServerDeafened = model.IsServerDeafened.Value; | |||
if (model.IsServerMuted != null) | |||
IsServerMuted = model.IsServerMuted.Value; | |||
if (model.SessionId != null) | |||
SessionId = model.SessionId; | |||
if (model.Token != null) | |||
Token = model.Token; | |||
if (model.ChannelId != null) | |||
VoiceChannel = _client.Channels[model.ChannelId]; | |||
if (model.IsSelfDeafened != null) | |||
IsSelfDeafened = model.IsSelfDeafened.Value; | |||
if (model.IsSelfMuted != null) | |||
IsSelfMuted = model.IsSelfMuted.Value; | |||
if (model.IsServerSuppressed != null) | |||
IsServerSuppressed = model.IsServerSuppressed.Value; | |||
} | |||
private void UpdateRoles(IEnumerable<Role> roles) | |||
{ | |||
if (_server.Id != null) | |||
{ | |||
Dictionary<string, Role> newRoles; | |||
if (roles != null) | |||
newRoles = roles.ToDictionary(x => x.Id, x => x); | |||
else | |||
newRoles = new Dictionary<string, Role>(); | |||
var everyone = Server.EveryoneRole; | |||
newRoles.Add(everyone.Id, everyone); | |||
_roles = newRoles; | |||
} | |||
else | |||
_roles = new Dictionary<string, Role>(); | |||
} | |||
internal void UpdateActivity(DateTime? activity = null) | |||
{ | |||
if (_lastPrivateActivity == null || activity > _lastPrivateActivity.Value) | |||
_lastPrivateActivity = activity ?? DateTime.UtcNow; | |||
if (LastActivityAt == null || activity > LastActivityAt.Value) | |||
LastActivityAt = activity ?? DateTime.UtcNow; | |||
} | |||
public override string ToString() => Name; | |||
internal void UpdateServerPermissions() | |||
{ | |||
if (_roles == null) return; // We don't have all our data processed yet, this will be called again soon | |||
var server = Server; | |||
if (server == null) return; | |||
uint newPermissions = 0x0; | |||
uint oldPermissions = _serverPermissions.RawValue; | |||
if (server.Owner == this) | |||
newPermissions = ServerPermissions.All.RawValue; | |||
else | |||
{ | |||
//var roles = Roles.OrderBy(x => x.Id); | |||
var roles = Roles; | |||
foreach (var serverRole in roles) | |||
newPermissions |= serverRole.Permissions.RawValue; | |||
} | |||
if (BitHelper.GetBit(newPermissions, (int)PermissionsBits.ManageRolesOrPermissions)) | |||
newPermissions = ServerPermissions.All.RawValue; | |||
internal void AddServer(string serverId) | |||
if (newPermissions != oldPermissions) | |||
{ | |||
_serverPermissions.SetRawValueInternal(newPermissions); | |||
foreach (var channel in _channels) | |||
UpdateChannelPermissions(channel.Value); | |||
} | |||
} | |||
internal void UpdateChannelPermissions(Channel channel) | |||
{ | |||
_servers.TryAdd(serverId, true); | |||
if (_roles == null) return; // We don't have all our data processed yet, this will be called again soon | |||
var server = Server; | |||
if (server == null) return; | |||
if (channel.Server != server) throw new InvalidOperationException(); | |||
ChannelPermissions permissions; | |||
if (!_permissions.TryGetValue(channel.Id, out permissions)) return; | |||
uint newPermissions = _serverPermissions.RawValue; | |||
uint oldPermissions = permissions.RawValue; | |||
if (server.Owner == this) | |||
newPermissions = ChannelPermissions.All(channel).RawValue; | |||
else | |||
{ | |||
var channelOverwrites = channel.PermissionOverwrites; | |||
//var roles = Roles.OrderBy(x => x.Id); | |||
var roles = Roles; | |||
foreach (var denyRole in channelOverwrites.Where(x => x.TargetType == PermissionTarget.Role && x.Deny.RawValue != 0 && roles.Any(y => y.Id == x.TargetId))) | |||
newPermissions &= ~denyRole.Deny.RawValue; | |||
foreach (var allowRole in channelOverwrites.Where(x => x.TargetType == PermissionTarget.Role && x.Allow.RawValue != 0 && roles.Any(y => y.Id == x.TargetId))) | |||
newPermissions |= allowRole.Allow.RawValue; | |||
foreach (var denyUser in channelOverwrites.Where(x => x.TargetType == PermissionTarget.User && x.TargetId == Id && x.Deny.RawValue != 0)) | |||
newPermissions &= ~denyUser.Deny.RawValue; | |||
foreach (var allowUser in channelOverwrites.Where(x => x.TargetType == PermissionTarget.User && x.TargetId == Id && x.Allow.RawValue != 0)) | |||
newPermissions |= allowUser.Allow.RawValue; | |||
} | |||
if (BitHelper.GetBit(newPermissions, (int)PermissionsBits.ManageRolesOrPermissions)) | |||
newPermissions = ChannelPermissions.All(channel).RawValue; | |||
if (newPermissions != oldPermissions) | |||
{ | |||
permissions.SetRawValueInternal(newPermissions); | |||
channel.InvalidateMembersCache(); | |||
} | |||
permissions.SetRawValueInternal(newPermissions); | |||
} | |||
internal bool RemoveServer(string serverId) | |||
public ServerPermissions GetServerPermissions() => _serverPermissions; | |||
public ChannelPermissions GetPermissions(Channel channel) | |||
{ | |||
bool ignored; | |||
return _servers.TryRemove(serverId, out ignored); | |||
if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||
//Return static permissions if this is a private chat | |||
if (_server.Id == null) | |||
return ChannelPermissions.PrivateOnly; | |||
ChannelPermissions perms; | |||
if (_permissions.TryGetValue(channel.Id, out perms)) | |||
return perms; | |||
return null; | |||
} | |||
public void AddRef() | |||
internal void AddChannel(Channel channel) | |||
{ | |||
if (_server.Id != null) | |||
{ | |||
var perms = new ChannelPermissions(); | |||
perms.Lock(); | |||
_channels.TryAdd(channel.Id, channel); | |||
_permissions.TryAdd(channel.Id, perms); | |||
UpdateChannelPermissions(channel); | |||
} | |||
} | |||
internal void RemoveChannel(Channel channel) | |||
{ | |||
Interlocked.Increment(ref _refCount); | |||
if (_server.Id != null) | |||
{ | |||
ChannelPermissions ignored; | |||
_channels.TryRemove(channel.Id, out channel); | |||
_permissions.TryRemove(channel.Id, out ignored); | |||
} | |||
} | |||
public void RemoveRef() | |||
public bool HasRole(Role role) | |||
{ | |||
if (Interlocked.Decrement(ref _refCount) == 0) | |||
_client.Users.TryRemove(Id); | |||
if (role == null) throw new ArgumentNullException(nameof(role)); | |||
return _roles.ContainsKey(role.Id); | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,13 @@ | |||
using System.Net.Http; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
namespace Discord.Net.Rest | |||
{ | |||
internal interface IRestEngine | |||
{ | |||
void SetToken(string token); | |||
Task<string> Send(HttpMethod method, string path, string json, CancellationToken cancelToken); | |||
Task<string> SendFile(HttpMethod method, string path, string filePath, CancellationToken cancelToken); | |||
} | |||
} |
@@ -1,9 +1,9 @@ | |||
using System; | |||
using System.Net.Http; | |||
namespace Discord.API | |||
namespace Discord.Net.Rest | |||
{ | |||
internal partial class RestClient | |||
internal sealed partial class RestClient | |||
{ | |||
public class RequestEventArgs : EventArgs | |||
{ | |||
@@ -21,7 +21,7 @@ namespace Discord.API | |||
} | |||
public event EventHandler<RequestEventArgs> OnRequest; | |||
protected void RaiseOnRequest(HttpMethod method, string path, string payload, double milliseconds) | |||
private void RaiseOnRequest(HttpMethod method, string path, string payload, double milliseconds) | |||
{ | |||
if (OnRequest != null) | |||
OnRequest(this, new RequestEventArgs(method, path, payload, milliseconds)); |
@@ -1,32 +1,28 @@ | |||
using Newtonsoft.Json; | |||
using Discord.API; | |||
using Newtonsoft.Json; | |||
using System; | |||
using System.Diagnostics; | |||
using System.Net.Http; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
namespace Discord.API | |||
namespace Discord.Net.Rest | |||
{ | |||
internal interface IRestEngine | |||
{ | |||
void SetToken(string token); | |||
Task<string> Send(HttpMethod method, string path, string json, CancellationToken cancelToken); | |||
Task<string> SendFile(HttpMethod method, string path, string filePath, CancellationToken cancelToken); | |||
} | |||
internal partial class RestClient | |||
internal sealed partial class RestClient | |||
{ | |||
private readonly DiscordAPIClientConfig _config; | |||
private readonly IRestEngine _engine; | |||
private readonly LogMessageSeverity _logLevel; | |||
private CancellationToken _cancelToken; | |||
public RestClient(LogMessageSeverity logLevel, string userAgent, int timeout) | |||
public RestClient(DiscordAPIClientConfig config) | |||
{ | |||
_logLevel = logLevel; | |||
_engine = new RestSharpRestEngine(userAgent, timeout); | |||
} | |||
_config = config; | |||
_engine = new SharpRestEngine(config); | |||
} | |||
public void SetToken(string token) => _engine.SetToken(token); | |||
//DELETE | |||
private static readonly HttpMethod _delete = HttpMethod.Delete; | |||
internal Task<ResponseT> Delete<ResponseT>(string path, object data) where ResponseT : class | |||
=> Send<ResponseT>(_delete, path, data); | |||
@@ -37,12 +33,14 @@ namespace Discord.API | |||
internal Task Delete(string path) | |||
=> Send(_delete, path); | |||
//GET | |||
private static readonly HttpMethod _get = HttpMethod.Get; | |||
internal Task<ResponseT> Get<ResponseT>(string path) where ResponseT : class | |||
=> Send<ResponseT>(_get, path); | |||
internal Task Get(string path) | |||
=> Send(_get, path); | |||
//PATCH | |||
private static readonly HttpMethod _patch = new HttpMethod("PATCH"); | |||
internal Task<ResponseT> Patch<ResponseT>(string path, object data) where ResponseT : class | |||
=> Send<ResponseT>(_patch, path, data); | |||
@@ -94,7 +92,7 @@ namespace Discord.API | |||
if (content != null) | |||
requestJson = JsonConvert.SerializeObject(content); | |||
if (_logLevel >= LogMessageSeverity.Verbose) | |||
if (_config.LogLevel >= LogMessageSeverity.Verbose) | |||
stopwatch = Stopwatch.StartNew(); | |||
string responseJson = await _engine.Send(method, path, requestJson, _cancelToken).ConfigureAwait(false); | |||
@@ -104,10 +102,10 @@ namespace Discord.API | |||
throw new Exception("API check failed: Response is not empty."); | |||
#endif | |||
if (_logLevel >= LogMessageSeverity.Verbose) | |||
if (_config.LogLevel >= LogMessageSeverity.Verbose) | |||
{ | |||
stopwatch.Stop(); | |||
if (content != null && _logLevel >= LogMessageSeverity.Debug) | |||
if (content != null && _config.LogLevel >= LogMessageSeverity.Debug) | |||
{ | |||
if (path.StartsWith(Endpoints.Auth)) | |||
RaiseOnRequest(method, path, "[Hidden]", stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerMillisecond); | |||
@@ -133,7 +131,7 @@ namespace Discord.API | |||
{ | |||
Stopwatch stopwatch = null; | |||
if (_logLevel >= LogMessageSeverity.Verbose) | |||
if (_config.LogLevel >= LogMessageSeverity.Verbose) | |||
stopwatch = Stopwatch.StartNew(); | |||
string responseJson = await _engine.SendFile(method, path, filePath, _cancelToken).ConfigureAwait(false); | |||
@@ -143,10 +141,10 @@ namespace Discord.API | |||
throw new Exception("API check failed: Response is not empty."); | |||
#endif | |||
if (_logLevel >= LogMessageSeverity.Verbose) | |||
if (_config.LogLevel >= LogMessageSeverity.Verbose) | |||
{ | |||
stopwatch.Stop(); | |||
if (_logLevel >= LogMessageSeverity.Debug) | |||
if (_config.LogLevel >= LogMessageSeverity.Debug) | |||
RaiseOnRequest(method, path, filePath, stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerMillisecond); | |||
else | |||
RaiseOnRequest(method, path, null, stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerMillisecond); | |||
@@ -173,7 +171,6 @@ namespace Discord.API | |||
#endif | |||
} | |||
internal void SetToken(string token) => _engine.SetToken(token); | |||
internal void SetCancelToken(CancellationToken token) => _cancelToken = token; | |||
} | |||
} |
@@ -1,28 +1,35 @@ | |||
using RestSharp; | |||
using Discord.API; | |||
using RestSharp; | |||
using System; | |||
using System.IO; | |||
using System.Net; | |||
using System.Net.Http; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
namespace Discord.API | |||
namespace Discord.Net.Rest | |||
{ | |||
internal class RestSharpRestEngine : IRestEngine | |||
internal sealed class SharpRestEngine : IRestEngine | |||
{ | |||
private readonly DiscordAPIClientConfig _config; | |||
private readonly RestSharp.RestClient _client; | |||
public RestSharpRestEngine(string userAgent, int timeout) | |||
public SharpRestEngine(DiscordAPIClientConfig config) | |||
{ | |||
_config = config; | |||
_client = new RestSharp.RestClient(Endpoints.BaseApi) | |||
{ | |||
PreAuthenticate = false | |||
PreAuthenticate = false, | |||
ReadWriteTimeout = _config.APITimeout, | |||
UserAgent = _config.UserAgent | |||
}; | |||
_client.RemoveDefaultParameter("Accept"); | |||
if (_config.ProxyUrl != null) | |||
_client.Proxy = new WebProxy(_config.ProxyUrl, true, new string[0], _config.ProxyCredentials); | |||
else | |||
_client.Proxy = null; | |||
_client.RemoveDefaultParameter("Accept"); | |||
_client.AddDefaultHeader("accept", "*/*"); | |||
_client.AddDefaultHeader("accept-encoding", "gzip,deflate"); | |||
_client.UserAgent = userAgent; | |||
_client.ReadWriteTimeout = timeout; | |||
} | |||
} | |||
public void SetToken(string token) | |||
{ |
@@ -1,9 +1,10 @@ | |||
using Newtonsoft.Json; | |||
using Discord.API; | |||
using Newtonsoft.Json; | |||
using Newtonsoft.Json.Linq; | |||
using System; | |||
using System.Threading.Tasks; | |||
namespace Discord.WebSockets.Data | |||
namespace Discord.Net.WebSockets | |||
{ | |||
internal partial class DataWebSocket : WebSocket | |||
{ | |||
@@ -12,7 +13,7 @@ namespace Discord.WebSockets.Data | |||
public string SessionId => _sessionId; | |||
private string _sessionId; | |||
public DataWebSocket(DiscordSimpleClient client) | |||
public DataWebSocket(DiscordWSClient client) | |||
: base(client) | |||
{ | |||
} | |||
@@ -25,6 +26,9 @@ namespace Discord.WebSockets.Data | |||
LoginCommand msg = new LoginCommand(); | |||
msg.Payload.Token = token; | |||
msg.Payload.Properties["$device"] = "Discord.Net"; | |||
if (_client.Config.UseLargeThreshold) | |||
msg.Payload.LargeThreshold = 50; | |||
msg.Payload.Compress = true; | |||
QueueMessage(msg); | |||
} | |||
private async Task Redirect(string server) | |||
@@ -66,6 +70,7 @@ namespace Discord.WebSockets.Data | |||
protected override async Task ProcessMessage(string json) | |||
{ | |||
await base.ProcessMessage(json).ConfigureAwait(false); | |||
var msg = JsonConvert.DeserializeObject<WebSocketMessage>(json); | |||
if (msg.Sequence.HasValue) | |||
_lastSeq = msg.Sequence.Value; | |||
@@ -99,7 +104,7 @@ namespace Discord.WebSockets.Data | |||
Host = payload.Url; | |||
if (_logLevel >= LogMessageSeverity.Info) | |||
RaiseOnLog(LogMessageSeverity.Info, "Redirected to " + payload.Url); | |||
await Redirect(payload.Url); | |||
await Redirect(payload.Url).ConfigureAwait(false); | |||
} | |||
} | |||
break; |