Project restructure, Added .Net Core support, Fixed some bugs
This commit is contained in:
67
src/Discord.Net.Commands.Net45/Discord.Net.Commands.csproj
Normal file
67
src/Discord.Net.Commands.Net45/Discord.Net.Commands.csproj
Normal file
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{1B5603B4-6F8F-4289-B945-7BAAE523D740}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Discord.Net</RootNamespace>
|
||||
<AssemblyName>Discord.Net.Commands</AssemblyName>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
<ItemGroup />
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Discord.Net.Net45\Discord.Net.csproj">
|
||||
<Project>{8d71a857-879a-4a10-859e-5ff824ed6688}</Project>
|
||||
<Name>Discord.Net</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Discord.Net.Commands\CommandBuilder.cs">
|
||||
<Link>CommandBuilder.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Discord.Net.Commands\CommandParser.cs">
|
||||
<Link>CommandParser.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Discord.Net.Commands\DiscordBotClient.cs">
|
||||
<Link>DiscordBotClient.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Discord.Net.Commands\DiscordBotClient.Events.cs">
|
||||
<Link>DiscordBotClient.Events.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</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.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
17
src/Discord.Net.Commands.Net45/Properties/AssemblyInfo.cs
Normal file
17
src/Discord.Net.Commands.Net45/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
[assembly: AssemblyTitle("Discord.Net")]
|
||||
[assembly: AssemblyDescription("An unofficial .Net API wrapper for the Discord client.")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("RogueException")]
|
||||
[assembly: AssemblyProduct("Discord.Net")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2015")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
[assembly: ComVisible(false)]
|
||||
[assembly: Guid("76ea00e6-ea24-41e1-acb2-639c0313fa80")]
|
||||
|
||||
[assembly: AssemblyVersion("0.2.0.0")]
|
||||
[assembly: AssemblyFileVersion("0.2.0.0")]
|
||||
32
src/Discord.Net.Commands/CommandBuilder.cs
Normal file
32
src/Discord.Net.Commands/CommandBuilder.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public sealed class CommandBuilder
|
||||
{
|
||||
private readonly DiscordBotClient _client;
|
||||
private readonly string _prefix;
|
||||
private readonly bool _useWhitelist;
|
||||
|
||||
public CommandBuilder(DiscordBotClient client, string prefix, bool useWhitelist = false)
|
||||
{
|
||||
_client = client;
|
||||
_prefix = prefix;
|
||||
_useWhitelist = useWhitelist;
|
||||
}
|
||||
|
||||
public void AddCommandGroup(string cmd, Action<CommandBuilder> config, bool useWhitelist = false)
|
||||
{
|
||||
config(new CommandBuilder(_client, _prefix + ' ' + cmd, useWhitelist));
|
||||
}
|
||||
public void AddCommand(string cmd, int minArgs, int maxArgs, Action<DiscordBotClient.CommandEventArgs> handler, bool? useWhitelist = null)
|
||||
{
|
||||
AddCommand(cmd, minArgs, maxArgs, e => { handler(e); return null; }, useWhitelist);
|
||||
}
|
||||
public void AddCommand(string cmd, int minArgs, int maxArgs, Func<DiscordBotClient.CommandEventArgs, Task> handler, bool? useWhitelist = null)
|
||||
{
|
||||
_client.AddCommand(cmd != "" ? _prefix + ' ' + cmd : _prefix, minArgs, maxArgs, handler, useWhitelist ?? _useWhitelist);
|
||||
}
|
||||
}
|
||||
}
|
||||
154
src/Discord.Net.Commands/CommandParser.cs
Normal file
154
src/Discord.Net.Commands/CommandParser.cs
Normal file
@@ -0,0 +1,154 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public static class CommandParser
|
||||
{
|
||||
private enum CommandParserPart
|
||||
{
|
||||
None,
|
||||
CommandName,
|
||||
Parameter,
|
||||
QuotedParameter,
|
||||
DoubleQuotedParameter
|
||||
}
|
||||
|
||||
public static bool Parse(string input, out string command, out string[] args)
|
||||
{
|
||||
return Parse(input, out command, out args, true);
|
||||
}
|
||||
public static bool ParseArgs(string input, out string[] args)
|
||||
{
|
||||
string ignored;
|
||||
return Parse(input, out ignored, out args, false);
|
||||
}
|
||||
|
||||
private static bool Parse(string input, out string command, out string[] args, bool parseCommand)
|
||||
{
|
||||
CommandParserPart currentPart = parseCommand ? CommandParserPart.CommandName : CommandParserPart.None;
|
||||
int startPosition = 0;
|
||||
int endPosition = 0;
|
||||
int inputLength = input.Length;
|
||||
bool isEscaped = false;
|
||||
List<string> argList = new List<string>();
|
||||
|
||||
command = null;
|
||||
args = null;
|
||||
|
||||
if (input == "")
|
||||
return false;
|
||||
|
||||
while (endPosition < inputLength)
|
||||
{
|
||||
char currentChar = input[endPosition++];
|
||||
if (isEscaped)
|
||||
isEscaped = false;
|
||||
else if (currentChar == '\\')
|
||||
isEscaped = true;
|
||||
|
||||
switch (currentPart)
|
||||
{
|
||||
case CommandParserPart.CommandName:
|
||||
if ((!isEscaped && currentChar == ' ') || endPosition >= inputLength)
|
||||
{
|
||||
int length = (currentChar == ' ' ? endPosition - 1 : endPosition) - startPosition;
|
||||
string temp = input.Substring(startPosition, length);
|
||||
if (temp == "")
|
||||
startPosition = endPosition;
|
||||
else
|
||||
{
|
||||
currentPart = CommandParserPart.None;
|
||||
command = temp;
|
||||
startPosition = endPosition;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CommandParserPart.None:
|
||||
if ((!isEscaped && currentChar == '\"'))
|
||||
{
|
||||
currentPart = CommandParserPart.DoubleQuotedParameter;
|
||||
startPosition = endPosition;
|
||||
}
|
||||
else if ((!isEscaped && currentChar == '\''))
|
||||
{
|
||||
currentPart = CommandParserPart.QuotedParameter;
|
||||
startPosition = endPosition;
|
||||
}
|
||||
else if ((!isEscaped && currentChar == ' ') || endPosition >= inputLength)
|
||||
{
|
||||
int length = (currentChar == ' ' ? endPosition - 1 : endPosition) - startPosition;
|
||||
string temp = input.Substring(startPosition, length);
|
||||
if (temp == "")
|
||||
startPosition = endPosition;
|
||||
else
|
||||
{
|
||||
currentPart = CommandParserPart.None;
|
||||
argList.Add(temp);
|
||||
startPosition = endPosition;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CommandParserPart.QuotedParameter:
|
||||
if ((!isEscaped && currentChar == '\''))
|
||||
{
|
||||
string temp = input.Substring(startPosition, endPosition - startPosition - 1);
|
||||
currentPart = CommandParserPart.None;
|
||||
argList.Add(temp);
|
||||
startPosition = endPosition;
|
||||
}
|
||||
else if (endPosition >= inputLength)
|
||||
return false;
|
||||
break;
|
||||
case CommandParserPart.DoubleQuotedParameter:
|
||||
if ((!isEscaped && currentChar == '\"'))
|
||||
{
|
||||
string temp = input.Substring(startPosition, endPosition - startPosition - 1);
|
||||
currentPart = CommandParserPart.None;
|
||||
argList.Add(temp);
|
||||
startPosition = endPosition;
|
||||
}
|
||||
else if (endPosition >= inputLength)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseCommand && (command == null || command == ""))
|
||||
return false;
|
||||
|
||||
args = argList.ToArray();
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ArgsEqual(string[] args, int expected)
|
||||
{
|
||||
return args.Length == expected;
|
||||
}
|
||||
public static bool ArgsAtLeast(string[] args, int expected)
|
||||
{
|
||||
return args.Length >= expected;
|
||||
}
|
||||
public static bool ArgsAtMost(string[] args, int expected)
|
||||
{
|
||||
return args.Length <= expected;
|
||||
}
|
||||
public static bool ArgsIn(string[] args, params int[] expected)
|
||||
{
|
||||
int count = args.Length;
|
||||
for (int i = 0; i < expected.Length; i++)
|
||||
{
|
||||
if (count == expected[i])
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public static bool ArgsBetween(string[] args, int min, int max)
|
||||
{
|
||||
return args.Length >= min && args.Length <= max;
|
||||
}
|
||||
public static bool NoArgs(string[] args, params int[] expected)
|
||||
{
|
||||
return args.Length == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/Discord.Net.Commands/Discord.Net.Commands.xproj
Normal file
21
src/Discord.Net.Commands/Discord.Net.Commands.xproj
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>19793545-ef89-48f4-8100-3ebaad0a9141</ProjectGuid>
|
||||
<RootNamespace>Discord.Net.Commands</RootNamespace>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<ProduceOutputsOnBuild>True</ProduceOutputsOnBuild>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
50
src/Discord.Net.Commands/DiscordBotClient.Events.cs
Normal file
50
src/Discord.Net.Commands/DiscordBotClient.Events.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public partial class DiscordBotClient : DiscordClient
|
||||
{
|
||||
public class CommandEventArgs
|
||||
{
|
||||
public readonly Message Message;
|
||||
public readonly Command Command;
|
||||
public readonly string[] Args;
|
||||
|
||||
public User User => Message.User;
|
||||
public string UserId => Message.UserId;
|
||||
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[] args)
|
||||
{
|
||||
Message = message;
|
||||
Command = command;
|
||||
Args = args;
|
||||
}
|
||||
}
|
||||
public class CommandErrorEventArgs : CommandEventArgs
|
||||
{
|
||||
public readonly Exception Exception;
|
||||
public CommandErrorEventArgs(Message message, Command command, string[] args, Exception ex)
|
||||
: base(message, command, args)
|
||||
{
|
||||
Exception = ex;
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler<CommandEventArgs> RanCommand;
|
||||
private void RaiseRanCommand(CommandEventArgs args)
|
||||
{
|
||||
if (RanCommand != null)
|
||||
RanCommand(this, args);
|
||||
}
|
||||
public event EventHandler<CommandErrorEventArgs> CommandError;
|
||||
private void RaiseCommandError(Message msg, Command command, string[] args, Exception ex)
|
||||
{
|
||||
if (CommandError != null)
|
||||
CommandError(this, new CommandErrorEventArgs(msg, command, args, ex));
|
||||
}
|
||||
}
|
||||
}
|
||||
151
src/Discord.Net.Commands/DiscordBotClient.cs
Normal file
151
src/Discord.Net.Commands/DiscordBotClient.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public sealed class Command
|
||||
{
|
||||
public readonly string[] Text;
|
||||
public readonly int MinArgs, MaxArgs;
|
||||
public readonly bool UseWhitelist;
|
||||
internal readonly Func<DiscordBotClient.CommandEventArgs, Task> Handler;
|
||||
|
||||
public Command(string[] text, int minArgs, int maxArgs, bool useWhitelist, Func<DiscordBotClient.CommandEventArgs, Task> handler)
|
||||
{
|
||||
Text = text;
|
||||
MinArgs = minArgs;
|
||||
MaxArgs = maxArgs;
|
||||
UseWhitelist = useWhitelist;
|
||||
Handler = handler;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A Discord.Net client with extensions for handling common bot operations like text commands.
|
||||
/// </summary>
|
||||
public partial class DiscordBotClient : DiscordClient
|
||||
{
|
||||
private List<Command> _commands;
|
||||
private List<string> _whitelist;
|
||||
|
||||
public IEnumerable<Command> Commands => _commands;
|
||||
|
||||
public char CommandChar { get; set; }
|
||||
public bool UseCommandChar { get; set; }
|
||||
public bool RequireCommandCharInPublic { get; set; }
|
||||
public bool RequireCommandCharInPrivate { get; set; }
|
||||
public bool AlwaysUseWhitelist { get; set; }
|
||||
|
||||
public DiscordBotClient()
|
||||
{
|
||||
_commands = new List<Command>();
|
||||
_whitelist = new List<string>();
|
||||
|
||||
CommandChar = '~';
|
||||
RequireCommandCharInPublic = true;
|
||||
RequireCommandCharInPrivate = true;
|
||||
AlwaysUseWhitelist = false;
|
||||
|
||||
MessageCreated += async (s, e) =>
|
||||
{
|
||||
//Ignore messages from ourselves
|
||||
if (e.Message.UserId == UserId)
|
||||
return;
|
||||
|
||||
//Check the global whitelist
|
||||
if (AlwaysUseWhitelist && !_whitelist.Contains(e.Message.UserId))
|
||||
return;
|
||||
|
||||
//Check for the command character
|
||||
string msg = e.Message.Text;
|
||||
if (UseCommandChar)
|
||||
{
|
||||
if (msg.Length == 0)
|
||||
return;
|
||||
bool isPrivate = e.Message.Channel.IsPrivate;
|
||||
bool hasCommandChar = msg[0] == CommandChar;
|
||||
if (hasCommandChar)
|
||||
msg = msg.Substring(1);
|
||||
if (!isPrivate && RequireCommandCharInPublic && !hasCommandChar)
|
||||
return;
|
||||
if (isPrivate && RequireCommandCharInPrivate && !hasCommandChar)
|
||||
return;
|
||||
}
|
||||
|
||||
string[] args;
|
||||
if (!CommandParser.ParseArgs(msg, out args))
|
||||
return;
|
||||
|
||||
for (int i = 0; i < _commands.Count; i++)
|
||||
{
|
||||
Command cmd = _commands[i];
|
||||
|
||||
//Check Command Parts
|
||||
if (args.Length < cmd.Text.Length)
|
||||
continue;
|
||||
|
||||
bool isValid = true;
|
||||
for (int j = 0; j < cmd.Text.Length; j++)
|
||||
{
|
||||
if (!string.Equals(args[j], cmd.Text[j], StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
isValid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isValid)
|
||||
continue;
|
||||
|
||||
//Check Whitelist
|
||||
if (cmd.UseWhitelist && !_whitelist.Contains(e.Message.UserId))
|
||||
continue;
|
||||
|
||||
//Check Arg Count
|
||||
int argCount = args.Length - cmd.Text.Length;
|
||||
if (argCount < cmd.MinArgs || argCount > cmd.MaxArgs)
|
||||
continue;
|
||||
|
||||
//Run Command
|
||||
string[] newArgs = new string[argCount];
|
||||
for (int j = 0; j < newArgs.Length; j++)
|
||||
newArgs[j] = args[j + cmd.Text.Length];
|
||||
|
||||
var eventArgs = new CommandEventArgs(e.Message, cmd, newArgs);
|
||||
RaiseRanCommand(eventArgs);
|
||||
try
|
||||
{
|
||||
var task = cmd.Handler(eventArgs);
|
||||
if (task != null)
|
||||
await task;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
RaiseCommandError(e.Message, cmd, newArgs, ex);
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void AddCommandGroup(string cmd, Action<CommandBuilder> config, bool useWhitelist = false)
|
||||
{
|
||||
config(new CommandBuilder(this, cmd, useWhitelist));
|
||||
}
|
||||
public void AddCommand(string cmd, int minArgs, int maxArgs, Action<CommandEventArgs> handler, bool useWhitelist = false)
|
||||
{
|
||||
AddCommand(cmd, minArgs, maxArgs, e => { handler(e); return null; }, useWhitelist);
|
||||
}
|
||||
public void AddCommand(string cmd, int minArgs, int maxArgs, Func<CommandEventArgs, Task> handler, bool useWhitelist = false)
|
||||
{
|
||||
_commands.Add(new Command(cmd.Split(' '), minArgs, maxArgs, useWhitelist, handler));
|
||||
}
|
||||
|
||||
public void AddWhitelist(User user)
|
||||
=> AddWhitelist(user.Id);
|
||||
public void AddWhitelist(string userId)
|
||||
{
|
||||
_whitelist.Add(userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/Discord.Net.Commands/project.json
Normal file
26
src/Discord.Net.Commands/project.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"version": "0.2.0-*",
|
||||
"description": "A small Discord.Net extension to make bot creation easier.",
|
||||
"authors": [ "RogueException" ],
|
||||
"tags": [ "discord", "discordapp" ],
|
||||
"projectUrl": "https://github.com/RogueException/Discord.Net",
|
||||
"licenseUrl": "http://opensource.org/licenses/MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/RogueException/Discord.Net"
|
||||
},
|
||||
"compilationOptions": {
|
||||
"warningsAsErrors": true
|
||||
},
|
||||
"dependencies": {
|
||||
"Discord.Net": ""
|
||||
},
|
||||
"frameworks": {
|
||||
"dotnet": {
|
||||
"dependencies": {
|
||||
"System.Runtime": "4.0.20",
|
||||
"Microsoft.CSharp": "4.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
121
src/Discord.Net.Net45/Discord.Net.csproj
Normal file
121
src/Discord.Net.Net45/Discord.Net.csproj
Normal file
@@ -0,0 +1,121 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{8D71A857-879A-4A10-859E-5FF824ED6688}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Discord.Net</RootNamespace>
|
||||
<AssemblyName>Discord.Net</AssemblyName>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Discord.Net\API\DiscordAPI.cs">
|
||||
<Link>API\DiscordAPI.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Discord.Net\API\Endpoints.cs">
|
||||
<Link>API\Endpoints.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Discord.Net\API\Models\APIRequests.cs">
|
||||
<Link>API\Models\APIRequests.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Discord.Net\API\Models\APIResponses.cs">
|
||||
<Link>API\Models\APIResponses.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Discord.Net\API\Models\Common.cs">
|
||||
<Link>API\Models\Common.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Discord.Net\API\Models\WebSocketCommands.cs">
|
||||
<Link>API\Models\WebSocketCommands.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Discord.Net\API\Models\WebSocketEvents.cs">
|
||||
<Link>API\Models\WebSocketEvents.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Discord.Net\Channel.cs">
|
||||
<Link>Channel.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Discord.Net\ChannelTypes.cs">
|
||||
<Link>ChannelTypes.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Discord.Net\DiscordClient.cs">
|
||||
<Link>DiscordClient.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Discord.Net\DiscordClient.Events.cs">
|
||||
<Link>DiscordClient.Events.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Discord.Net\DiscordWebSocket.cs">
|
||||
<Link>DiscordWebSocket.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Discord.Net\DiscordWebSocket.Events.cs">
|
||||
<Link>DiscordWebSocket.Events.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Discord.Net\Helpers\AsyncCache.cs">
|
||||
<Link>Helpers\AsyncCache.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Discord.Net\Helpers\Http.cs">
|
||||
<Link>Helpers\Http.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Discord.Net\Invite.cs">
|
||||
<Link>Invite.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Discord.Net\Membership.cs">
|
||||
<Link>Membership.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Discord.Net\Message.cs">
|
||||
<Link>Message.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Discord.Net\Regions.cs">
|
||||
<Link>Regions.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Discord.Net\Role.cs">
|
||||
<Link>Role.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Discord.Net\Server.cs">
|
||||
<Link>Server.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Discord.Net\User.cs">
|
||||
<Link>User.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</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.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
17
src/Discord.Net.Net45/Properties/AssemblyInfo.cs
Normal file
17
src/Discord.Net.Net45/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
[assembly: AssemblyTitle("Discord.Net")]
|
||||
[assembly: AssemblyDescription("An unofficial .Net API wrapper for the Discord client.")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("RogueException")]
|
||||
[assembly: AssemblyProduct("Discord.Net")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2015")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
[assembly: ComVisible(false)]
|
||||
[assembly: Guid("76ea00e6-ea24-41e1-acb2-639c0313fa80")]
|
||||
|
||||
[assembly: AssemblyVersion("0.3.3.0")]
|
||||
[assembly: AssemblyFileVersion("0.3.3.0")]
|
||||
4
src/Discord.Net.Net45/packages.config
Normal file
4
src/Discord.Net.Net45/packages.config
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Newtonsoft.Json" version="7.0.1" targetFramework="net45" />
|
||||
</packages>
|
||||
117
src/Discord.Net/API/DiscordAPI.cs
Normal file
117
src/Discord.Net/API/DiscordAPI.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
using Discord.API.Models;
|
||||
using Discord.Helpers;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord.API
|
||||
{
|
||||
internal static class DiscordAPI
|
||||
{
|
||||
public const int MaxMessageSize = 2000;
|
||||
|
||||
//Auth
|
||||
public static async Task<APIResponses.AuthRegister> LoginAnonymous(string username, HttpOptions options)
|
||||
{
|
||||
var fingerprintResponse = await Http.Post<APIResponses.AuthFingerprint>(Endpoints.AuthFingerprint, options);
|
||||
var registerRequest = new APIRequests.AuthRegisterRequest { Fingerprint = fingerprintResponse.Fingerprint, Username = username };
|
||||
var registerResponse = await Http.Post<APIResponses.AuthRegister>(Endpoints.AuthRegister, registerRequest, options);
|
||||
return registerResponse;
|
||||
}
|
||||
public static async Task<APIResponses.AuthLogin> Login(string email, string password, HttpOptions options)
|
||||
{
|
||||
var request = new APIRequests.AuthLogin { Email = email, Password = password };
|
||||
var response = await Http.Post<APIResponses.AuthLogin>(Endpoints.AuthLogin, request, options);
|
||||
options.Token = response.Token;
|
||||
return response;
|
||||
}
|
||||
public static Task Logout(HttpOptions options)
|
||||
=> Http.Post(Endpoints.AuthLogout, options);
|
||||
|
||||
//Servers
|
||||
public static Task<APIResponses.CreateServer> CreateServer(string name, string region, HttpOptions options)
|
||||
{
|
||||
var request = new APIRequests.CreateServer { Name = name, Region = region };
|
||||
return Http.Post<APIResponses.CreateServer>(Endpoints.Servers, request, options);
|
||||
}
|
||||
public static Task LeaveServer(string id, HttpOptions options)
|
||||
=> Http.Delete<APIResponses.DeleteServer>(Endpoints.Server(id), options);
|
||||
|
||||
//Channels
|
||||
public static Task<APIResponses.CreateChannel> CreateChannel(string serverId, string name, string channelType, HttpOptions options)
|
||||
{
|
||||
var request = new APIRequests.CreateChannel { Name = name, Type = channelType };
|
||||
return Http.Post<APIResponses.CreateChannel>(Endpoints.ServerChannels(serverId), request, options);
|
||||
}
|
||||
public static Task<APIResponses.CreateChannel> CreatePMChannel(string myId, string recipientId, HttpOptions options)
|
||||
{
|
||||
var request = new APIRequests.CreatePMChannel { RecipientId = recipientId };
|
||||
return Http.Post<APIResponses.CreateChannel>(Endpoints.UserChannels(myId), request, options);
|
||||
}
|
||||
public static Task<APIResponses.DestroyChannel> DestroyChannel(string channelId, HttpOptions options)
|
||||
=> Http.Delete<APIResponses.DestroyChannel>(Endpoints.Channel(channelId), options);
|
||||
public static Task<APIResponses.GetMessages[]> GetMessages(string channelId, int count, HttpOptions options)
|
||||
=> Http.Get<APIResponses.GetMessages[]>(Endpoints.ChannelMessages(channelId, count), options);
|
||||
|
||||
//Members
|
||||
public static Task Kick(string serverId, string memberId, HttpOptions options)
|
||||
=> Http.Delete(Endpoints.ServerMember(serverId, memberId), options);
|
||||
public static Task Ban(string serverId, string memberId, HttpOptions options)
|
||||
=> Http.Put(Endpoints.ServerBan(serverId, memberId), options);
|
||||
public static Task Unban(string serverId, string memberId, HttpOptions options)
|
||||
=> Http.Delete(Endpoints.ServerBan(serverId, memberId), options);
|
||||
|
||||
//Invites
|
||||
public static Task<APIResponses.CreateInvite> CreateInvite(string channelId, int maxAge, int maxUses, bool isTemporary, bool hasXkcdPass, HttpOptions options)
|
||||
{
|
||||
var request = new APIRequests.CreateInvite { MaxAge = maxAge, MaxUses = maxUses, IsTemporary = isTemporary, HasXkcdPass = hasXkcdPass };
|
||||
return Http.Post<APIResponses.CreateInvite>(Endpoints.ChannelInvites(channelId), request, options);
|
||||
}
|
||||
public static Task<APIResponses.GetInvite> GetInvite(string id, HttpOptions options)
|
||||
=> Http.Get<APIResponses.GetInvite>(Endpoints.Invite(id), options);
|
||||
public static Task AcceptInvite(string id, HttpOptions options)
|
||||
=> Http.Post<APIResponses.AcceptInvite>(Endpoints.Invite(id), options);
|
||||
public static Task DeleteInvite(string id, HttpOptions options)
|
||||
=> Http.Delete(Endpoints.Invite(id), options);
|
||||
|
||||
//Chat
|
||||
public static Task<APIResponses.SendMessage> SendMessage(string channelId, string message, string[] mentions, HttpOptions options)
|
||||
{
|
||||
var request = new APIRequests.SendMessage { Content = message, Mentions = mentions };
|
||||
return Http.Post<APIResponses.SendMessage>(Endpoints.ChannelMessages(channelId), request, options);
|
||||
}
|
||||
public static Task<APIResponses.EditMessage> EditMessage(string channelId, string messageId, string message, string[] mentions, HttpOptions options)
|
||||
{
|
||||
var request = new APIRequests.EditMessage { Content = message, Mentions = mentions };
|
||||
return Http.Patch<APIResponses.EditMessage>(Endpoints.ChannelMessage(channelId, messageId), request, options);
|
||||
}
|
||||
public static Task SendIsTyping(string channelId, HttpOptions options)
|
||||
=> Http.Post(Endpoints.ChannelTyping(channelId), options);
|
||||
public static Task DeleteMessage(string channelId, string msgId, HttpOptions options)
|
||||
=> Http.Delete(Endpoints.ChannelMessage(channelId, msgId), options);
|
||||
|
||||
//Voice
|
||||
public static Task<APIResponses.GetRegions[]> GetVoiceRegions(HttpOptions options)
|
||||
=> Http.Get<APIResponses.GetRegions[]>(Endpoints.VoiceRegions, options);
|
||||
public static Task<APIResponses.GetIce> GetVoiceIce(HttpOptions options)
|
||||
=> Http.Get<APIResponses.GetIce>(Endpoints.VoiceIce, options);
|
||||
public static Task Mute(string serverId, string memberId, HttpOptions options)
|
||||
{
|
||||
var request = new APIRequests.SetMemberMute { Mute = true };
|
||||
return Http.Patch(Endpoints.ServerMember(serverId, memberId), options);
|
||||
}
|
||||
public static Task Unmute(string serverId, string memberId, HttpOptions options)
|
||||
{
|
||||
var request = new APIRequests.SetMemberMute { Mute = false };
|
||||
return Http.Patch(Endpoints.ServerMember(serverId, memberId), options);
|
||||
}
|
||||
public static Task Deafen(string serverId, string memberId, HttpOptions options)
|
||||
{
|
||||
var request = new APIRequests.SetMemberDeaf { Deaf = true };
|
||||
return Http.Patch(Endpoints.ServerMember(serverId, memberId), options);
|
||||
}
|
||||
public static Task Undeafen(string serverId, string memberId, HttpOptions options)
|
||||
{
|
||||
var request = new APIRequests.SetMemberDeaf { Deaf = false };
|
||||
return Http.Patch(Endpoints.ServerMember(serverId, memberId), options);
|
||||
}
|
||||
}
|
||||
}
|
||||
57
src/Discord.Net/API/Endpoints.cs
Normal file
57
src/Discord.Net/API/Endpoints.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
namespace Discord.API
|
||||
{
|
||||
internal static class Endpoints
|
||||
{
|
||||
public static readonly string BaseUrl = "discordapp.com";
|
||||
public static readonly string BaseShortUrl = "discord.gg";
|
||||
public static readonly string BaseHttps = $"https://{BaseUrl}";
|
||||
|
||||
// /api
|
||||
public static readonly string BaseApi = $"{BaseHttps}/api";
|
||||
public static readonly string Track = $"{BaseApi}/track";
|
||||
|
||||
// /api/auth
|
||||
public static readonly string Auth = $"{BaseApi}/auth";
|
||||
public static readonly string AuthFingerprint = $"{Auth}fingerprint";
|
||||
public static readonly string AuthRegister = $"{Auth}/register";
|
||||
public static readonly string AuthLogin = $"{Auth}/login";
|
||||
public static readonly string AuthLogout = $"{Auth}/logout";
|
||||
|
||||
// /api/channels
|
||||
public static readonly string Channels = $"{BaseApi}/channels";
|
||||
public static string Channel(string channelId) => $"{Channels}/{channelId}";
|
||||
public static string ChannelTyping(string channelId) => $"{Channels}/{channelId}/typing";
|
||||
public static string ChannelMessages(string channelId) => $"{Channels}/{channelId}/messages";
|
||||
public static string ChannelMessages(string channelId, int limit) => $"{Channels}/{channelId}/messages?limit={limit}";
|
||||
public static string ChannelMessage(string channelId, string msgId) => $"{Channels}/{channelId}/messages/{msgId}";
|
||||
public static string ChannelInvites(string channelId) => $"{Channels}/{channelId}/invites";
|
||||
|
||||
// /api/guilds
|
||||
public static readonly string Servers = $"{BaseApi}/guilds";
|
||||
public static string Server(string serverId) => $"{Servers}/{serverId}";
|
||||
public static string ServerChannels(string serverId) => $"{Servers}/{serverId}/channels";
|
||||
public static string ServerMember(string serverId, string userId) => $"{Servers}/{serverId}/members/{userId}";
|
||||
public static string ServerBan(string serverId, string userId) => $"{Servers}/{serverId}/bans/{userId}";
|
||||
|
||||
// /api/invites
|
||||
public static readonly string Invites = $"{BaseApi}/invite";
|
||||
public static string Invite(string id) => $"{Invites}/{id}";
|
||||
|
||||
// /api/users
|
||||
public static readonly string Users = $"{BaseApi}/users";
|
||||
public static string UserChannels(string userId) => $"{Users}/{userId}/channels";
|
||||
public static string UserAvatar(string userId, string avatarId) => $"{Users}/{userId}/avatars/{avatarId}.jpg";
|
||||
|
||||
// /api/voice
|
||||
public static readonly string Voice = $"{BaseApi}/voice";
|
||||
public static readonly string VoiceRegions = $"{Voice}/regions";
|
||||
public static readonly string VoiceIce = $"{Voice}/ice";
|
||||
|
||||
//Web Sockets
|
||||
public static readonly string BaseWss = "wss://" + BaseUrl;
|
||||
public static readonly string WebSocket_Hub = $"{BaseWss}/hub";
|
||||
|
||||
//Website
|
||||
public static string InviteUrl(string code) => $"{BaseShortUrl}/{code}";
|
||||
}
|
||||
}
|
||||
79
src/Discord.Net/API/Models/APIRequests.cs
Normal file
79
src/Discord.Net/API/Models/APIRequests.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
//Ignore unused/unassigned variable warnings
|
||||
#pragma warning disable CS0649
|
||||
#pragma warning disable CS0169
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Models
|
||||
{
|
||||
internal static class APIRequests
|
||||
{
|
||||
public class AuthRegisterRequest
|
||||
{
|
||||
[JsonProperty(PropertyName = "fingerprint")]
|
||||
public string Fingerprint;
|
||||
[JsonProperty(PropertyName = "username")]
|
||||
public string Username;
|
||||
}
|
||||
public class AuthLogin
|
||||
{
|
||||
[JsonProperty(PropertyName = "email")]
|
||||
public string Email;
|
||||
[JsonProperty(PropertyName = "password")]
|
||||
public string Password;
|
||||
}
|
||||
|
||||
public class CreateServer
|
||||
{
|
||||
[JsonProperty(PropertyName = "name")]
|
||||
public string Name;
|
||||
[JsonProperty(PropertyName = "region")]
|
||||
public string Region;
|
||||
}
|
||||
|
||||
public class CreateChannel
|
||||
{
|
||||
[JsonProperty(PropertyName = "name")]
|
||||
public string Name;
|
||||
[JsonProperty(PropertyName = "type")]
|
||||
public string Type;
|
||||
}
|
||||
public class CreatePMChannel
|
||||
{
|
||||
[JsonProperty(PropertyName = "recipient_id")]
|
||||
public string RecipientId;
|
||||
}
|
||||
|
||||
public class CreateInvite
|
||||
{
|
||||
[JsonProperty(PropertyName = "max_age")]
|
||||
public int MaxAge;
|
||||
[JsonProperty(PropertyName = "max_uses")]
|
||||
public int MaxUses;
|
||||
[JsonProperty(PropertyName = "temporary")]
|
||||
public bool IsTemporary;
|
||||
[JsonProperty(PropertyName = "xkcdpass")]
|
||||
public bool HasXkcdPass;
|
||||
}
|
||||
|
||||
public class SendMessage
|
||||
{
|
||||
[JsonProperty(PropertyName = "content")]
|
||||
public string Content;
|
||||
[JsonProperty(PropertyName = "mentions")]
|
||||
public string[] Mentions;
|
||||
}
|
||||
public class EditMessage : SendMessage { }
|
||||
|
||||
public class SetMemberMute
|
||||
{
|
||||
[JsonProperty(PropertyName = "mute")]
|
||||
public bool Mute;
|
||||
}
|
||||
public class SetMemberDeaf
|
||||
{
|
||||
[JsonProperty(PropertyName = "deaf")]
|
||||
public bool Deaf;
|
||||
}
|
||||
}
|
||||
}
|
||||
95
src/Discord.Net/API/Models/APIResponses.cs
Normal file
95
src/Discord.Net/API/Models/APIResponses.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
//Ignore unused/unassigned variable warnings
|
||||
#pragma warning disable CS0649
|
||||
#pragma warning disable CS0169
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
|
||||
namespace Discord.API.Models
|
||||
{
|
||||
internal static class APIResponses
|
||||
{
|
||||
public class AuthFingerprint
|
||||
{
|
||||
[JsonProperty(PropertyName = "fingerprint")]
|
||||
public string Fingerprint;
|
||||
}
|
||||
public class AuthRegister : AuthLogin { }
|
||||
public class AuthLogin
|
||||
{
|
||||
[JsonProperty(PropertyName = "token")]
|
||||
public string Token;
|
||||
}
|
||||
|
||||
public class CreateServer : ServerInfo { }
|
||||
public class DeleteServer : ServerInfo { }
|
||||
|
||||
public class CreateChannel : ChannelInfo { }
|
||||
public class DestroyChannel : ChannelInfo { }
|
||||
|
||||
public class CreateInvite : GetInvite
|
||||
{
|
||||
[JsonProperty(PropertyName = "max_age")]
|
||||
public int MaxAge;
|
||||
[JsonProperty(PropertyName = "max_uses")]
|
||||
public int MaxUses;
|
||||
[JsonProperty(PropertyName = "revoked")]
|
||||
public bool IsRevoked;
|
||||
[JsonProperty(PropertyName = "temporary")]
|
||||
public bool IsTemporary;
|
||||
[JsonProperty(PropertyName = "uses")]
|
||||
public int Uses;
|
||||
[JsonProperty(PropertyName = "created_at")]
|
||||
public DateTime CreatedAt;
|
||||
}
|
||||
|
||||
public class GetInvite
|
||||
{
|
||||
[JsonProperty(PropertyName = "inviter")]
|
||||
public UserReference Inviter;
|
||||
[JsonProperty(PropertyName = "guild")]
|
||||
public ServerReference Server;
|
||||
[JsonProperty(PropertyName = "channel")]
|
||||
public ChannelReference Channel;
|
||||
[JsonProperty(PropertyName = "code")]
|
||||
public string Code;
|
||||
[JsonProperty(PropertyName = "xkcdpass")]
|
||||
public string XkcdPass;
|
||||
}
|
||||
public class AcceptInvite : GetInvite { }
|
||||
|
||||
public class SendMessage : Message { }
|
||||
public class EditMessage : Message { }
|
||||
public class GetMessages : Message { }
|
||||
|
||||
public class GetRegions
|
||||
{
|
||||
[JsonProperty(PropertyName = "sample_hostname")]
|
||||
public string Hostname;
|
||||
[JsonProperty(PropertyName = "sample_port")]
|
||||
public int Port;
|
||||
[JsonProperty(PropertyName = "id")]
|
||||
public string Id;
|
||||
[JsonProperty(PropertyName = "name")]
|
||||
public string Name;
|
||||
}
|
||||
|
||||
public class GetIce
|
||||
{
|
||||
[JsonProperty(PropertyName = "ttl")]
|
||||
public string TTL;
|
||||
[JsonProperty(PropertyName = "servers")]
|
||||
public Server[] Servers;
|
||||
|
||||
public class Server
|
||||
{
|
||||
[JsonProperty(PropertyName = "url")]
|
||||
public string URL;
|
||||
[JsonProperty(PropertyName = "username")]
|
||||
public string Username;
|
||||
[JsonProperty(PropertyName = "credential")]
|
||||
public string Credential;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
181
src/Discord.Net/API/Models/Common.cs
Normal file
181
src/Discord.Net/API/Models/Common.cs
Normal file
@@ -0,0 +1,181 @@
|
||||
//Ignore unused/unassigned variable warnings
|
||||
#pragma warning disable CS0649
|
||||
#pragma warning disable CS0169
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
|
||||
namespace Discord.API.Models
|
||||
{
|
||||
internal class WebSocketMessage
|
||||
{
|
||||
[JsonProperty(PropertyName = "op")]
|
||||
public int Operation;
|
||||
[JsonProperty(PropertyName = "t")]
|
||||
public string Type;
|
||||
[JsonProperty(PropertyName = "d")]
|
||||
public object Payload;
|
||||
}
|
||||
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; }
|
||||
}
|
||||
}
|
||||
|
||||
//Users
|
||||
internal class UserReference
|
||||
{
|
||||
[JsonProperty(PropertyName = "username")]
|
||||
public string Username;
|
||||
[JsonProperty(PropertyName = "id")]
|
||||
public string Id;
|
||||
[JsonProperty(PropertyName = "discriminator")]
|
||||
public string Discriminator;
|
||||
[JsonProperty(PropertyName = "avatar")]
|
||||
public string Avatar;
|
||||
}
|
||||
internal class SelfUserInfo : UserReference
|
||||
{
|
||||
[JsonProperty(PropertyName = "email")]
|
||||
public string Email;
|
||||
[JsonProperty(PropertyName = "verified")]
|
||||
public bool IsVerified;
|
||||
}
|
||||
internal class PresenceUserInfo : UserReference
|
||||
{
|
||||
[JsonProperty(PropertyName = "game_id")]
|
||||
public string GameId;
|
||||
[JsonProperty(PropertyName = "status")]
|
||||
public string Status;
|
||||
}
|
||||
|
||||
//Channels
|
||||
internal class ChannelReference
|
||||
{
|
||||
[JsonProperty(PropertyName = "id")]
|
||||
public string Id;
|
||||
[JsonProperty(PropertyName = "guild_id")]
|
||||
public string GuildId;
|
||||
[JsonProperty(PropertyName = "name")]
|
||||
public string Name;
|
||||
[JsonProperty(PropertyName = "type")]
|
||||
public string Type;
|
||||
}
|
||||
internal class ChannelInfo : ChannelReference
|
||||
{
|
||||
[JsonProperty(PropertyName = "last_message_id")]
|
||||
public string LastMessageId;
|
||||
[JsonProperty(PropertyName = "is_private")]
|
||||
public bool IsPrivate;
|
||||
[JsonProperty(PropertyName = "permission_overwrites")]
|
||||
public object[] PermissionOverwrites;
|
||||
[JsonProperty(PropertyName = "recipient")]
|
||||
public UserReference Recipient;
|
||||
}
|
||||
|
||||
//Servers
|
||||
internal class ServerReference
|
||||
{
|
||||
[JsonProperty(PropertyName = "id")]
|
||||
public string Id;
|
||||
[JsonProperty(PropertyName = "name")]
|
||||
public string Name;
|
||||
}
|
||||
internal class ServerInfo : ServerReference
|
||||
{
|
||||
[JsonProperty(PropertyName = "afk_channel_id")]
|
||||
public string AFKChannelId;
|
||||
[JsonProperty(PropertyName = "afk_timeout")]
|
||||
public int AFKTimeout;
|
||||
[JsonProperty(PropertyName = "embed_channel_id")]
|
||||
public string EmbedChannelId;
|
||||
[JsonProperty(PropertyName = "embed_enabled")]
|
||||
public bool EmbedEnabled;
|
||||
[JsonProperty(PropertyName = "icon")]
|
||||
public string Icon;
|
||||
[JsonProperty(PropertyName = "joined_at")]
|
||||
public DateTime? JoinedAt;
|
||||
[JsonProperty(PropertyName = "owner_id")]
|
||||
public string OwnerId;
|
||||
[JsonProperty(PropertyName = "region")]
|
||||
public string Region;
|
||||
[JsonProperty(PropertyName = "roles")]
|
||||
public Role[] Roles;
|
||||
}
|
||||
internal class ExtendedServerInfo : ServerInfo
|
||||
{
|
||||
public class Membership
|
||||
{
|
||||
[JsonProperty(PropertyName = "roles")]
|
||||
public string[] Roles;
|
||||
[JsonProperty(PropertyName = "mute")]
|
||||
public bool IsMuted;
|
||||
[JsonProperty(PropertyName = "deaf")]
|
||||
public bool IsDeaf;
|
||||
[JsonProperty(PropertyName = "joined_at")]
|
||||
public DateTime JoinedAt;
|
||||
[JsonProperty(PropertyName = "user")]
|
||||
public UserReference User;
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "channels")]
|
||||
public ChannelInfo[] Channels;
|
||||
[JsonProperty(PropertyName = "members")]
|
||||
public Membership[] Members;
|
||||
[JsonProperty(PropertyName = "presence")]
|
||||
public object[] Presence;
|
||||
[JsonProperty(PropertyName = "voice_states")]
|
||||
public object[] VoiceStates;
|
||||
}
|
||||
|
||||
//Messages
|
||||
internal class MessageReference
|
||||
{
|
||||
[JsonProperty(PropertyName = "id")]
|
||||
public string Id;
|
||||
[JsonProperty(PropertyName = "channel_id")]
|
||||
public string ChannelId;
|
||||
[JsonProperty(PropertyName = "message_id")]
|
||||
public string MessageId { get { return Id; } set { Id = value; } }
|
||||
}
|
||||
internal class Message : MessageReference
|
||||
{
|
||||
[JsonProperty(PropertyName = "tts")]
|
||||
public bool IsTextToSpeech;
|
||||
[JsonProperty(PropertyName = "mention_everyone")]
|
||||
public bool IsMentioningEveryone;
|
||||
[JsonProperty(PropertyName = "timestamp")]
|
||||
public DateTime Timestamp;
|
||||
[JsonProperty(PropertyName = "mentions")]
|
||||
public UserReference[] Mentions;
|
||||
[JsonProperty(PropertyName = "embeds")]
|
||||
public object[] Embeds;
|
||||
[JsonProperty(PropertyName = "attachments")]
|
||||
public object[] Attachments;
|
||||
[JsonProperty(PropertyName = "content")]
|
||||
public string Content;
|
||||
[JsonProperty(PropertyName = "author")]
|
||||
public UserReference Author;
|
||||
}
|
||||
|
||||
//Roles
|
||||
internal class Role
|
||||
{
|
||||
[JsonProperty(PropertyName = "permissions")]
|
||||
public int Permissions;
|
||||
[JsonProperty(PropertyName = "name")]
|
||||
public string Name;
|
||||
[JsonProperty(PropertyName = "id")]
|
||||
public string Id;
|
||||
}
|
||||
}
|
||||
41
src/Discord.Net/API/Models/WebSocketCommands.cs
Normal file
41
src/Discord.Net/API/Models/WebSocketCommands.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
//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.Models
|
||||
{
|
||||
internal static class WebSocketCommands
|
||||
{
|
||||
public sealed class KeepAlive : WebSocketMessage<int>
|
||||
{
|
||||
private static DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
public KeepAlive() : base(1, (int)(DateTime.UtcNow - epoch).TotalMilliseconds) { }
|
||||
}
|
||||
public sealed class Login : WebSocketMessage<Login.Data>
|
||||
{
|
||||
public Login() : base(2) { }
|
||||
public class Data
|
||||
{
|
||||
[JsonProperty(PropertyName = "token")]
|
||||
public string Token;
|
||||
[JsonProperty(PropertyName = "properties")]
|
||||
public Dictionary<string, string> Properties = new Dictionary<string, string>();
|
||||
}
|
||||
}
|
||||
public sealed class UpdateStatus : WebSocketMessage<UpdateStatus.Data>
|
||||
{
|
||||
public UpdateStatus() : base(3) { }
|
||||
public class Data
|
||||
{
|
||||
[JsonProperty(PropertyName = "idle_since")]
|
||||
public string IdleSince;
|
||||
[JsonProperty(PropertyName = "game_id")]
|
||||
public string GameId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
143
src/Discord.Net/API/Models/WebSocketEvents.cs
Normal file
143
src/Discord.Net/API/Models/WebSocketEvents.cs
Normal file
@@ -0,0 +1,143 @@
|
||||
//Ignore unused/unassigned variable warnings
|
||||
#pragma warning disable CS0649
|
||||
#pragma warning disable CS0169
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
|
||||
namespace Discord.API.Models
|
||||
{
|
||||
internal static class WebSocketEvents
|
||||
{
|
||||
public sealed class Ready
|
||||
{
|
||||
[JsonProperty(PropertyName = "user")]
|
||||
public SelfUserInfo User;
|
||||
[JsonProperty(PropertyName = "session_id")]
|
||||
public string SessionId;
|
||||
[JsonProperty(PropertyName = "read_state")]
|
||||
public object[] ReadState;
|
||||
[JsonProperty(PropertyName = "guilds")]
|
||||
public ExtendedServerInfo[] Guilds;
|
||||
[JsonProperty(PropertyName = "private_channels")]
|
||||
public ChannelInfo[] PrivateChannels;
|
||||
[JsonProperty(PropertyName = "heartbeat_interval")]
|
||||
public int HeartbeatInterval;
|
||||
}
|
||||
|
||||
//Servers
|
||||
public sealed class GuildCreate : ExtendedServerInfo { }
|
||||
public sealed class GuildUpdate : ServerInfo { }
|
||||
public sealed class GuildDelete : ExtendedServerInfo { }
|
||||
|
||||
//Channels
|
||||
public sealed class ChannelCreate : ChannelInfo { }
|
||||
public sealed class ChannelDelete : ChannelInfo { }
|
||||
public sealed class ChannelUpdate : ChannelInfo { }
|
||||
|
||||
//Memberships
|
||||
public abstract class GuildMemberEvent
|
||||
{
|
||||
[JsonProperty(PropertyName = "user")]
|
||||
public UserReference User;
|
||||
[JsonProperty(PropertyName = "guild_id")]
|
||||
public string GuildId;
|
||||
}
|
||||
public sealed class GuildMemberAdd : GuildMemberEvent
|
||||
{
|
||||
[JsonProperty(PropertyName = "joined_at")]
|
||||
public DateTime JoinedAt;
|
||||
[JsonProperty(PropertyName = "roles")]
|
||||
public string[] Roles;
|
||||
}
|
||||
public sealed class GuildMemberUpdate : GuildMemberEvent
|
||||
{
|
||||
[JsonProperty(PropertyName = "roles")]
|
||||
public string[] Roles;
|
||||
}
|
||||
public sealed class GuildMemberRemove : GuildMemberEvent { }
|
||||
|
||||
//Roles
|
||||
public abstract class GuildRoleEvent
|
||||
{
|
||||
[JsonProperty(PropertyName = "guild_id")]
|
||||
public string GuildId;
|
||||
}
|
||||
public sealed class GuildRoleCreateUpdate : GuildRoleEvent
|
||||
{
|
||||
[JsonProperty(PropertyName = "role")]
|
||||
public Role Role;
|
||||
}
|
||||
public sealed class GuildRoleDelete : GuildRoleEvent
|
||||
{
|
||||
[JsonProperty(PropertyName = "role_id")]
|
||||
public string RoleId;
|
||||
}
|
||||
|
||||
//Bans
|
||||
public abstract class GuildBanEvent
|
||||
{
|
||||
[JsonProperty(PropertyName = "guild_id")]
|
||||
public string GuildId;
|
||||
}
|
||||
public sealed class GuildBanAddRemove : GuildBanEvent
|
||||
{
|
||||
[JsonProperty(PropertyName = "user")]
|
||||
public UserReference User;
|
||||
}
|
||||
public sealed class GuildBanRemove : GuildBanEvent
|
||||
{
|
||||
[JsonProperty(PropertyName = "user_id")]
|
||||
public string UserId;
|
||||
}
|
||||
|
||||
//User
|
||||
public sealed class UserUpdate : SelfUserInfo { }
|
||||
public sealed class PresenceUpdate : PresenceUserInfo { }
|
||||
public sealed class VoiceStateUpdate
|
||||
{
|
||||
[JsonProperty(PropertyName = "user_id")]
|
||||
public string UserId;
|
||||
[JsonProperty(PropertyName = "guild_id")]
|
||||
public string GuildId;
|
||||
[JsonProperty(PropertyName = "channel_id")]
|
||||
public string ChannelId;
|
||||
[JsonProperty(PropertyName = "suppress")]
|
||||
public bool IsSuppressed;
|
||||
[JsonProperty(PropertyName = "session_id")]
|
||||
public string SessionId;
|
||||
[JsonProperty(PropertyName = "self_mute")]
|
||||
public bool IsSelfMuted;
|
||||
[JsonProperty(PropertyName = "self_deaf")]
|
||||
public bool IsSelfDeafened;
|
||||
[JsonProperty(PropertyName = "mute")]
|
||||
public bool IsMuted;
|
||||
[JsonProperty(PropertyName = "deaf")]
|
||||
public bool IsDeafened;
|
||||
}
|
||||
|
||||
//Chat
|
||||
public sealed class MessageCreate : Message { }
|
||||
public sealed class MessageUpdate : Message { }
|
||||
public sealed class MessageDelete : MessageReference { }
|
||||
public sealed class MessageAck : MessageReference { }
|
||||
public sealed class TypingStart
|
||||
{
|
||||
[JsonProperty(PropertyName = "user_id")]
|
||||
public string UserId;
|
||||
[JsonProperty(PropertyName = "channel_id")]
|
||||
public string ChannelId;
|
||||
[JsonProperty(PropertyName = "timestamp")]
|
||||
public int Timestamp;
|
||||
}
|
||||
|
||||
//Voice
|
||||
public sealed class VoiceServerUpdate
|
||||
{
|
||||
[JsonProperty(PropertyName = "guild_id")]
|
||||
public string ServerId;
|
||||
[JsonProperty(PropertyName = "endpoint")]
|
||||
public string Endpoint;
|
||||
}
|
||||
}
|
||||
}
|
||||
46
src/Discord.Net/Channel.cs
Normal file
46
src/Discord.Net/Channel.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public sealed class Channel
|
||||
{
|
||||
private readonly DiscordClient _client;
|
||||
|
||||
public string Id { get; }
|
||||
|
||||
private string _name;
|
||||
public string Name { get { return !IsPrivate ? $"#{_name}" : $"@{Recipient.Name}"; } internal set { _name = value; } }
|
||||
|
||||
public bool IsPrivate { get; }
|
||||
public string Type { get; internal set; }
|
||||
|
||||
public string ServerId { get; }
|
||||
[JsonIgnore]
|
||||
public Server Server => ServerId != null ? _client.GetServer(ServerId) : null;
|
||||
|
||||
[JsonIgnore]
|
||||
public string RecipientId { get; internal set; }
|
||||
public User Recipient => _client.GetUser(RecipientId);
|
||||
|
||||
[JsonIgnore]
|
||||
public IEnumerable<Message> Messages => _client.Messages.Where(x => x.ChannelId == Id);
|
||||
|
||||
//Not Implemented
|
||||
public object[] PermissionOverwrites { get; internal set; }
|
||||
|
||||
internal Channel(string id, string serverId, DiscordClient client)
|
||||
{
|
||||
Id = id;
|
||||
ServerId = serverId;
|
||||
IsPrivate = serverId == null;
|
||||
_client = client;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
8
src/Discord.Net/ChannelTypes.cs
Normal file
8
src/Discord.Net/ChannelTypes.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Discord
|
||||
{
|
||||
public static class ChannelTypes
|
||||
{
|
||||
public const string Text = "text";
|
||||
public const string Voice = "voice";
|
||||
}
|
||||
}
|
||||
21
src/Discord.Net/Discord.Net.xproj
Normal file
21
src/Discord.Net/Discord.Net.xproj
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>acfb060b-ec8a-4926-b293-04c01e17ee23</ProjectGuid>
|
||||
<RootNamespace>Discord.Net</RootNamespace>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<ProduceOutputsOnBuild>True</ProduceOutputsOnBuild>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
252
src/Discord.Net/DiscordClient.Events.cs
Normal file
252
src/Discord.Net/DiscordClient.Events.cs
Normal file
@@ -0,0 +1,252 @@
|
||||
using System;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public partial class DiscordClient
|
||||
{
|
||||
//Debug
|
||||
public sealed class LogMessageEventArgs : EventArgs
|
||||
{
|
||||
public readonly string Message;
|
||||
internal LogMessageEventArgs(string msg) { Message = msg; }
|
||||
}
|
||||
public event EventHandler<LogMessageEventArgs> DebugMessage;
|
||||
private void RaiseOnDebugMessage(string message)
|
||||
{
|
||||
if (DebugMessage != null)
|
||||
DebugMessage(this, new LogMessageEventArgs(message));
|
||||
}
|
||||
|
||||
//General
|
||||
public event EventHandler Connected;
|
||||
private void RaiseConnected()
|
||||
{
|
||||
if (Connected != null)
|
||||
Connected(this, EventArgs.Empty);
|
||||
}
|
||||
public event EventHandler Disconnected;
|
||||
private void RaiseDisconnected()
|
||||
{
|
||||
if (Disconnected != null)
|
||||
Disconnected(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
//Server
|
||||
public sealed class ServerEventArgs : EventArgs
|
||||
{
|
||||
public readonly Server Server;
|
||||
internal ServerEventArgs(Server server) { Server = server; }
|
||||
}
|
||||
|
||||
public event EventHandler<ServerEventArgs> ServerCreated;
|
||||
private void RaiseServerCreated(Server server)
|
||||
{
|
||||
if (ServerCreated != null)
|
||||
ServerCreated(this, new ServerEventArgs(server));
|
||||
}
|
||||
public event EventHandler<ServerEventArgs> ServerDestroyed;
|
||||
private void RaiseServerDestroyed(Server server)
|
||||
{
|
||||
if (ServerDestroyed != null)
|
||||
ServerDestroyed(this, new ServerEventArgs(server));
|
||||
}
|
||||
public event EventHandler<ServerEventArgs> ServerUpdated;
|
||||
private void RaiseServerUpdated(Server server)
|
||||
{
|
||||
if (ServerUpdated != null)
|
||||
ServerUpdated(this, new ServerEventArgs(server));
|
||||
}
|
||||
|
||||
//Channel
|
||||
public sealed class ChannelEventArgs : EventArgs
|
||||
{
|
||||
public readonly Channel Channel;
|
||||
internal ChannelEventArgs(Channel channel) { Channel = channel; }
|
||||
}
|
||||
|
||||
public event EventHandler<ChannelEventArgs> ChannelCreated;
|
||||
private void RaiseChannelCreated(Channel channel)
|
||||
{
|
||||
if (ChannelCreated != null)
|
||||
ChannelCreated(this, new ChannelEventArgs(channel));
|
||||
}
|
||||
public event EventHandler<ChannelEventArgs> ChannelDestroyed;
|
||||
private void RaiseChannelDestroyed(Channel channel)
|
||||
{
|
||||
if (ChannelDestroyed != null)
|
||||
ChannelDestroyed(this, new ChannelEventArgs(channel));
|
||||
}
|
||||
public event EventHandler<ChannelEventArgs> ChannelUpdated;
|
||||
private void RaiseChannelUpdated(Channel channel)
|
||||
{
|
||||
if (ChannelUpdated != null)
|
||||
ChannelUpdated(this, new ChannelEventArgs(channel));
|
||||
}
|
||||
|
||||
//User
|
||||
public sealed class UserEventArgs : EventArgs
|
||||
{
|
||||
public readonly User User;
|
||||
internal UserEventArgs(User user) { User = user; }
|
||||
}
|
||||
|
||||
//Message
|
||||
public sealed class MessageEventArgs : EventArgs
|
||||
{
|
||||
public readonly Message Message;
|
||||
internal MessageEventArgs(Message msg) { Message = msg; }
|
||||
}
|
||||
|
||||
public event EventHandler<MessageEventArgs> MessageCreated;
|
||||
private void RaiseMessageCreated(Message msg)
|
||||
{
|
||||
if (MessageCreated != null)
|
||||
MessageCreated(this, new MessageEventArgs(msg));
|
||||
}
|
||||
public event EventHandler<MessageEventArgs> MessageDeleted;
|
||||
private void RaiseMessageDeleted(Message msg)
|
||||
{
|
||||
if (MessageDeleted != null)
|
||||
MessageDeleted(this, new MessageEventArgs(msg));
|
||||
}
|
||||
public event EventHandler<MessageEventArgs> MessageUpdated;
|
||||
private void RaiseMessageUpdated(Message msg)
|
||||
{
|
||||
if (MessageUpdated != null)
|
||||
MessageUpdated(this, new MessageEventArgs(msg));
|
||||
}
|
||||
public event EventHandler<MessageEventArgs> MessageAcknowledged;
|
||||
private void RaiseMessageAcknowledged(Message msg)
|
||||
{
|
||||
if (MessageAcknowledged != null)
|
||||
MessageAcknowledged(this, new MessageEventArgs(msg));
|
||||
}
|
||||
|
||||
//Role
|
||||
public sealed class RoleEventArgs : EventArgs
|
||||
{
|
||||
public readonly Role Role;
|
||||
internal RoleEventArgs(Role role) { Role = role; }
|
||||
}
|
||||
|
||||
public event EventHandler<RoleEventArgs> RoleCreated;
|
||||
private void RaiseRoleCreated(Role role)
|
||||
{
|
||||
if (RoleCreated != null)
|
||||
RoleCreated(this, new RoleEventArgs(role));
|
||||
}
|
||||
public event EventHandler<RoleEventArgs> RoleUpdated;
|
||||
private void RaiseRoleDeleted(Role role)
|
||||
{
|
||||
if (RoleDeleted != null)
|
||||
RoleDeleted(this, new RoleEventArgs(role));
|
||||
}
|
||||
public event EventHandler<RoleEventArgs> RoleDeleted;
|
||||
private void RaiseRoleUpdated(Role role)
|
||||
{
|
||||
if (RoleUpdated != null)
|
||||
RoleUpdated(this, new RoleEventArgs(role));
|
||||
}
|
||||
|
||||
//Ban
|
||||
public sealed class BanEventArgs : EventArgs
|
||||
{
|
||||
public readonly User User;
|
||||
public readonly Server Server;
|
||||
internal BanEventArgs(User user, Server server)
|
||||
{
|
||||
User = user;
|
||||
Server = server;
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler<BanEventArgs> BanAdded;
|
||||
private void RaiseBanAdded(User user, Server server)
|
||||
{
|
||||
if (BanAdded != null)
|
||||
BanAdded(this, new BanEventArgs(user, server));
|
||||
}
|
||||
public event EventHandler<BanEventArgs> BanRemoved;
|
||||
private void RaiseBanRemoved(User user, Server server)
|
||||
{
|
||||
if (BanRemoved != null)
|
||||
BanRemoved(this, new BanEventArgs(user, server));
|
||||
}
|
||||
|
||||
//Member
|
||||
public sealed class MemberEventArgs : EventArgs
|
||||
{
|
||||
public readonly Membership Membership;
|
||||
internal MemberEventArgs(Membership membership) { Membership = membership; }
|
||||
}
|
||||
|
||||
public event EventHandler<MemberEventArgs> MemberAdded;
|
||||
private void RaiseMemberAdded(Membership membership, Server server)
|
||||
{
|
||||
if (MemberAdded != null)
|
||||
MemberAdded(this, new MemberEventArgs(membership));
|
||||
}
|
||||
public event EventHandler<MemberEventArgs> MemberRemoved;
|
||||
private void RaiseMemberRemoved(Membership membership, Server server)
|
||||
{
|
||||
if (MemberRemoved != null)
|
||||
MemberRemoved(this, new MemberEventArgs(membership));
|
||||
}
|
||||
public event EventHandler<MemberEventArgs> MemberUpdated;
|
||||
private void RaiseMemberUpdated(Membership membership, Server server)
|
||||
{
|
||||
if (MemberUpdated != null)
|
||||
MemberUpdated(this, new MemberEventArgs(membership));
|
||||
}
|
||||
|
||||
//Status
|
||||
public sealed class UserTypingEventArgs : EventArgs
|
||||
{
|
||||
public readonly User User;
|
||||
public readonly Channel Channel;
|
||||
internal UserTypingEventArgs(User user, Channel channel)
|
||||
{
|
||||
User = user;
|
||||
Channel = channel;
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler<UserEventArgs> PresenceUpdated;
|
||||
private void RaisePresenceUpdated(User user)
|
||||
{
|
||||
if (PresenceUpdated != null)
|
||||
PresenceUpdated(this, new UserEventArgs(user));
|
||||
}
|
||||
public event EventHandler<UserEventArgs> VoiceStateUpdated;
|
||||
private void RaiseVoiceStateUpdated(User user)
|
||||
{
|
||||
if (VoiceStateUpdated != null)
|
||||
VoiceStateUpdated(this, new UserEventArgs(user));
|
||||
}
|
||||
public event EventHandler<UserTypingEventArgs> UserTyping;
|
||||
private void RaiseUserTyping(User user, Channel channel)
|
||||
{
|
||||
if (UserTyping != null)
|
||||
UserTyping(this, new UserTypingEventArgs(user, channel));
|
||||
}
|
||||
|
||||
//Voice
|
||||
public sealed class VoiceServerUpdatedEventArgs : EventArgs
|
||||
{
|
||||
public readonly Server Server;
|
||||
public readonly string Endpoint;
|
||||
internal VoiceServerUpdatedEventArgs(Server server, string endpoint)
|
||||
{
|
||||
Server = server;
|
||||
Endpoint = endpoint;
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler<VoiceServerUpdatedEventArgs> VoiceServerUpdated;
|
||||
private void RaiseVoiceServerUpdated(Server server, string endpoint)
|
||||
{
|
||||
if (VoiceServerUpdated != null)
|
||||
VoiceServerUpdated(this, new VoiceServerUpdatedEventArgs(server, endpoint));
|
||||
}
|
||||
}
|
||||
}
|
||||
812
src/Discord.Net/DiscordClient.cs
Normal file
812
src/Discord.Net/DiscordClient.cs
Normal file
@@ -0,0 +1,812 @@
|
||||
using Discord.API;
|
||||
using Discord.API.Models;
|
||||
using Discord.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public partial class DiscordClient
|
||||
{
|
||||
public const int ReconnectDelay = 1000; //Time in milliseconds to wait after an unexpected disconnect before retrying
|
||||
public const int FailedReconnectDelay = 10000; //Time in milliseconds to wait after a failed reconnect attempt
|
||||
|
||||
private DiscordWebSocket _webSocket;
|
||||
private HttpOptions _httpOptions;
|
||||
private bool _isReady;
|
||||
|
||||
public string UserId { get; private set; }
|
||||
public User User => _users[UserId];
|
||||
|
||||
public IEnumerable<User> Users => _users;
|
||||
private AsyncCache<User, API.Models.UserReference> _users;
|
||||
|
||||
public IEnumerable<Server> Servers => _servers;
|
||||
private AsyncCache<Server, API.Models.ServerReference> _servers;
|
||||
|
||||
public IEnumerable<Channel> Channels => _channels;
|
||||
private AsyncCache<Channel, API.Models.ChannelReference> _channels;
|
||||
|
||||
public IEnumerable<Message> Messages => _messages;
|
||||
private AsyncCache<Message, API.Models.MessageReference> _messages;
|
||||
|
||||
public IEnumerable<Role> Roles => _roles;
|
||||
private AsyncCache<Role, API.Models.Role> _roles;
|
||||
|
||||
private ManualResetEventSlim _isStopping;
|
||||
|
||||
public bool IsConnected => _isReady;
|
||||
|
||||
public DiscordClient()
|
||||
{
|
||||
_isStopping = new ManualResetEventSlim(false);
|
||||
string version = typeof(DiscordClient).GetTypeInfo().Assembly.GetName().Version.ToString(2);
|
||||
_httpOptions = new HttpOptions($"Discord.Net/{version} (https://github.com/RogueException/Discord.Net)");
|
||||
|
||||
_servers = new AsyncCache<Server, API.Models.ServerReference>(
|
||||
(key, parentKey) => new Server(key, this),
|
||||
(server, model) =>
|
||||
{
|
||||
server.Name = model.Name;
|
||||
if (!server.Channels.Any()) //Assume a default channel exists with the same id as the server. Not sure if this is safe?
|
||||
{
|
||||
var defaultChannel = new ChannelReference() { Id = server.DefaultChannelId, GuildId = server.Id };
|
||||
_channels.Update(defaultChannel.Id, defaultChannel.GuildId, defaultChannel);
|
||||
}
|
||||
if (model is ExtendedServerInfo)
|
||||
{
|
||||
var extendedModel = model as ExtendedServerInfo;
|
||||
server.AFKChannelId = extendedModel.AFKChannelId;
|
||||
server.AFKTimeout = extendedModel.AFKTimeout;
|
||||
server.JoinedAt = extendedModel.JoinedAt ?? DateTime.MinValue;
|
||||
server.OwnerId = extendedModel.OwnerId;
|
||||
server.Presence = extendedModel.Presence;
|
||||
server.Region = extendedModel.Region;
|
||||
server.VoiceStates = extendedModel.VoiceStates;
|
||||
|
||||
foreach (var role in extendedModel.Roles)
|
||||
_roles.Update(role.Id, model.Id, role);
|
||||
foreach (var channel in extendedModel.Channels)
|
||||
_channels.Update(channel.Id, model.Id, channel);
|
||||
foreach (var membership in extendedModel.Members)
|
||||
{
|
||||
_users.Update(membership.User.Id, membership.User);
|
||||
server.AddMember(new Membership(server.Id, membership.User.Id, membership.JoinedAt, this) { RoleIds = membership.Roles, IsMuted = membership.IsMuted, IsDeafened = membership.IsDeaf });
|
||||
}
|
||||
}
|
||||
},
|
||||
server => { }
|
||||
);
|
||||
|
||||
_channels = new AsyncCache<Channel, API.Models.ChannelReference>(
|
||||
(key, parentKey) => new Channel(key, parentKey, this),
|
||||
(channel, model) =>
|
||||
{
|
||||
channel.Name = model.Name;
|
||||
channel.Type = model.Type;
|
||||
if (model is ChannelInfo)
|
||||
{
|
||||
var extendedModel = model as ChannelInfo;
|
||||
channel.PermissionOverwrites = extendedModel.PermissionOverwrites;
|
||||
channel.RecipientId = extendedModel.Recipient?.Id;
|
||||
}
|
||||
},
|
||||
channel => { });
|
||||
_messages = new AsyncCache<Message, API.Models.MessageReference>(
|
||||
(key, parentKey) => new Message(key, parentKey, this),
|
||||
(message, model) =>
|
||||
{
|
||||
if (model is API.Models.Message)
|
||||
{
|
||||
var extendedModel = model as API.Models.Message;
|
||||
message.Attachments = extendedModel.Attachments;
|
||||
message.Embeds = extendedModel.Embeds;
|
||||
message.IsMentioningEveryone = extendedModel.IsMentioningEveryone;
|
||||
message.IsTTS = extendedModel.IsTextToSpeech;
|
||||
message.MentionIds = extendedModel.Mentions?.Select(x => x.Id)?.ToArray() ?? new string[0];
|
||||
if (extendedModel.Author != null)
|
||||
message.UserId = extendedModel.Author.Id;
|
||||
message.Timestamp = extendedModel.Timestamp;
|
||||
message.Text = extendedModel.Content;
|
||||
}
|
||||
},
|
||||
message => { }
|
||||
);
|
||||
_roles = new AsyncCache<Role, API.Models.Role>(
|
||||
(key, parentKey) => new Role(key, parentKey, this),
|
||||
(role, model) =>
|
||||
{
|
||||
role.Name = model.Name;
|
||||
role.Permissions.RawValue = (uint)model.Permissions;
|
||||
},
|
||||
role => { }
|
||||
);
|
||||
_users = new AsyncCache<User, API.Models.UserReference>(
|
||||
(key, parentKey) => new User(key, this),
|
||||
(user, model) =>
|
||||
{
|
||||
user.AvatarId = model.Avatar;
|
||||
user.Discriminator = model.Discriminator;
|
||||
user.Name = model.Username;
|
||||
if (model is SelfUserInfo)
|
||||
{
|
||||
var extendedModel = model as SelfUserInfo;
|
||||
user.Email = extendedModel.Email;
|
||||
user.IsVerified = extendedModel.IsVerified;
|
||||
}
|
||||
if (model is PresenceUserInfo)
|
||||
{
|
||||
var extendedModel = model as PresenceUserInfo;
|
||||
user.GameId = extendedModel.GameId;
|
||||
user.Status = extendedModel.Status;
|
||||
}
|
||||
},
|
||||
user => { }
|
||||
);
|
||||
|
||||
_webSocket = new DiscordWebSocket();
|
||||
_webSocket.Connected += (s, e) => RaiseConnected();
|
||||
_webSocket.Disconnected += async (s, e) =>
|
||||
{
|
||||
//Reconnect if we didn't cause the disconnect
|
||||
RaiseDisconnected();
|
||||
while (!_isStopping.IsSet)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Delay(ReconnectDelay);
|
||||
await _webSocket.ConnectAsync(Endpoints.WebSocket_Hub, true, _httpOptions);
|
||||
break;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//Net is down? We can keep trying to reconnect until the user runs Disconnect()
|
||||
await Task.Delay(FailedReconnectDelay);
|
||||
}
|
||||
}
|
||||
};
|
||||
_webSocket.GotEvent += (s, e) =>
|
||||
{
|
||||
switch (e.Type)
|
||||
{
|
||||
//Global
|
||||
case "READY": //Resync
|
||||
{
|
||||
var data = e.Event.ToObject<WebSocketEvents.Ready>();
|
||||
|
||||
_servers.Clear();
|
||||
_channels.Clear();
|
||||
_users.Clear();
|
||||
|
||||
UserId = data.User.Id;
|
||||
_users.Update(data.User.Id, data.User);
|
||||
foreach (var server in data.Guilds)
|
||||
_servers.Update(server.Id, server);
|
||||
foreach (var channel in data.PrivateChannels)
|
||||
_channels.Update(channel.Id, null, channel);
|
||||
}
|
||||
break;
|
||||
|
||||
//Servers
|
||||
case "GUILD_CREATE":
|
||||
{
|
||||
var data = e.Event.ToObject<WebSocketEvents.GuildCreate>();
|
||||
var server = _servers.Update(data.Id, data);
|
||||
RaiseServerCreated(server);
|
||||
}
|
||||
break;
|
||||
case "GUILD_UPDATE":
|
||||
{
|
||||
var data = e.Event.ToObject<WebSocketEvents.GuildUpdate>();
|
||||
var server = _servers.Update(data.Id, data);
|
||||
RaiseServerUpdated(server);
|
||||
}
|
||||
break;
|
||||
case "GUILD_DELETE":
|
||||
{
|
||||
var data = e.Event.ToObject<WebSocketEvents.GuildDelete>();
|
||||
var server = _servers.Remove(data.Id);
|
||||
if (server != null)
|
||||
RaiseServerDestroyed(server);
|
||||
}
|
||||
break;
|
||||
|
||||
//Channels
|
||||
case "CHANNEL_CREATE":
|
||||
{
|
||||
var data = e.Event.ToObject<WebSocketEvents.ChannelCreate>();
|
||||
var channel = _channels.Update(data.Id, data.GuildId, data);
|
||||
RaiseChannelCreated(channel);
|
||||
}
|
||||
break;
|
||||
case "CHANNEL_UPDATE":
|
||||
{
|
||||
var data = e.Event.ToObject<WebSocketEvents.ChannelUpdate>();
|
||||
var channel = _channels.Update(data.Id, data.GuildId, data);
|
||||
RaiseChannelUpdated(channel);
|
||||
}
|
||||
break;
|
||||
case "CHANNEL_DELETE":
|
||||
{
|
||||
var data = e.Event.ToObject<WebSocketEvents.ChannelDelete>();
|
||||
var channel = _channels.Remove(data.Id);
|
||||
if (channel != null)
|
||||
RaiseChannelDestroyed(channel);
|
||||
}
|
||||
break;
|
||||
|
||||
//Members
|
||||
case "GUILD_MEMBER_ADD":
|
||||
{
|
||||
var data = e.Event.ToObject<WebSocketEvents.GuildMemberAdd>();
|
||||
var user = _users.Update(data.User.Id, data.User);
|
||||
var server = _servers[data.GuildId];
|
||||
var membership = new Membership(server.Id, data.User.Id, data.JoinedAt, this) { RoleIds = data.Roles };
|
||||
server.AddMember(membership);
|
||||
RaiseMemberAdded(membership, server);
|
||||
}
|
||||
break;
|
||||
case "GUILD_MEMBER_UPDATE":
|
||||
{
|
||||
var data = e.Event.ToObject<WebSocketEvents.GuildMemberUpdate>();
|
||||
var user = _users.Update(data.User.Id, data.User);
|
||||
var server = _servers[data.GuildId];
|
||||
var membership = server.GetMembership(data.User.Id);
|
||||
if (membership != null)
|
||||
membership.RoleIds = data.Roles;
|
||||
RaiseMemberUpdated(membership, server);
|
||||
}
|
||||
break;
|
||||
case "GUILD_MEMBER_REMOVE":
|
||||
{
|
||||
var data = e.Event.ToObject<WebSocketEvents.GuildMemberRemove>();
|
||||
var user = _users.Update(data.User.Id, data.User);
|
||||
var server = _servers[data.GuildId];
|
||||
if (server != null)
|
||||
{
|
||||
var membership = server.RemoveMember(user.Id);
|
||||
if (membership != null)
|
||||
RaiseMemberRemoved(membership, server);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
//Roles
|
||||
case "GUILD_ROLE_CREATE":
|
||||
{
|
||||
var data = e.Event.ToObject<WebSocketEvents.GuildRoleCreateUpdate>();
|
||||
var role = _roles.Update(data.Role.Id, data.GuildId, data.Role);
|
||||
RaiseRoleCreated(role);
|
||||
}
|
||||
break;
|
||||
case "GUILD_ROLE_UPDATE":
|
||||
{
|
||||
var data = e.Event.ToObject<WebSocketEvents.GuildRoleCreateUpdate>();
|
||||
var role = _roles.Update(data.Role.Id, data.GuildId, data.Role);
|
||||
RaiseRoleUpdated(role);
|
||||
}
|
||||
break;
|
||||
case "GUILD_ROLE_DELETE":
|
||||
{
|
||||
var data = e.Event.ToObject<WebSocketEvents.GuildRoleDelete>();
|
||||
var role = _roles.Remove(data.RoleId);
|
||||
if (role != null)
|
||||
RaiseRoleDeleted(role);
|
||||
}
|
||||
break;
|
||||
|
||||
//Bans
|
||||
case "GUILD_BAN_ADD":
|
||||
{
|
||||
var data = e.Event.ToObject<WebSocketEvents.GuildBanAddRemove>();
|
||||
var user = _users.Update(data.User.Id, data.User);
|
||||
var server = _servers[data.GuildId];
|
||||
RaiseBanAdded(user, server);
|
||||
}
|
||||
break;
|
||||
case "GUILD_BAN_REMOVE":
|
||||
{
|
||||
var data = e.Event.ToObject<WebSocketEvents.GuildBanAddRemove>();
|
||||
var user = _users.Update(data.User.Id, data.User);
|
||||
var server = _servers[data.GuildId];
|
||||
if (server != null && server.RemoveBan(user.Id))
|
||||
RaiseBanRemoved(user, server);
|
||||
}
|
||||
break;
|
||||
|
||||
//Messages
|
||||
case "MESSAGE_CREATE":
|
||||
{
|
||||
var data = e.Event.ToObject<WebSocketEvents.MessageCreate>();
|
||||
var msg = _messages.Update(data.Id, data.ChannelId, data);
|
||||
msg.User.UpdateActivity(data.Timestamp);
|
||||
RaiseMessageCreated(msg);
|
||||
}
|
||||
break;
|
||||
case "MESSAGE_UPDATE":
|
||||
{
|
||||
var data = e.Event.ToObject<WebSocketEvents.MessageUpdate>();
|
||||
var msg = _messages.Update(data.Id, data.ChannelId, data);
|
||||
RaiseMessageUpdated(msg);
|
||||
}
|
||||
break;
|
||||
case "MESSAGE_DELETE":
|
||||
{
|
||||
var data = e.Event.ToObject<WebSocketEvents.MessageDelete>();
|
||||
var msg = GetMessage(data.MessageId);
|
||||
if (msg != null)
|
||||
_messages.Remove(msg.Id);
|
||||
}
|
||||
break;
|
||||
case "MESSAGE_ACK":
|
||||
{
|
||||
var data = e.Event.ToObject<WebSocketEvents.MessageAck>();
|
||||
var msg = GetMessage(data.MessageId);
|
||||
RaiseMessageAcknowledged(msg);
|
||||
}
|
||||
break;
|
||||
|
||||
//Statuses
|
||||
case "PRESENCE_UPDATE":
|
||||
{
|
||||
var data = e.Event.ToObject<WebSocketEvents.PresenceUpdate>();
|
||||
var user = _users.Update(data.Id, data);
|
||||
RaisePresenceUpdated(user);
|
||||
}
|
||||
break;
|
||||
case "VOICE_STATE_UPDATE":
|
||||
{
|
||||
var data = e.Event.ToObject<WebSocketEvents.VoiceStateUpdate>();
|
||||
var user = _users[data.UserId]; //TODO: Don't ignore this
|
||||
RaiseVoiceStateUpdated(user);
|
||||
}
|
||||
break;
|
||||
case "TYPING_START":
|
||||
{
|
||||
var data = e.Event.ToObject<WebSocketEvents.TypingStart>();
|
||||
var channel = _channels[data.ChannelId];
|
||||
var user = _users[data.UserId];
|
||||
RaiseUserTyping(user, channel);
|
||||
}
|
||||
break;
|
||||
|
||||
//Voice
|
||||
case "VOICE_SERVER_UPDATE":
|
||||
{
|
||||
var data = e.Event.ToObject<WebSocketEvents.VoiceServerUpdate>();
|
||||
var server = _servers[data.ServerId];
|
||||
RaiseVoiceServerUpdated(server, data.Endpoint);
|
||||
}
|
||||
break;
|
||||
|
||||
//Others
|
||||
default:
|
||||
RaiseOnDebugMessage("Unknown WebSocket message type: " + e.Type);
|
||||
break;
|
||||
}
|
||||
};
|
||||
_webSocket.OnDebugMessage += (s, e) => RaiseOnDebugMessage(e.Message);
|
||||
}
|
||||
|
||||
//Collections
|
||||
public User GetUser(string id) => _users[id];
|
||||
public User FindUser(string name)
|
||||
{
|
||||
return _users
|
||||
.Where(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase))
|
||||
.FirstOrDefault();
|
||||
}
|
||||
public User FindUser(string name, string discriminator)
|
||||
{
|
||||
if (name.StartsWith("@"))
|
||||
name = name.Substring(1);
|
||||
|
||||
return _users
|
||||
.Where(x =>
|
||||
string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) &&
|
||||
x.Discriminator == discriminator
|
||||
)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
public User FindChannelUser(Channel channel, string name)
|
||||
=> FindChannelUser(channel.Id, name);
|
||||
public User FindChannelUser(string channelId, string name)
|
||||
{
|
||||
if (name.StartsWith("@"))
|
||||
name = name.Substring(1);
|
||||
|
||||
return _users
|
||||
.Where(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase))
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
public Server GetServer(string id) => _servers[id];
|
||||
public Server FindServer(string name)
|
||||
{
|
||||
return _servers
|
||||
.Where(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase))
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
public Channel GetChannel(string id) => _channels[id];
|
||||
public Channel FindChannel(string name)
|
||||
{
|
||||
if (name.StartsWith("#"))
|
||||
name = name.Substring(1);
|
||||
|
||||
return _channels
|
||||
.Where(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase))
|
||||
.FirstOrDefault();
|
||||
}
|
||||
public Channel FindChannel(Server server, string name)
|
||||
=> FindChannel(server.Id, name);
|
||||
public Channel FindChannel(string serverId, string name)
|
||||
{
|
||||
if (name.StartsWith("#"))
|
||||
name = name.Substring(1);
|
||||
|
||||
return _channels
|
||||
.Where(x =>
|
||||
string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) &&
|
||||
x.ServerId == serverId
|
||||
)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
public Role GetRole(string id) => _roles[id];
|
||||
public Role FindRole(Server server, string name)
|
||||
=> FindRole(server.Id, name);
|
||||
public Role FindRole(string serverId, string name)
|
||||
{
|
||||
return _roles
|
||||
.Where(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase))
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
public Message GetMessage(string id) => _messages[id];
|
||||
public Task<Message[]> DownloadMessages(Channel channel, int count)
|
||||
=> DownloadMessages(channel.Id, count);
|
||||
public async Task<Message[]> DownloadMessages(string channelId, int count)
|
||||
{
|
||||
Channel channel = GetChannel(channelId);
|
||||
if (channel != null && channel.Type == ChannelTypes.Text)
|
||||
{
|
||||
try
|
||||
{
|
||||
var msgs = await DiscordAPI.GetMessages(channel.Id, count, _httpOptions);
|
||||
return msgs.OrderBy(x => x.Timestamp)
|
||||
.Select(x =>
|
||||
{
|
||||
var msg = _messages.Update(x.Id, x.ChannelId, x);
|
||||
var user = msg.User;
|
||||
if (user != null)
|
||||
user.UpdateActivity(x.Timestamp);
|
||||
return msg;
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
catch { } //Bad Permissions?
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//Auth
|
||||
public async Task Connect(string email, string password)
|
||||
{
|
||||
_isStopping.Reset();
|
||||
|
||||
//Open websocket while we wait for login response
|
||||
Task socketTask = _webSocket.ConnectAsync(Endpoints.WebSocket_Hub, false, _httpOptions);
|
||||
var response = await DiscordAPI.Login(email, password, _httpOptions);
|
||||
_httpOptions.Token = response.Token;
|
||||
|
||||
//Wait for websocket to finish connecting, then send token
|
||||
await socketTask;
|
||||
_webSocket.Login(_httpOptions);
|
||||
|
||||
_isReady = true;
|
||||
}
|
||||
public async Task ConnectAnonymous(string username)
|
||||
{
|
||||
_isStopping.Reset();
|
||||
|
||||
//Open websocket while we wait for login response
|
||||
Task socketTask = _webSocket.ConnectAsync(Endpoints.WebSocket_Hub, false, _httpOptions);
|
||||
var response = await DiscordAPI.LoginAnonymous(username, _httpOptions);
|
||||
_httpOptions.Token = response.Token;
|
||||
|
||||
//Wait for websocket to finish connecting, then send token
|
||||
await socketTask;
|
||||
_webSocket.Login(_httpOptions);
|
||||
|
||||
_isReady = true;
|
||||
}
|
||||
public async Task Disconnect()
|
||||
{
|
||||
_isReady = false;
|
||||
_isStopping.Set();
|
||||
await _webSocket.DisconnectAsync();
|
||||
|
||||
_channels.Clear();
|
||||
_messages.Clear();
|
||||
_roles.Clear();
|
||||
_servers.Clear();
|
||||
_users.Clear();
|
||||
}
|
||||
|
||||
//Servers
|
||||
public async Task<Server> CreateServer(string name, string region)
|
||||
{
|
||||
CheckReady();
|
||||
var response = await DiscordAPI.CreateServer(name, region, _httpOptions);
|
||||
return _servers.Update(response.Id, response);
|
||||
}
|
||||
public Task<Server> LeaveServer(Server server)
|
||||
=> LeaveServer(server.Id);
|
||||
public async Task<Server> LeaveServer(string serverId)
|
||||
{
|
||||
CheckReady();
|
||||
try
|
||||
{
|
||||
await DiscordAPI.LeaveServer(serverId, _httpOptions);
|
||||
}
|
||||
catch (WebException ex) when ((ex.Response as HttpWebResponse)?.StatusCode == HttpStatusCode.NotFound) {}
|
||||
return _servers.Remove(serverId);
|
||||
}
|
||||
|
||||
//Channels
|
||||
public Task<Channel> CreateChannel(Server server, string name, string type)
|
||||
=> CreateChannel(server.Id, name, type);
|
||||
public async Task<Channel> CreateChannel(string serverId, string name, string type)
|
||||
{
|
||||
CheckReady();
|
||||
var response = await DiscordAPI.CreateChannel(serverId, name, type, _httpOptions);
|
||||
return _channels.Update(response.Id, serverId, response);
|
||||
}
|
||||
public Task<Channel> CreatePMChannel(User user)
|
||||
=> CreatePMChannel(user.Id);
|
||||
public async Task<Channel> CreatePMChannel(string recipientId)
|
||||
{
|
||||
CheckReady();
|
||||
var response = await DiscordAPI.CreatePMChannel(UserId, recipientId, _httpOptions);
|
||||
return _channels.Update(response.Id, response);
|
||||
}
|
||||
public Task<Channel> DestroyChannel(Channel channel)
|
||||
=> DestroyChannel(channel.Id);
|
||||
public async Task<Channel> DestroyChannel(string channelId)
|
||||
{
|
||||
CheckReady();
|
||||
try
|
||||
{
|
||||
var response = await DiscordAPI.DestroyChannel(channelId, _httpOptions);
|
||||
}
|
||||
catch (WebException ex) when ((ex.Response as HttpWebResponse)?.StatusCode == HttpStatusCode.NotFound) { }
|
||||
return _channels.Remove(channelId);
|
||||
}
|
||||
|
||||
//Bans
|
||||
public Task Ban(Server server, User user)
|
||||
=> Ban(server.Id, user.Id);
|
||||
public Task Ban(Server server, string userId)
|
||||
=> Ban(server.Id, userId);
|
||||
public Task Ban(string server, User user)
|
||||
=> Ban(server, user.Id);
|
||||
public Task Ban(string serverId, string userId)
|
||||
{
|
||||
CheckReady();
|
||||
return DiscordAPI.Ban(serverId, userId, _httpOptions);
|
||||
}
|
||||
public Task Unban(Server server, User user)
|
||||
=> Unban(server.Id, user.Id);
|
||||
public Task Unban(Server server, string userId)
|
||||
=> Unban(server.Id, userId);
|
||||
public Task Unban(string server, User user)
|
||||
=> Unban(server, user.Id);
|
||||
public async Task Unban(string serverId, string userId)
|
||||
{
|
||||
CheckReady();
|
||||
try
|
||||
{
|
||||
await DiscordAPI.Unban(serverId, userId, _httpOptions);
|
||||
}
|
||||
catch (WebException ex) when ((ex.Response as HttpWebResponse)?.StatusCode == HttpStatusCode.NotFound) { }
|
||||
}
|
||||
|
||||
//Invites
|
||||
public Task<Invite> CreateInvite(Server server, int maxAge, int maxUses, bool isTemporary, bool hasXkcdPass)
|
||||
{
|
||||
return CreateInvite(server.DefaultChannelId, maxAge, maxUses, isTemporary, hasXkcdPass);
|
||||
}
|
||||
public Task<Invite> CreateInvite(Channel channel, int maxAge, int maxUses, bool isTemporary, bool hasXkcdPass)
|
||||
{
|
||||
return CreateInvite(channel, maxAge, maxUses, isTemporary, hasXkcdPass);
|
||||
}
|
||||
public async Task<Invite> CreateInvite(string channelId, int maxAge, int maxUses, bool isTemporary, bool hasXkcdPass)
|
||||
{
|
||||
CheckReady();
|
||||
var response = await DiscordAPI.CreateInvite(channelId, maxAge, maxUses, isTemporary, hasXkcdPass, _httpOptions);
|
||||
_channels.Update(response.Channel.Id, response.Server.Id, response.Channel);
|
||||
_servers.Update(response.Server.Id, response.Server);
|
||||
_users.Update(response.Inviter.Id, response.Inviter);
|
||||
return new Invite(response.Code, response.XkcdPass, this)
|
||||
{
|
||||
ChannelId = response.Channel.Id,
|
||||
InviterId = response.Inviter.Id,
|
||||
ServerId = response.Server.Id,
|
||||
IsRevoked = response.IsRevoked,
|
||||
IsTemporary = response.IsTemporary,
|
||||
MaxAge = response.MaxAge,
|
||||
MaxUses = response.MaxUses,
|
||||
Uses = response.Uses
|
||||
};
|
||||
}
|
||||
public async Task<Invite> GetInvite(string id)
|
||||
{
|
||||
CheckReady();
|
||||
var response = await DiscordAPI.GetInvite(id, _httpOptions);
|
||||
return new Invite(response.Code, response.XkcdPass, this)
|
||||
{
|
||||
ChannelId = response.Channel.Id,
|
||||
InviterId = response.Inviter.Id,
|
||||
ServerId = response.Server.Id
|
||||
};
|
||||
}
|
||||
public Task AcceptInvite(Invite invite)
|
||||
{
|
||||
CheckReady();
|
||||
return DiscordAPI.AcceptInvite(invite.Code, _httpOptions);
|
||||
}
|
||||
public async Task AcceptInvite(string id)
|
||||
{
|
||||
CheckReady();
|
||||
//Check if this is a human-readable link and get its ID
|
||||
var response = await DiscordAPI.GetInvite(id, _httpOptions);
|
||||
await DiscordAPI.AcceptInvite(response.Code, _httpOptions);
|
||||
}
|
||||
public async Task DeleteInvite(string id)
|
||||
{
|
||||
CheckReady();
|
||||
try
|
||||
{
|
||||
//Check if this is a human-readable link and get its ID
|
||||
var response = await DiscordAPI.GetInvite(id, _httpOptions);
|
||||
await DiscordAPI.DeleteInvite(response.Code, _httpOptions);
|
||||
}
|
||||
catch (WebException ex) when ((ex.Response as HttpWebResponse)?.StatusCode == HttpStatusCode.NotFound) { }
|
||||
}
|
||||
|
||||
//Chat
|
||||
public Task<Message[]> SendMessage(Channel channel, string text)
|
||||
=> SendMessage(channel.Id, text, new string[0]);
|
||||
public Task<Message[]> SendMessage(string channelId, string text)
|
||||
=> SendMessage(channelId, text, new string[0]);
|
||||
public Task<Message[]> SendMessage(Channel channel, string text, string[] mentions)
|
||||
=> SendMessage(channel, text, mentions);
|
||||
public async Task<Message[]> SendMessage(string channelId, string text, string[] mentions)
|
||||
{
|
||||
CheckReady();
|
||||
|
||||
if (text.Length <= 2000)
|
||||
{
|
||||
var msg = await DiscordAPI.SendMessage(channelId, text, mentions, _httpOptions);
|
||||
return new Message[] { _messages.Update(msg.Id, channelId, msg) };
|
||||
}
|
||||
else
|
||||
{
|
||||
int blockCount = (int)Math.Ceiling(text.Length / (double)DiscordAPI.MaxMessageSize);
|
||||
Message[] result = new Message[blockCount];
|
||||
for (int i = 0; i < blockCount; i++)
|
||||
{
|
||||
int index = i * DiscordAPI.MaxMessageSize;
|
||||
var msg = await DiscordAPI.SendMessage(channelId, text.Substring(index, Math.Min(2000, text.Length - index)), mentions, _httpOptions);
|
||||
result[i] = _messages.Update(msg.Id, channelId, msg);
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public Task EditMessage(Message message, string text)
|
||||
=> EditMessage(message.ChannelId, message.Id, text, new string[0]);
|
||||
public Task EditMessage(Channel channel, string messageId, string text)
|
||||
=> EditMessage(channel.Id, messageId, text, new string[0]);
|
||||
public Task EditMessage(string channelId, string messageId, string text)
|
||||
=> EditMessage(channelId, messageId, text, new string[0]);
|
||||
public Task EditMessage(Message message, string text, string[] mentions)
|
||||
=> EditMessage(message.ChannelId, message.Id, text, mentions);
|
||||
public Task EditMessage(Channel channel, string messageId, string text, string[] mentions)
|
||||
=> EditMessage(channel.Id, messageId, text, mentions);
|
||||
public async Task EditMessage(string channelId, string messageId, string text, string[] mentions)
|
||||
{
|
||||
CheckReady();
|
||||
if (text.Length > DiscordAPI.MaxMessageSize)
|
||||
text = text.Substring(0, DiscordAPI.MaxMessageSize);
|
||||
|
||||
var msg = await DiscordAPI.EditMessage(channelId, messageId, text, mentions, _httpOptions);
|
||||
_messages.Update(msg.Id, channelId, msg);
|
||||
}
|
||||
|
||||
public Task DeleteMessage(Message msg)
|
||||
=> DeleteMessage(msg.ChannelId, msg.Id);
|
||||
public async Task<Message> DeleteMessage(string channelId, string msgId)
|
||||
{
|
||||
try
|
||||
{
|
||||
await DiscordAPI.DeleteMessage(channelId, msgId, _httpOptions);
|
||||
return _messages.Remove(msgId);
|
||||
}
|
||||
catch (WebException ex) when ((ex.Response as HttpWebResponse)?.StatusCode == HttpStatusCode.NotFound) { }
|
||||
catch (WebException ex) when ((ex.Response as HttpWebResponse)?.StatusCode == HttpStatusCode.InternalServerError) { } //TODO: Remove me - temporary fix for deleting nonexisting messages
|
||||
return null;
|
||||
}
|
||||
|
||||
//Voice
|
||||
public Task Mute(Server server, User user)
|
||||
=> Mute(server.Id, user.Id);
|
||||
public Task Mute(Server server, string userId)
|
||||
=> Mute(server.Id, userId);
|
||||
public Task Mute(string server, User user)
|
||||
=> Mute(server, user.Id);
|
||||
public Task Mute(string serverId, string userId)
|
||||
{
|
||||
CheckReady();
|
||||
return DiscordAPI.Mute(serverId, userId, _httpOptions);
|
||||
}
|
||||
|
||||
public Task Unmute(Server server, User user)
|
||||
=> Unmute(server.Id, user.Id);
|
||||
public Task Unmute(Server server, string userId)
|
||||
=> Unmute(server.Id, userId);
|
||||
public Task Unmute(string server, User user)
|
||||
=> Unmute(server, user.Id);
|
||||
public Task Unmute(string serverId, string userId)
|
||||
{
|
||||
CheckReady();
|
||||
return DiscordAPI.Unmute(serverId, userId, _httpOptions);
|
||||
}
|
||||
|
||||
public Task Deafen(Server server, User user)
|
||||
=> Deafen(server.Id, user.Id);
|
||||
public Task Deafen(Server server, string userId)
|
||||
=> Deafen(server.Id, userId);
|
||||
public Task Deafen(string server, User user)
|
||||
=> Deafen(server, user.Id);
|
||||
public Task Deafen(string serverId, string userId)
|
||||
{
|
||||
CheckReady();
|
||||
return DiscordAPI.Deafen(serverId, userId, _httpOptions);
|
||||
}
|
||||
|
||||
public Task Undeafen(Server server, User user)
|
||||
=> Undeafen(server.Id, user.Id);
|
||||
public Task Undeafen(Server server, string userId)
|
||||
=> Undeafen(server.Id, userId);
|
||||
public Task Undeafen(string server, User user)
|
||||
=> Undeafen(server, user.Id);
|
||||
public Task Undeafen(string serverId, string userId)
|
||||
{
|
||||
CheckReady();
|
||||
return DiscordAPI.Undeafen(serverId, userId, _httpOptions);
|
||||
}
|
||||
|
||||
private void CheckReady()
|
||||
{
|
||||
if (!_isReady)
|
||||
throw new InvalidOperationException("The client is not currently connected to Discord");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blocking call that will not return until client has been stopped. This is mainly intended for use in console applications.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public void Block()
|
||||
{
|
||||
_isStopping.Wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
46
src/Discord.Net/DiscordWebSocket.Events.cs
Normal file
46
src/Discord.Net/DiscordWebSocket.Events.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
internal partial class DiscordWebSocket
|
||||
{
|
||||
public event EventHandler Connected;
|
||||
private void RaiseConnected()
|
||||
{
|
||||
if (Connected != null)
|
||||
Connected(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public event EventHandler Disconnected;
|
||||
private void RaiseDisconnected()
|
||||
{
|
||||
if (Disconnected != null)
|
||||
Disconnected(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public event EventHandler<MessageEventArgs> GotEvent;
|
||||
public sealed class MessageEventArgs : EventArgs
|
||||
{
|
||||
public readonly string Type;
|
||||
public readonly JToken Event;
|
||||
internal MessageEventArgs(string type, JToken data)
|
||||
{
|
||||
Type = type;
|
||||
Event = data;
|
||||
}
|
||||
}
|
||||
private void RaiseGotEvent(string type, JToken payload)
|
||||
{
|
||||
if (GotEvent != null)
|
||||
GotEvent(this, new MessageEventArgs(type, payload));
|
||||
}
|
||||
|
||||
public event EventHandler<DiscordClient.LogMessageEventArgs> OnDebugMessage;
|
||||
private void RaiseOnDebugMessage(string message)
|
||||
{
|
||||
if (OnDebugMessage != null)
|
||||
OnDebugMessage(this, new DiscordClient.LogMessageEventArgs(message));
|
||||
}
|
||||
}
|
||||
}
|
||||
204
src/Discord.Net/DiscordWebSocket.cs
Normal file
204
src/Discord.Net/DiscordWebSocket.cs
Normal file
@@ -0,0 +1,204 @@
|
||||
using Discord.API.Models;
|
||||
using Discord.Helpers;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
internal sealed partial class DiscordWebSocket : IDisposable
|
||||
{
|
||||
private const int ReceiveChunkSize = 4096;
|
||||
private const int SendChunkSize = 4096;
|
||||
private const int ReadyTimeout = 5000; //Max time in milliseconds between connecting to Discord and receiving a READY event
|
||||
|
||||
private volatile ClientWebSocket _webSocket;
|
||||
private volatile CancellationTokenSource _cancelToken;
|
||||
private volatile Task _tasks;
|
||||
private ConcurrentQueue<byte[]> _sendQueue;
|
||||
private int _heartbeatInterval;
|
||||
private DateTime _lastHeartbeat;
|
||||
private AutoResetEvent _connectWaitOnLogin, _connectWaitOnLogin2;
|
||||
|
||||
public async Task ConnectAsync(string url, bool autoLogin, HttpOptions options)
|
||||
{
|
||||
await DisconnectAsync();
|
||||
|
||||
_connectWaitOnLogin = new AutoResetEvent(false);
|
||||
_connectWaitOnLogin2 = new AutoResetEvent(false);
|
||||
_sendQueue = new ConcurrentQueue<byte[]>();
|
||||
|
||||
_webSocket = new ClientWebSocket();
|
||||
_webSocket.Options.KeepAliveInterval = TimeSpan.Zero;
|
||||
|
||||
_cancelToken = new CancellationTokenSource();
|
||||
var cancelToken = _cancelToken.Token;
|
||||
|
||||
await _webSocket.ConnectAsync(new Uri(url), cancelToken);
|
||||
_tasks = Task.WhenAll(
|
||||
await Task.Factory.StartNew(ReceiveAsync, cancelToken, TaskCreationOptions.LongRunning, TaskScheduler.Default),
|
||||
await Task.Factory.StartNew(SendAsync, cancelToken, TaskCreationOptions.LongRunning, TaskScheduler.Default)
|
||||
).ContinueWith(x =>
|
||||
{
|
||||
//Do not clean up until both tasks have ended
|
||||
_heartbeatInterval = 0;
|
||||
_lastHeartbeat = DateTime.MinValue;
|
||||
_webSocket.Dispose();
|
||||
_webSocket = null;
|
||||
_cancelToken.Dispose();
|
||||
_cancelToken = null;
|
||||
_tasks = null;
|
||||
|
||||
RaiseDisconnected();
|
||||
});
|
||||
|
||||
if (autoLogin)
|
||||
Login(options);
|
||||
}
|
||||
public void Login(HttpOptions options)
|
||||
{
|
||||
WebSocketCommands.Login msg = new WebSocketCommands.Login();
|
||||
msg.Payload.Token = options.Token;
|
||||
msg.Payload.Properties["$os"] = "";
|
||||
msg.Payload.Properties["$browser"] = "";
|
||||
msg.Payload.Properties["$device"] = "Discord.Net";
|
||||
msg.Payload.Properties["$referrer"] = "";
|
||||
msg.Payload.Properties["$referring_domain"] = "";
|
||||
SendMessage(msg, _cancelToken.Token);
|
||||
|
||||
if (!_connectWaitOnLogin.WaitOne(ReadyTimeout)) //Pre-Event
|
||||
throw new Exception("No reply from Discord server");
|
||||
_connectWaitOnLogin2.WaitOne(); //Post-Event
|
||||
|
||||
RaiseConnected();
|
||||
}
|
||||
public async Task DisconnectAsync()
|
||||
{
|
||||
if (_tasks != null)
|
||||
{
|
||||
_cancelToken.Cancel();
|
||||
await _tasks;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ReceiveAsync()
|
||||
{
|
||||
var cancelToken = _cancelToken.Token;
|
||||
var buffer = new byte[ReceiveChunkSize];
|
||||
var builder = new StringBuilder();
|
||||
|
||||
try
|
||||
{
|
||||
while (_webSocket.State == WebSocketState.Open && !cancelToken.IsCancellationRequested)
|
||||
{
|
||||
WebSocketReceiveResult result;
|
||||
do
|
||||
{
|
||||
result = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), _cancelToken.Token);
|
||||
|
||||
if (result.MessageType == WebSocketMessageType.Close)
|
||||
{
|
||||
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
|
||||
return;
|
||||
}
|
||||
else
|
||||
builder.Append(Encoding.UTF8.GetString(buffer, 0, result.Count));
|
||||
|
||||
}
|
||||
while (!result.EndOfMessage);
|
||||
|
||||
var msg = JsonConvert.DeserializeObject<WebSocketMessage>(builder.ToString());
|
||||
switch (msg.Operation)
|
||||
{
|
||||
case 0:
|
||||
if (msg.Type == "READY")
|
||||
{
|
||||
var payload = (msg.Payload as JToken).ToObject<WebSocketEvents.Ready>();
|
||||
_heartbeatInterval = payload.HeartbeatInterval;
|
||||
SendMessage(new WebSocketCommands.UpdateStatus(), cancelToken);
|
||||
SendMessage(new WebSocketCommands.KeepAlive(), cancelToken);
|
||||
_connectWaitOnLogin.Set(); //Pre-Event
|
||||
}
|
||||
RaiseGotEvent(msg.Type, msg.Payload as JToken);
|
||||
if (msg.Type == "READY")
|
||||
_connectWaitOnLogin2.Set(); //Post-Event
|
||||
break;
|
||||
default:
|
||||
RaiseOnDebugMessage("Unknown WebSocket operation ID: " + msg.Operation);
|
||||
break;
|
||||
}
|
||||
|
||||
builder.Clear();
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
finally { _cancelToken.Cancel(); }
|
||||
}
|
||||
|
||||
private async Task SendAsync()
|
||||
{
|
||||
var cancelToken = _cancelToken.Token;
|
||||
try
|
||||
{
|
||||
byte[] bytes;
|
||||
while (_webSocket.State == WebSocketState.Open && !cancelToken.IsCancellationRequested)
|
||||
{
|
||||
if (_heartbeatInterval > 0)
|
||||
{
|
||||
DateTime now = DateTime.UtcNow;
|
||||
if ((now - _lastHeartbeat).TotalMilliseconds > _heartbeatInterval)
|
||||
{
|
||||
SendMessage(new WebSocketCommands.KeepAlive(), cancelToken);
|
||||
_lastHeartbeat = now;
|
||||
}
|
||||
}
|
||||
while (_sendQueue.TryDequeue(out bytes))
|
||||
{
|
||||
var frameCount = (int)Math.Ceiling((double)bytes.Length / SendChunkSize);
|
||||
|
||||
int offset = 0;
|
||||
for (var i = 0; i < frameCount; i++, offset += SendChunkSize)
|
||||
{
|
||||
bool isLast = i == (frameCount - 1);
|
||||
|
||||
int count;
|
||||
if (isLast)
|
||||
count = bytes.Length - (i * SendChunkSize);
|
||||
else
|
||||
count = SendChunkSize;
|
||||
|
||||
await _webSocket.SendAsync(new ArraySegment<byte>(bytes, offset, count), WebSocketMessageType.Text, isLast, cancelToken);
|
||||
}
|
||||
}
|
||||
await Task.Delay(100);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
finally { _cancelToken.Cancel(); }
|
||||
}
|
||||
|
||||
private void SendMessage(object frame, CancellationToken token)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(frame));
|
||||
_sendQueue.Enqueue(bytes);
|
||||
}
|
||||
|
||||
#region IDisposable Support
|
||||
private bool _isDisposed = false;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_isDisposed)
|
||||
{
|
||||
DisconnectAsync().Wait();
|
||||
_isDisposed = true;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
90
src/Discord.Net/Helpers/AsyncCache.cs
Normal file
90
src/Discord.Net/Helpers/AsyncCache.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Discord.Helpers
|
||||
{
|
||||
public class AsyncCache<TValue, TModel> : IEnumerable<TValue>
|
||||
where TValue : class
|
||||
where TModel : class
|
||||
{
|
||||
protected readonly ConcurrentDictionary<string, TValue> _dictionary;
|
||||
private readonly Func<string, string, TValue> _onCreate;
|
||||
private readonly Action<TValue, TModel> _onUpdate;
|
||||
private readonly Action<TValue> _onRemove;
|
||||
|
||||
public AsyncCache(Func<string, string, TValue> onCreate, Action<TValue, TModel> onUpdate, Action<TValue> onRemove)
|
||||
{
|
||||
_dictionary = new ConcurrentDictionary<string, TValue>();
|
||||
_onCreate = onCreate;
|
||||
_onUpdate = onUpdate;
|
||||
_onRemove = onRemove;
|
||||
}
|
||||
|
||||
public TValue this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (key == null)
|
||||
return null;
|
||||
TValue value = null;
|
||||
_dictionary.TryGetValue(key, out value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public TValue Update(string key, TModel model)
|
||||
{
|
||||
return Update(key, null, model);
|
||||
}
|
||||
public TValue Update(string key, string parentKey, TModel model)
|
||||
{
|
||||
if (key == null)
|
||||
return null;
|
||||
while (true)
|
||||
{
|
||||
bool isNew;
|
||||
TValue value;
|
||||
isNew = !_dictionary.TryGetValue(key, out value);
|
||||
if (isNew)
|
||||
value = _onCreate(key, parentKey);
|
||||
_onUpdate(value, model);
|
||||
if (isNew)
|
||||
{
|
||||
//If this fails, repeat as an update instead of an add
|
||||
if (_dictionary.TryAdd(key, value))
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
_dictionary[key] = value;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TValue Remove(string key)
|
||||
{
|
||||
TValue value = null;
|
||||
if (_dictionary.TryRemove(key, out value))
|
||||
return value;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_dictionary.Clear();
|
||||
}
|
||||
|
||||
public IEnumerator<TValue> GetEnumerator()
|
||||
{
|
||||
return _dictionary.Values.GetEnumerator();
|
||||
}
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return _dictionary.Values.GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
205
src/Discord.Net/Helpers/Http.cs
Normal file
205
src/Discord.Net/Helpers/Http.cs
Normal file
@@ -0,0 +1,205 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord.Helpers
|
||||
{
|
||||
internal class HttpOptions
|
||||
{
|
||||
public readonly string UserAgent;
|
||||
public string Token;
|
||||
|
||||
public HttpOptions(string userAgent)
|
||||
{
|
||||
UserAgent = userAgent;
|
||||
}
|
||||
}
|
||||
|
||||
internal static class Http
|
||||
{
|
||||
#if DEBUG
|
||||
private const bool _isDebug = true;
|
||||
#else
|
||||
private const bool _isDebug = false;
|
||||
#endif
|
||||
|
||||
internal static Task<ResponseT> Get<ResponseT>(string path, HttpOptions options)
|
||||
where ResponseT : class
|
||||
=> Send<ResponseT>("GET", path, null, options);
|
||||
internal static Task<string> Get(string path, HttpOptions options)
|
||||
=> Send("GET", path, null, options);
|
||||
|
||||
internal static Task<ResponseT> Post<ResponseT>(string path, object data, HttpOptions options)
|
||||
where ResponseT : class
|
||||
=> Send<ResponseT>("POST", path, data, options);
|
||||
internal static Task<string> Post(string path, object data, HttpOptions options)
|
||||
=> Send("POST", path, data, options);
|
||||
internal static Task<ResponseT> Post<ResponseT>(string path, HttpOptions options)
|
||||
where ResponseT : class
|
||||
=> Send<ResponseT>("POST", path, null, options);
|
||||
internal static Task<string> Post(string path, HttpOptions options)
|
||||
=> Send("POST", path, null, options);
|
||||
|
||||
internal static Task<ResponseT> Put<ResponseT>(string path, object data, HttpOptions options)
|
||||
where ResponseT : class
|
||||
=> Send<ResponseT>("PUT", path, data, options);
|
||||
internal static Task<string> Put(string path, object data, HttpOptions options)
|
||||
=> Send("PUT", path, data, options);
|
||||
internal static Task<ResponseT> Put<ResponseT>(string path, HttpOptions options)
|
||||
where ResponseT : class
|
||||
=> Send<ResponseT>("PUT", path, null, options);
|
||||
internal static Task<string> Put(string path, HttpOptions options)
|
||||
=> Send("PUT", path, null, options);
|
||||
|
||||
internal static Task<ResponseT> Patch<ResponseT>(string path, object data, HttpOptions options)
|
||||
where ResponseT : class
|
||||
=> Send<ResponseT>("PATCH", path, data, options);
|
||||
internal static Task<string> Patch(string path, object data, HttpOptions options)
|
||||
=> Send("PATCH", path, data, options);
|
||||
internal static Task<ResponseT> Patch<ResponseT>(string path, HttpOptions options)
|
||||
where ResponseT : class
|
||||
=> Send<ResponseT>("PATCH", path, null, options);
|
||||
internal static Task<string> Patch(string path, HttpOptions options)
|
||||
=> Send("PATCH", path, null, options);
|
||||
|
||||
internal static Task<ResponseT> Delete<ResponseT>(string path, object data, HttpOptions options)
|
||||
where ResponseT : class
|
||||
=> Send<ResponseT>("DELETE", path, data, options);
|
||||
internal static Task<string> Delete(string path, object data, HttpOptions options)
|
||||
=> Send("DELETE", path, data, options);
|
||||
internal static Task<ResponseT> Delete<ResponseT>(string path, HttpOptions options)
|
||||
where ResponseT : class
|
||||
=> Send<ResponseT>("DELETE", path, null, options);
|
||||
internal static Task<string> Delete(string path, HttpOptions options)
|
||||
=> Send("DELETE", path, null, options);
|
||||
|
||||
internal static async Task<ResponseT> Send<ResponseT>(string method, string path, object data, HttpOptions options)
|
||||
where ResponseT : class
|
||||
{
|
||||
string requestJson = data != null ? JsonConvert.SerializeObject(data) : null;
|
||||
string responseJson = await SendRequest(method, path, requestJson, options, true);
|
||||
var response = JsonConvert.DeserializeObject<ResponseT>(responseJson);
|
||||
#if DEBUG
|
||||
CheckResponse(responseJson, response);
|
||||
#endif
|
||||
return response;
|
||||
}
|
||||
internal static async Task<string> Send(string method, string path, object data, HttpOptions options)
|
||||
{
|
||||
string requestJson = data != null ? JsonConvert.SerializeObject(data) : null;
|
||||
string responseJson = await SendRequest(method, path, requestJson, options, _isDebug);
|
||||
#if DEBUG
|
||||
CheckEmptyResponse(responseJson);
|
||||
#endif
|
||||
return responseJson;
|
||||
}
|
||||
|
||||
private static async Task<string> SendRequest(string method, string path, string data, HttpOptions options, bool hasResponse)
|
||||
{
|
||||
//Create Request
|
||||
HttpWebRequest request = WebRequest.CreateHttp(path);
|
||||
request.Accept = "*/*";
|
||||
request.Method = method;
|
||||
request.Proxy = null;
|
||||
request.Headers[HttpRequestHeader.AcceptLanguage] = "en-US;q=0.8";
|
||||
request.Headers[HttpRequestHeader.AcceptEncoding] = "gzip, deflate";
|
||||
request.Headers[HttpRequestHeader.UserAgent] = options.UserAgent;
|
||||
request.Headers[HttpRequestHeader.Authorization] = options.Token;
|
||||
//request.UserAgent = options.UserAgent;
|
||||
|
||||
//Add Payload
|
||||
if (data != null)
|
||||
{
|
||||
byte[] buffer = Encoding.UTF8.GetBytes(data);
|
||||
using (var payload = await request.GetRequestStreamAsync())
|
||||
payload.Write(buffer, 0, buffer.Length);
|
||||
request.ContentType = "application/json";
|
||||
}
|
||||
|
||||
//Get Response
|
||||
using (var response = (HttpWebResponse)(await request.GetResponseAsync()))
|
||||
{
|
||||
if (hasResponse)
|
||||
{
|
||||
using (var stream = response.GetResponseStream())
|
||||
using (var reader = new BinaryReader(stream))
|
||||
using (var largeBuffer = new MemoryStream())
|
||||
{
|
||||
//Read the response in small chunks and add them to a larger buffer.
|
||||
//ContentLength isn't always provided, so this is safer.
|
||||
int bytesRead = 0;
|
||||
byte[] smallBuffer = new byte[4096];
|
||||
while ((bytesRead = reader.Read(smallBuffer, 0, smallBuffer.Length)) > 0)
|
||||
largeBuffer.Write(smallBuffer, 0, bytesRead);
|
||||
|
||||
//Do we need to decompress?
|
||||
string encoding = response.Headers[HttpResponseHeader.ContentEncoding];
|
||||
if (!string.IsNullOrEmpty(encoding))
|
||||
{
|
||||
largeBuffer.Position = 0;
|
||||
using (var decoder = GetDecoder(encoding, largeBuffer))
|
||||
using (var decodedStream = new MemoryStream())
|
||||
{
|
||||
decoder.CopyTo(decodedStream);
|
||||
#if !DOTNET
|
||||
return Encoding.UTF8.GetString(decodedStream.GetBuffer(), 0, (int)decodedStream.Length);
|
||||
#else
|
||||
ArraySegment<byte> buffer;
|
||||
if (!decodedStream.TryGetBuffer(out buffer))
|
||||
throw new InvalidOperationException("Failed to get response buffer.");
|
||||
return Encoding.UTF8.GetString(buffer.Array, buffer.Offset, (int)decodedStream.Length);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
#if !DOTNET
|
||||
return Encoding.UTF8.GetString(largeBuffer.GetBuffer(), 0, (int)largeBuffer.Length);
|
||||
#else
|
||||
ArraySegment<byte> buffer;
|
||||
if (!largeBuffer.TryGetBuffer(out buffer))
|
||||
throw new InvalidOperationException("Failed to get response buffer.");
|
||||
return Encoding.UTF8.GetString(buffer.Array, buffer.Offset, (int)largeBuffer.Length);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Stream GetDecoder(string contentEncoding, MemoryStream encodedStream)
|
||||
{
|
||||
switch (contentEncoding)
|
||||
{
|
||||
case "gzip":
|
||||
return new GZipStream(encodedStream, CompressionMode.Decompress, true);
|
||||
case "deflate":
|
||||
return new DeflateStream(encodedStream, CompressionMode.Decompress, true);
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException("Unknown encoding: " + contentEncoding);
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
private static void CheckResponse<T>(string json, T obj)
|
||||
{
|
||||
/*JToken token = JToken.Parse(json);
|
||||
JToken token2 = JToken.FromObject(obj);
|
||||
if (!JToken.DeepEquals(token, token2))
|
||||
throw new Exception("API check failed: Objects do not match.");*/
|
||||
}
|
||||
|
||||
private static void CheckEmptyResponse(string json)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(json))
|
||||
throw new Exception("API check failed: Response is not empty.");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
34
src/Discord.Net/Invite.cs
Normal file
34
src/Discord.Net/Invite.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public sealed class Invite
|
||||
{
|
||||
private readonly DiscordClient _client;
|
||||
|
||||
public int MaxAge, Uses, MaxUses;
|
||||
public bool IsRevoked, IsTemporary;
|
||||
public readonly string Code, XkcdPass;
|
||||
|
||||
public string Url => API.Endpoints.InviteUrl(XkcdPass ?? Code);
|
||||
|
||||
public string InviterId { get; internal set; }
|
||||
[JsonIgnore]
|
||||
public User Inviter => _client.GetUser(InviterId);
|
||||
|
||||
public string ServerId { get; internal set; }
|
||||
[JsonIgnore]
|
||||
public Server Server => _client.GetServer(ServerId);
|
||||
|
||||
public string ChannelId { get; internal set; }
|
||||
[JsonIgnore]
|
||||
public Channel Channel => _client.GetChannel(ChannelId);
|
||||
|
||||
internal Invite(string code, string xkcdPass, DiscordClient client)
|
||||
{
|
||||
Code = code;
|
||||
XkcdPass = xkcdPass;
|
||||
_client = client;
|
||||
}
|
||||
}
|
||||
}
|
||||
33
src/Discord.Net/Membership.cs
Normal file
33
src/Discord.Net/Membership.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public class Membership
|
||||
{
|
||||
private readonly DiscordClient _client;
|
||||
|
||||
public DateTime JoinedAt;
|
||||
|
||||
public bool IsMuted { get; internal set; }
|
||||
public bool IsDeafened { get; internal set; }
|
||||
|
||||
public string ServerId { get; }
|
||||
public Server Server => _client.GetServer(ServerId);
|
||||
|
||||
public string UserId { get; }
|
||||
public User User => _client.GetUser(UserId);
|
||||
|
||||
public string[] RoleIds { get; internal set; }
|
||||
public IEnumerable<Role> Roles => RoleIds.Select(x => _client.GetRole(x));
|
||||
|
||||
public Membership(string serverId, string userId, DateTime joinedAt, DiscordClient client)
|
||||
{
|
||||
ServerId = serverId;
|
||||
UserId = userId;
|
||||
_client = client;
|
||||
JoinedAt = joinedAt;
|
||||
}
|
||||
}
|
||||
}
|
||||
47
src/Discord.Net/Message.cs
Normal file
47
src/Discord.Net/Message.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public sealed class Message
|
||||
{
|
||||
private readonly DiscordClient _client;
|
||||
|
||||
public string Id { get; }
|
||||
|
||||
public bool IsMentioningEveryone { get; internal set; }
|
||||
public bool IsTTS { get; internal set; }
|
||||
public string Text { get; internal set; }
|
||||
public DateTime Timestamp { get; internal set; }
|
||||
|
||||
public string[] MentionIds { get; internal set; }
|
||||
[JsonIgnore]
|
||||
public IEnumerable<User> Mentions => MentionIds.Select(x => _client.GetUser(x)).Where(x => x != null);
|
||||
|
||||
public string ChannelId { get; }
|
||||
[JsonIgnore]
|
||||
public Channel Channel => _client.GetChannel(ChannelId);
|
||||
|
||||
public string UserId { get; internal set; }
|
||||
[JsonIgnore]
|
||||
public User User => _client.GetUser(UserId);
|
||||
|
||||
//Not Implemented
|
||||
public object[] Attachments { get; internal set; }
|
||||
public object[] Embeds { get; internal set; }
|
||||
|
||||
internal Message(string id, string channelId, DiscordClient client)
|
||||
{
|
||||
Id = id;
|
||||
ChannelId = channelId;
|
||||
_client = client;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return User.ToString() + ": " + Text;
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/Discord.Net/Regions.cs
Normal file
12
src/Discord.Net/Regions.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
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";
|
||||
}
|
||||
}
|
||||
63
src/Discord.Net/Role.cs
Normal file
63
src/Discord.Net/Role.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public sealed class Role
|
||||
{
|
||||
public sealed class PackedPermissions
|
||||
{
|
||||
private uint _rawValue;
|
||||
public uint RawValue { get { return _rawValue; } set { _rawValue = value; } }
|
||||
|
||||
public PackedPermissions() { }
|
||||
|
||||
public bool General_CreateInstantInvite => ((_rawValue >> 0) & 0x1) == 1;
|
||||
public bool General_BanMembers => ((_rawValue >> 1) & 0x1) == 1;
|
||||
public bool General_KickMembers => ((_rawValue >> 2) & 0x1) == 1;
|
||||
public bool General_ManageRoles => ((_rawValue >> 3) & 0x1) == 1;
|
||||
public bool General_ManageChannels => ((_rawValue >> 4) & 0x1) == 1;
|
||||
public bool General_ManageServer => ((_rawValue >> 5) & 0x1) == 1;
|
||||
//4 Unused
|
||||
public bool Text_ReadMessages => ((_rawValue >> 10) & 0x1) == 1;
|
||||
public bool Text_SendMessages => ((_rawValue >> 11) & 0x1) == 1;
|
||||
public bool Text_SendTTSMessages => ((_rawValue >> 12) & 0x1) == 1;
|
||||
public bool Text_ManageMessages => ((_rawValue >> 13) & 0x1) == 1;
|
||||
public bool Text_EmbedLinks => ((_rawValue >> 14) & 0x1) == 1;
|
||||
public bool Text_AttachFiles => ((_rawValue >> 15) & 0x1) == 1;
|
||||
public bool Text_ReadMessageHistory => ((_rawValue >> 16) & 0x1) == 1;
|
||||
public bool Text_MentionEveryone => ((_rawValue >> 17) & 0x1) == 1;
|
||||
//2 Unused
|
||||
public bool Voice_Connect => ((_rawValue >> 20) & 0x1) == 1;
|
||||
public bool Voice_Speak => ((_rawValue >> 21) & 0x1) == 1;
|
||||
public bool Voice_MuteMembers => ((_rawValue >> 22) & 0x1) == 1;
|
||||
public bool Voice_DeafenMembers => ((_rawValue >> 23) & 0x1) == 1;
|
||||
public bool Voice_MoveMembers => ((_rawValue >> 24) & 0x1) == 1;
|
||||
public bool Voice_UseVoiceActivation => ((_rawValue >> 25) & 0x1) == 1;
|
||||
//6 Unused
|
||||
}
|
||||
|
||||
private readonly DiscordClient _client;
|
||||
|
||||
public string Id { get; }
|
||||
public string Name { get; internal set; }
|
||||
|
||||
public PackedPermissions Permissions { get; }
|
||||
|
||||
public string ServerId { get; }
|
||||
[JsonIgnore]
|
||||
public Server Server => _client.GetServer(ServerId);
|
||||
|
||||
internal Role(string id, string serverId, DiscordClient client)
|
||||
{
|
||||
Permissions = new PackedPermissions();
|
||||
Id = id;
|
||||
ServerId = serverId;
|
||||
_client = client;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
82
src/Discord.Net/Server.cs
Normal file
82
src/Discord.Net/Server.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public sealed class Server
|
||||
{
|
||||
private readonly DiscordClient _client;
|
||||
|
||||
public string Id { get; }
|
||||
public string Name { get; internal set; }
|
||||
|
||||
public string AFKChannelId { get; internal set; }
|
||||
public int AFKTimeout { get; internal set; }
|
||||
public DateTime JoinedAt { get; internal set; }
|
||||
public string Region { get; internal set; }
|
||||
|
||||
public string OwnerId { get; internal set; }
|
||||
public User Owner => _client.GetUser(OwnerId);
|
||||
public bool IsOwner => _client.UserId == OwnerId;
|
||||
|
||||
public string DefaultChannelId => Id;
|
||||
public Channel DefaultChannel =>_client.GetChannel(DefaultChannelId);
|
||||
|
||||
internal ConcurrentDictionary<string, Membership> _members;
|
||||
public IEnumerable<Membership> Members => _members.Values;
|
||||
|
||||
internal ConcurrentDictionary<string, bool> _bans;
|
||||
public IEnumerable<User> Bans => _bans.Keys.Select(x => _client.GetUser(x));
|
||||
|
||||
public IEnumerable<Channel> Channels => _client.Channels.Where(x => x.ServerId == Id);
|
||||
public IEnumerable<Role> Roles => _client.Roles.Where(x => x.ServerId == Id);
|
||||
|
||||
//Not Implemented
|
||||
public object Presence { get; internal set; }
|
||||
public object[] VoiceStates { get; internal set; }
|
||||
|
||||
internal Server(string id, DiscordClient client)
|
||||
{
|
||||
Id = id;
|
||||
_client = client;
|
||||
_members = new ConcurrentDictionary<string, Membership>();
|
||||
_bans = new ConcurrentDictionary<string, bool>();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
|
||||
internal void AddMember(Membership membership)
|
||||
{
|
||||
_members[membership.UserId] = membership;
|
||||
}
|
||||
internal Membership RemoveMember(string userId)
|
||||
{
|
||||
Membership result = null;
|
||||
_members.TryRemove(userId, out result);
|
||||
return result;
|
||||
}
|
||||
public Membership GetMembership(User user)
|
||||
=> GetMembership(user.Id);
|
||||
public Membership GetMembership(string userId)
|
||||
{
|
||||
Membership result = null;
|
||||
_members.TryGetValue(userId, out result);
|
||||
return result;
|
||||
}
|
||||
|
||||
internal void AddBan(string banId)
|
||||
{
|
||||
_bans.TryAdd(banId, true);
|
||||
}
|
||||
internal bool RemoveBan(string banId)
|
||||
{
|
||||
bool ignored;
|
||||
return _bans.TryRemove(banId, out ignored);
|
||||
}
|
||||
}
|
||||
}
|
||||
44
src/Discord.Net/User.cs
Normal file
44
src/Discord.Net/User.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Discord.API;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public sealed class User
|
||||
{
|
||||
private readonly DiscordClient _client;
|
||||
|
||||
public string Id { get; }
|
||||
public string Name { get; internal set; }
|
||||
|
||||
public string AvatarId { get; internal set; }
|
||||
public string AvatarUrl => Endpoints.UserAvatar(Id, AvatarId);
|
||||
public string Discriminator { get; internal set; }
|
||||
[JsonIgnore]
|
||||
public string Email { get; internal set; }
|
||||
[JsonIgnore]
|
||||
public bool IsVerified { get; internal set; } = true;
|
||||
public string GameId { get; internal set; }
|
||||
public string Status { get; internal set; }
|
||||
|
||||
public DateTime LastActivity { get; private set; }
|
||||
|
||||
internal User(string id, DiscordClient client)
|
||||
{
|
||||
Id = id;
|
||||
_client = client;
|
||||
LastActivity = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
internal void UpdateActivity(DateTime activity)
|
||||
{
|
||||
if (activity > LastActivity)
|
||||
LastActivity = activity;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
4
src/Discord.Net/packages.config
Normal file
4
src/Discord.Net/packages.config
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Newtonsoft.Json" version="7.0.1" targetFramework="net45" />
|
||||
</packages>
|
||||
28
src/Discord.Net/project.json
Normal file
28
src/Discord.Net/project.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"version": "0.3.3-*",
|
||||
"description": "An unofficial .Net API wrapper for the Discord client.",
|
||||
"authors": [ "RogueException" ],
|
||||
"tags": [ "discord", "discordapp" ],
|
||||
"projectUrl": "https://github.com/RogueException/Discord.Net",
|
||||
"licenseUrl": "http://opensource.org/licenses/MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/RogueException/Discord.Net"
|
||||
},
|
||||
"compilationOptions": {
|
||||
"warningsAsErrors": true
|
||||
},
|
||||
"frameworks": {
|
||||
"dotnet": {
|
||||
"dependencies": {
|
||||
"System.Collections.Concurrent": "4.0.10",
|
||||
"System.IO.Compression": "4.0.0-beta-23109",
|
||||
"System.Net.Requests": "4.0.10-beta-23109",
|
||||
"System.Net.WebSockets.Client": "4.0.0-beta-23109",
|
||||
"System.Runtime": "4.0.20",
|
||||
"Microsoft.CSharp": "4.0.0",
|
||||
"Newtonsoft.Json": "7.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user