@@ -84,9 +84,18 @@ | |||||
<Compile Include="..\Discord.Net.Audio\Sodium\SecretBox.cs"> | <Compile Include="..\Discord.Net.Audio\Sodium\SecretBox.cs"> | ||||
<Link>Sodium\SecretBox.cs</Link> | <Link>Sodium\SecretBox.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net.Audio\UserIsTalkingEventArgs.cs"> | |||||
<Link>UserIsTalkingEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net.Audio\VoiceBuffer.cs"> | <Compile Include="..\Discord.Net.Audio\VoiceBuffer.cs"> | ||||
<Link>VoiceBuffer.cs</Link> | <Link>VoiceBuffer.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net.Audio\VoiceDisconnectedEventArgs.cs"> | |||||
<Link>VoiceDisconnectedEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net.Audio\VoicePacketEventArgs.cs"> | |||||
<Link>VoicePacketEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="Properties\AssemblyInfo.cs" /> | <Compile Include="Properties\AssemblyInfo.cs" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
@@ -1,49 +1,10 @@ | |||||
using Discord.Net.WebSockets; | |||||
using System; | |||||
using System; | |||||
using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
using System.Linq; | using System.Linq; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
namespace Discord.Audio | namespace Discord.Audio | ||||
{ | { | ||||
public class VoiceDisconnectedEventArgs : DisconnectedEventArgs | |||||
{ | |||||
public readonly ulong ServerId; | |||||
public VoiceDisconnectedEventArgs(ulong serverId, DisconnectedEventArgs e) | |||||
: base(e.WasUnexpected, e.Exception) | |||||
{ | |||||
ServerId = serverId; | |||||
} | |||||
} | |||||
public class UserIsSpeakingEventArgs : UserEventArgs | |||||
{ | |||||
public readonly bool IsSpeaking; | |||||
public UserIsSpeakingEventArgs(User user, bool isSpeaking) | |||||
: base(user) | |||||
{ | |||||
IsSpeaking = isSpeaking; | |||||
} | |||||
} | |||||
public class VoicePacketEventArgs : EventArgs | |||||
{ | |||||
public readonly ulong UserId; | |||||
public readonly ulong ChannelId; | |||||
public readonly byte[] Buffer; | |||||
public readonly int Offset; | |||||
public readonly int Count; | |||||
public VoicePacketEventArgs(ulong userId, ulong channelId, byte[] buffer, int offset, int count) | |||||
{ | |||||
UserId = userId; | |||||
ChannelId = channelId; | |||||
Buffer = buffer; | |||||
Offset = offset; | |||||
Count = count; | |||||
} | |||||
} | |||||
public class AudioService : IService | public class AudioService : IService | ||||
{ | { | ||||
private AudioClient _defaultClient; | private AudioClient _defaultClient; | ||||
@@ -51,46 +12,33 @@ namespace Discord.Audio | |||||
private ConcurrentDictionary<User, bool> _talkingUsers; | private ConcurrentDictionary<User, bool> _talkingUsers; | ||||
//private int _nextClientId; | //private int _nextClientId; | ||||
internal DiscordClient Client => _client; | |||||
private DiscordClient _client; | |||||
internal DiscordClient Client { get; private set; } | |||||
public AudioServiceConfig Config { get; } | |||||
public AudioServiceConfig Config => _config; | |||||
private readonly AudioServiceConfig _config; | |||||
public event EventHandler Connected = delegate { }; | |||||
public event EventHandler<VoiceDisconnectedEventArgs> Disconnected = delegate { }; | |||||
public event EventHandler<VoicePacketEventArgs> PacketReceived = delegate { }; | |||||
public event EventHandler<UserIsSpeakingEventArgs> UserIsSpeakingUpdated = delegate { }; | |||||
public event EventHandler Connected; | |||||
private void RaiseConnected() | |||||
{ | |||||
if (Connected != null) | |||||
Connected(this, EventArgs.Empty); | |||||
} | |||||
public event EventHandler<VoiceDisconnectedEventArgs> Disconnected; | |||||
private void RaiseDisconnected(ulong serverId, DisconnectedEventArgs e) | |||||
{ | |||||
if (Disconnected != null) | |||||
Disconnected(this, new VoiceDisconnectedEventArgs(serverId, e)); | |||||
} | |||||
public event EventHandler<VoicePacketEventArgs> OnPacket; | |||||
internal void RaiseOnPacket(VoicePacketEventArgs e) | |||||
{ | |||||
if (OnPacket != null) | |||||
OnPacket(this, e); | |||||
} | |||||
public event EventHandler<UserIsSpeakingEventArgs> UserIsSpeakingUpdated; | |||||
private void RaiseUserIsSpeakingUpdated(User user, bool isSpeaking) | |||||
{ | |||||
if (UserIsSpeakingUpdated != null) | |||||
UserIsSpeakingUpdated(this, new UserIsSpeakingEventArgs(user, isSpeaking)); | |||||
} | |||||
private void OnConnected() | |||||
=> Connected(this, EventArgs.Empty); | |||||
private void OnDisconnected(ulong serverId, bool wasUnexpected, Exception ex) | |||||
=> Disconnected(this, new VoiceDisconnectedEventArgs(serverId, wasUnexpected, ex)); | |||||
internal void OnPacketReceived(VoicePacketEventArgs e) | |||||
=> PacketReceived(this, e); | |||||
private void OnUserIsSpeakingUpdated(User user, bool isSpeaking) | |||||
=> UserIsSpeakingUpdated(this, new UserIsSpeakingEventArgs(user, isSpeaking)); | |||||
public AudioService(AudioServiceConfig config) | public AudioService(AudioServiceConfig config) | ||||
{ | { | ||||
_config = config; | |||||
_config.Lock(); | |||||
Config = config; | |||||
} | } | ||||
public void Install(DiscordClient client) | public void Install(DiscordClient client) | ||||
{ | { | ||||
_client = client; | |||||
if (Config.EnableMultiserver) | |||||
Client = client; | |||||
Config.Lock(); | |||||
if (Config.EnableMultiserver) | |||||
_voiceClients = new ConcurrentDictionary<ulong, IAudioClient>(); | _voiceClients = new ConcurrentDictionary<ulong, IAudioClient>(); | ||||
else | else | ||||
{ | { | ||||
@@ -113,7 +61,7 @@ namespace Discord.Audio | |||||
{ | { | ||||
bool ignored; | bool ignored; | ||||
if (_talkingUsers.TryRemove(member.Key, out ignored)) | if (_talkingUsers.TryRemove(member.Key, out ignored)) | ||||
RaiseUserIsSpeakingUpdated(member.Key, false); | |||||
OnUserIsSpeakingUpdated(member.Key, false); | |||||
} | } | ||||
}; | }; | ||||
} | } | ||||
@@ -0,0 +1,13 @@ | |||||
namespace Discord | |||||
{ | |||||
public class UserIsSpeakingEventArgs : UserEventArgs | |||||
{ | |||||
public bool IsSpeaking { get; } | |||||
public UserIsSpeakingEventArgs(User user, bool isSpeaking) | |||||
: base(user) | |||||
{ | |||||
IsSpeaking = isSpeaking; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,15 @@ | |||||
using System; | |||||
namespace Discord | |||||
{ | |||||
public class VoiceDisconnectedEventArgs : DisconnectedEventArgs | |||||
{ | |||||
public ulong ServerId { get; } | |||||
public VoiceDisconnectedEventArgs(ulong serverId, bool wasUnexpected, Exception ex) | |||||
: base(wasUnexpected, ex) | |||||
{ | |||||
ServerId = serverId; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,22 @@ | |||||
using System; | |||||
namespace Discord | |||||
{ | |||||
public class VoicePacketEventArgs : EventArgs | |||||
{ | |||||
public ulong UserId { get; } | |||||
public ulong ChannelId { get; } | |||||
public byte[] Buffer { get; } | |||||
public int Offset { get; } | |||||
public int Count { get; } | |||||
public VoicePacketEventArgs(ulong userId, ulong channelId, byte[] buffer, int offset, int count) | |||||
{ | |||||
UserId = userId; | |||||
ChannelId = channelId; | |||||
Buffer = buffer; | |||||
Offset = offset; | |||||
Count = count; | |||||
} | |||||
} | |||||
} |
@@ -45,21 +45,27 @@ | |||||
<Compile Include="..\Discord.Net.Commands\CommandBuilder.cs"> | <Compile Include="..\Discord.Net.Commands\CommandBuilder.cs"> | ||||
<Link>CommandBuilder.cs</Link> | <Link>CommandBuilder.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net.Commands\CommandErrorEventArgs.cs"> | |||||
<Link>CommandErrorEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net.Commands\CommandEventArgs.cs"> | |||||
<Link>CommandEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net.Commands\CommandExtensions.cs"> | <Compile Include="..\Discord.Net.Commands\CommandExtensions.cs"> | ||||
<Link>CommandExtensions.cs</Link> | <Link>CommandExtensions.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net.Commands\CommandMap.cs"> | <Compile Include="..\Discord.Net.Commands\CommandMap.cs"> | ||||
<Link>CommandMap.cs</Link> | <Link>CommandMap.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net.Commands\CommandParameter.cs"> | |||||
<Link>CommandParameter.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net.Commands\CommandParser.cs"> | <Compile Include="..\Discord.Net.Commands\CommandParser.cs"> | ||||
<Link>CommandParser.cs</Link> | <Link>CommandParser.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net.Commands\CommandService.cs"> | <Compile Include="..\Discord.Net.Commands\CommandService.cs"> | ||||
<Link>CommandService.cs</Link> | <Link>CommandService.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net.Commands\CommandService.Events.cs"> | |||||
<Link>CommandService.Events.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net.Commands\CommandServiceConfig.cs"> | <Compile Include="..\Discord.Net.Commands\CommandServiceConfig.cs"> | ||||
<Link>CommandServiceConfig.cs</Link> | <Link>CommandServiceConfig.cs</Link> | ||||
</Compile> | </Compile> | ||||
@@ -5,48 +5,24 @@ using System.Threading.Tasks; | |||||
namespace Discord.Commands | namespace Discord.Commands | ||||
{ | { | ||||
public enum ParameterType | |||||
{ | |||||
/// <summary> Catches a single required parameter. </summary> | |||||
Required, | |||||
/// <summary> Catches a single optional parameter. </summary> | |||||
Optional, | |||||
/// <summary> Catches a zero or more optional parameters. </summary> | |||||
Multiple, | |||||
/// <summary> Catches all remaining text as a single optional parameter. </summary> | |||||
Unparsed | |||||
} | |||||
public sealed class CommandParameter | |||||
{ | |||||
public string Name { get; } | |||||
public int Id { get; internal set; } | |||||
public ParameterType Type { get; } | |||||
public CommandParameter(string name, ParameterType type) | |||||
{ | |||||
Name = name; | |||||
Type = type; | |||||
} | |||||
} | |||||
public sealed class Command | public sealed class Command | ||||
{ | |||||
public string Text { get; } | |||||
{ | |||||
private string[] _aliases; | |||||
internal CommandParameter[] _parameters; | |||||
private IPermissionChecker[] _checks; | |||||
private Func<CommandEventArgs, Task> _runFunc; | |||||
internal readonly Dictionary<string, CommandParameter> _parametersByName; | |||||
public string Text { get; } | |||||
public string Category { get; internal set; } | public string Category { get; internal set; } | ||||
public bool IsHidden { get; internal set; } | public bool IsHidden { get; internal set; } | ||||
public string Description { get; internal set; } | public string Description { get; internal set; } | ||||
public IEnumerable<string> Aliases => _aliases; | public IEnumerable<string> Aliases => _aliases; | ||||
private string[] _aliases; | |||||
public IEnumerable<CommandParameter> Parameters => _parameters; | public IEnumerable<CommandParameter> Parameters => _parameters; | ||||
internal CommandParameter[] _parameters; | |||||
private IPermissionChecker[] _checks; | |||||
private Func<CommandEventArgs, Task> _runFunc; | |||||
internal readonly Dictionary<string, CommandParameter> _parametersByName; | |||||
public CommandParameter this[string name] => _parametersByName[name]; | |||||
internal Command(string text) | |||||
internal Command(string text) | |||||
{ | { | ||||
Text = text; | Text = text; | ||||
IsHidden = false; | IsHidden = false; | ||||
@@ -55,7 +31,6 @@ namespace Discord.Commands | |||||
_parametersByName = new Dictionary<string, CommandParameter>(); | _parametersByName = new Dictionary<string, CommandParameter>(); | ||||
} | } | ||||
public CommandParameter this[string name] => _parametersByName[name]; | |||||
internal void SetAliases(string[] aliases) | internal void SetAliases(string[] aliases) | ||||
{ | { | ||||
@@ -0,0 +1,18 @@ | |||||
using System; | |||||
namespace Discord.Commands | |||||
{ | |||||
public enum CommandErrorType { Exception, UnknownCommand, BadPermissions, BadArgCount, InvalidInput } | |||||
public class CommandErrorEventArgs : CommandEventArgs | |||||
{ | |||||
public CommandErrorType ErrorType { get; } | |||||
public Exception Exception { get; } | |||||
public CommandErrorEventArgs(CommandErrorType errorType, CommandEventArgs baseArgs, Exception ex) | |||||
: base(baseArgs.Message, baseArgs.Command, baseArgs.Args) | |||||
{ | |||||
Exception = ex; | |||||
ErrorType = errorType; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,27 @@ | |||||
using System; | |||||
namespace Discord.Commands | |||||
{ | |||||
public class CommandEventArgs : EventArgs | |||||
{ | |||||
private readonly string[] _args; | |||||
public Message Message { get; } | |||||
public Command Command { get; } | |||||
public User User => Message.User; | |||||
public Channel Channel => Message.Channel; | |||||
public Server Server => Message.Channel.Server; | |||||
public CommandEventArgs(Message message, Command command, string[] args) | |||||
{ | |||||
Message = message; | |||||
Command = command; | |||||
_args = args; | |||||
} | |||||
public string[] Args => _args; | |||||
public string GetArg(int index) => _args[index]; | |||||
public string GetArg(string name) => _args[Command[name].Id]; | |||||
} | |||||
} |
@@ -0,0 +1,26 @@ | |||||
namespace Discord.Commands | |||||
{ | |||||
public enum ParameterType | |||||
{ | |||||
/// <summary> Catches a single required parameter. </summary> | |||||
Required, | |||||
/// <summary> Catches a single optional parameter. </summary> | |||||
Optional, | |||||
/// <summary> Catches a zero or more optional parameters. </summary> | |||||
Multiple, | |||||
/// <summary> Catches all remaining text as a single optional parameter. </summary> | |||||
Unparsed | |||||
} | |||||
public sealed class CommandParameter | |||||
{ | |||||
public string Name { get; } | |||||
public int Id { get; internal set; } | |||||
public ParameterType Type { get; } | |||||
public CommandParameter(string name, ParameterType type) | |||||
{ | |||||
Name = name; | |||||
Type = type; | |||||
} | |||||
} | |||||
} |
@@ -1,57 +0,0 @@ | |||||
using System; | |||||
namespace Discord.Commands | |||||
{ | |||||
public class CommandEventArgs : EventArgs | |||||
{ | |||||
private readonly string[] _args; | |||||
public Message Message { get; } | |||||
public Command Command { get; } | |||||
public User User => Message.User; | |||||
public Channel Channel => Message.Channel; | |||||
public Server Server => Message.Channel.Server; | |||||
public CommandEventArgs(Message message, Command command, string[] args) | |||||
{ | |||||
Message = message; | |||||
Command = command; | |||||
_args = args; | |||||
} | |||||
public string[] Args => _args; | |||||
public string GetArg(int index) => _args[index]; | |||||
public string GetArg(string name) => _args[Command[name].Id]; | |||||
} | |||||
public enum CommandErrorType { Exception, UnknownCommand, BadPermissions, BadArgCount, InvalidInput } | |||||
public class CommandErrorEventArgs : CommandEventArgs | |||||
{ | |||||
public CommandErrorType ErrorType { get; } | |||||
public Exception Exception { get; } | |||||
public CommandErrorEventArgs(CommandErrorType errorType, CommandEventArgs baseArgs, Exception ex) | |||||
: base(baseArgs.Message, baseArgs.Command, baseArgs.Args) | |||||
{ | |||||
Exception = ex; | |||||
ErrorType = errorType; | |||||
} | |||||
} | |||||
public partial class CommandService | |||||
{ | |||||
public event EventHandler<CommandEventArgs> RanCommand; | |||||
private void RaiseRanCommand(CommandEventArgs args) | |||||
{ | |||||
if (RanCommand != null) | |||||
RanCommand(this, args); | |||||
} | |||||
public event EventHandler<CommandErrorEventArgs> CommandError; | |||||
private void RaiseCommandError(CommandErrorType errorType, CommandEventArgs args, Exception ex = null) | |||||
{ | |||||
if (CommandError != null) | |||||
CommandError(this, new CommandErrorEventArgs(errorType, args, ex)); | |||||
} | |||||
} | |||||
} |
@@ -6,42 +6,45 @@ using System.Threading.Tasks; | |||||
namespace Discord.Commands | namespace Discord.Commands | ||||
{ | { | ||||
/// <summary> A Discord.Net client with extensions for handling common bot operations like text commands. </summary> | |||||
public sealed partial class CommandService : IService | |||||
public partial class CommandService : IService | |||||
{ | { | ||||
private readonly CommandServiceConfig _config; | |||||
private readonly CommandGroupBuilder _root; | |||||
private DiscordClient _client; | |||||
public DiscordClient Client => _client; | |||||
public CommandGroupBuilder Root => _root; | |||||
private readonly List<Command> _allCommands; | |||||
private readonly Dictionary<string, CommandMap> _categories; | |||||
private readonly CommandMap _map; //Command map stores all commands by their input text, used for fast resolving and parsing | |||||
public CommandServiceConfig Config { get; } | |||||
public CommandGroupBuilder Root { get; } | |||||
public DiscordClient Client { get; private set; } | |||||
//AllCommands store a flattened collection of all commands | //AllCommands store a flattened collection of all commands | ||||
public IEnumerable<Command> AllCommands => _allCommands; | |||||
private readonly List<Command> _allCommands; | |||||
//Command map stores all commands by their input text, used for fast resolving and parsing | |||||
private readonly CommandMap _map; | |||||
public IEnumerable<Command> AllCommands => _allCommands; | |||||
//Groups store all commands by their module, used for more informative help | //Groups store all commands by their module, used for more informative help | ||||
internal IEnumerable<CommandMap> Categories => _categories.Values; | internal IEnumerable<CommandMap> Categories => _categories.Values; | ||||
private readonly Dictionary<string, CommandMap> _categories; | |||||
public CommandService(CommandServiceConfig config) | |||||
public event EventHandler<CommandEventArgs> Command = delegate { }; | |||||
public event EventHandler<CommandErrorEventArgs> CommandError = delegate { }; | |||||
private void OnCommand(CommandEventArgs args) | |||||
=> Command(this, args); | |||||
private void OnCommandError(CommandErrorType errorType, CommandEventArgs args, Exception ex = null) | |||||
=> CommandError(this, new CommandErrorEventArgs(errorType, args, ex)); | |||||
public CommandService(CommandServiceConfig config) | |||||
{ | { | ||||
_config = config; | |||||
Config = config; | |||||
_allCommands = new List<Command>(); | _allCommands = new List<Command>(); | ||||
_map = new CommandMap(null, "", ""); | _map = new CommandMap(null, "", ""); | ||||
_categories = new Dictionary<string, CommandMap>(); | _categories = new Dictionary<string, CommandMap>(); | ||||
_root = new CommandGroupBuilder(this, "", null); | |||||
Root = new CommandGroupBuilder(this, "", null); | |||||
} | } | ||||
void IService.Install(DiscordClient client) | void IService.Install(DiscordClient client) | ||||
{ | { | ||||
_client = client; | |||||
_config.Lock(); | |||||
Client = client; | |||||
Config.Lock(); | |||||
if (_config.HelpMode != HelpMode.Disable) | |||||
if (Config.HelpMode != HelpMode.Disable) | |||||
{ | { | ||||
CreateCommand("help") | CreateCommand("help") | ||||
.Parameter("command", ParameterType.Multiple) | .Parameter("command", ParameterType.Multiple) | ||||
@@ -49,7 +52,7 @@ namespace Discord.Commands | |||||
.Description("Returns information about commands.") | .Description("Returns information about commands.") | ||||
.Do(async e => | .Do(async e => | ||||
{ | { | ||||
Channel replyChannel = _config.HelpMode == HelpMode.Public ? e.Channel : await e.User.CreatePMChannel().ConfigureAwait(false); | |||||
Channel replyChannel = Config.HelpMode == HelpMode.Public ? e.Channel : await e.User.CreatePMChannel().ConfigureAwait(false); | |||||
if (e.Args.Length > 0) //Show command help | if (e.Args.Length > 0) //Show command help | ||||
{ | { | ||||
var map = _map.GetItem(string.Join(" ", e.Args)); | var map = _map.GetItem(string.Join(" ", e.Args)); | ||||
@@ -66,13 +69,13 @@ namespace Discord.Commands | |||||
client.MessageReceived += async (s, e) => | client.MessageReceived += async (s, e) => | ||||
{ | { | ||||
if (_allCommands.Count == 0) return; | if (_allCommands.Count == 0) return; | ||||
if (e.Message.User == null || e.Message.User.Id == _client.CurrentUser.Id) return; | |||||
if (e.Message.User == null || e.Message.User.Id == Client.CurrentUser.Id) return; | |||||
string msg = e.Message.RawText; | string msg = e.Message.RawText; | ||||
if (msg.Length == 0) return; | if (msg.Length == 0) return; | ||||
//Check for command char if one is provided | //Check for command char if one is provided | ||||
var chars = _config.CommandChars; | |||||
var chars = Config.CommandChars; | |||||
if (chars.Length > 0) | if (chars.Length > 0) | ||||
{ | { | ||||
if (!chars.Contains(msg[0])) | if (!chars.Contains(msg[0])) | ||||
@@ -87,7 +90,7 @@ namespace Discord.Commands | |||||
if (commands == null) | if (commands == null) | ||||
{ | { | ||||
CommandEventArgs errorArgs = new CommandEventArgs(e.Message, null, null); | CommandEventArgs errorArgs = new CommandEventArgs(e.Message, null, null); | ||||
RaiseCommandError(CommandErrorType.UnknownCommand, errorArgs); | |||||
OnCommandError(CommandErrorType.UnknownCommand, errorArgs); | |||||
return; | return; | ||||
} | } | ||||
else | else | ||||
@@ -104,7 +107,7 @@ namespace Discord.Commands | |||||
else | else | ||||
{ | { | ||||
var errorArgs = new CommandEventArgs(e.Message, command, null); | var errorArgs = new CommandEventArgs(e.Message, command, null); | ||||
RaiseCommandError(error.Value, errorArgs); | |||||
OnCommandError(error.Value, errorArgs); | |||||
return; | return; | ||||
} | } | ||||
} | } | ||||
@@ -115,24 +118,24 @@ namespace Discord.Commands | |||||
string errorText; | string errorText; | ||||
if (!command.CanRun(eventArgs.User, eventArgs.Channel, out errorText)) | if (!command.CanRun(eventArgs.User, eventArgs.Channel, out errorText)) | ||||
{ | { | ||||
RaiseCommandError(CommandErrorType.BadPermissions, eventArgs, errorText != null ? new Exception(errorText) : null); | |||||
OnCommandError(CommandErrorType.BadPermissions, eventArgs, errorText != null ? new Exception(errorText) : null); | |||||
return; | return; | ||||
} | } | ||||
// Run the command | // Run the command | ||||
try | try | ||||
{ | { | ||||
RaiseRanCommand(eventArgs); | |||||
OnCommand(eventArgs); | |||||
await command.Run(eventArgs).ConfigureAwait(false); | await command.Run(eventArgs).ConfigureAwait(false); | ||||
} | } | ||||
catch (Exception ex) | catch (Exception ex) | ||||
{ | { | ||||
RaiseCommandError(CommandErrorType.Exception, eventArgs, ex); | |||||
OnCommandError(CommandErrorType.Exception, eventArgs, ex); | |||||
} | } | ||||
return; | return; | ||||
} | } | ||||
var errorArgs2 = new CommandEventArgs(e.Message, null, null); | var errorArgs2 = new CommandEventArgs(e.Message, null, null); | ||||
RaiseCommandError(CommandErrorType.BadArgCount, errorArgs2); | |||||
OnCommandError(CommandErrorType.BadArgCount, errorArgs2); | |||||
} | } | ||||
}; | }; | ||||
} | } | ||||
@@ -184,7 +187,7 @@ namespace Discord.Commands | |||||
{ | { | ||||
output.Append("\n\n"); | output.Append("\n\n"); | ||||
var chars = _config.CommandChars; | |||||
var chars = Config.CommandChars; | |||||
if (chars.Length > 0) | if (chars.Length > 0) | ||||
{ | { | ||||
if (chars.Length == 1) | if (chars.Length == 1) | ||||
@@ -294,8 +297,8 @@ namespace Discord.Commands | |||||
output.AppendLine($"Aliases: `" + string.Join("`, `", command.Aliases) + '`'); | output.AppendLine($"Aliases: `" + string.Join("`, `", command.Aliases) + '`'); | ||||
} | } | ||||
public void CreateGroup(string cmd, Action<CommandGroupBuilder> config = null) => _root.CreateGroup(cmd, config); | |||||
public CommandBuilder CreateCommand(string cmd) => _root.CreateCommand(cmd); | |||||
public void CreateGroup(string cmd, Action<CommandGroupBuilder> config = null) => Root.CreateGroup(cmd, config); | |||||
public CommandBuilder CreateCommand(string cmd) => Root.CreateCommand(cmd); | |||||
internal void AddCommand(Command command) | internal void AddCommand(Command command) | ||||
{ | { | ||||
@@ -6,7 +6,7 @@ using System.Linq; | |||||
namespace Discord.Modules | namespace Discord.Modules | ||||
{ | { | ||||
public class ModuleManager | |||||
public sealed class ModuleManager | |||||
{ | { | ||||
public event EventHandler<ServerEventArgs> ServerEnabled = delegate { }; | public event EventHandler<ServerEventArgs> ServerEnabled = delegate { }; | ||||
public event EventHandler<ServerEventArgs> ServerDisabled = delegate { }; | public event EventHandler<ServerEventArgs> ServerDisabled = delegate { }; | ||||
@@ -5,21 +5,19 @@ namespace Discord.Modules | |||||
{ | { | ||||
public class ModuleService : IService | public class ModuleService : IService | ||||
{ | { | ||||
private DiscordClient _client; | |||||
//ModuleServiceConfig Config { get; } | |||||
public DiscordClient Client { get; private set; } | |||||
public IEnumerable<ModuleManager> Modules => _modules.Values; | public IEnumerable<ModuleManager> Modules => _modules.Values; | ||||
private readonly Dictionary<IModule, ModuleManager> _modules; | private readonly Dictionary<IModule, ModuleManager> _modules; | ||||
public ModuleService(/*ModuleServiceConfig config*/) | |||||
public ModuleService() | |||||
{ | { | ||||
//Config = config; | |||||
_modules = new Dictionary<IModule, ModuleManager>(); | _modules = new Dictionary<IModule, ModuleManager>(); | ||||
} | } | ||||
void IService.Install(DiscordClient client) | void IService.Install(DiscordClient client) | ||||
{ | { | ||||
_client = client; | |||||
Client = client; | |||||
} | } | ||||
public void Install<T>(T module, string name, FilterType type) | public void Install<T>(T module, string name, FilterType type) | ||||
@@ -27,10 +25,12 @@ namespace Discord.Modules | |||||
{ | { | ||||
if (module == null) throw new ArgumentNullException(nameof(module)); | if (module == null) throw new ArgumentNullException(nameof(module)); | ||||
if (name == null) throw new ArgumentNullException(nameof(name)); | if (name == null) throw new ArgumentNullException(nameof(name)); | ||||
if (_client == null) throw new InvalidOperationException("Service needs to be added to a DiscordClient before modules can be installed."); | |||||
if (_modules.ContainsKey(module)) throw new InvalidOperationException("This module has already been added."); | |||||
if (Client == null) | |||||
throw new InvalidOperationException("Service needs to be added to a DiscordClient before modules can be installed."); | |||||
if (_modules.ContainsKey(module)) | |||||
throw new InvalidOperationException("This module has already been added."); | |||||
var manager = new ModuleManager(_client, name, type); | |||||
var manager = new ModuleManager(Client, name, type); | |||||
_modules.Add(module, manager); | _modules.Add(module, manager); | ||||
module.Install(manager); | module.Install(manager); | ||||
} | } | ||||
@@ -391,15 +391,24 @@ | |||||
<Compile Include="..\Discord.Net\API\Status\Rest\Upcoming.cs"> | <Compile Include="..\Discord.Net\API\Status\Rest\Upcoming.cs"> | ||||
<Link>API\Status\Rest\Upcoming.cs</Link> | <Link>API\Status\Rest\Upcoming.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\ChannelEventArgs.cs"> | |||||
<Link>ChannelEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\ChannelUserEventArgs.cs"> | |||||
<Link>ChannelUserEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Config.cs"> | |||||
<Link>Config.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\DisconnectedEventArgs.cs"> | |||||
<Link>DisconnectedEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\DiscordClient.cs"> | <Compile Include="..\Discord.Net\DiscordClient.cs"> | ||||
<Link>DiscordClient.cs</Link> | <Link>DiscordClient.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\DiscordClient.Events.cs"> | <Compile Include="..\Discord.Net\DiscordClient.Events.cs"> | ||||
<Link>DiscordClient.Events.cs</Link> | <Link>DiscordClient.Events.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\DiscordClient.Obsolete.cs"> | |||||
<Link>DiscordClient.Obsolete.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\DiscordConfig.cs"> | <Compile Include="..\Discord.Net\DiscordConfig.cs"> | ||||
<Link>DiscordConfig.cs</Link> | <Link>DiscordConfig.cs</Link> | ||||
</Compile> | </Compile> | ||||
@@ -421,33 +430,6 @@ | |||||
<Compile Include="..\Discord.Net\Enums\UserStatus.cs"> | <Compile Include="..\Discord.Net\Enums\UserStatus.cs"> | ||||
<Link>Enums\UserStatus.cs</Link> | <Link>Enums\UserStatus.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\Events\ChannelEventArgs.cs"> | |||||
<Link>Events\ChannelEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Events\ChannelUserEventArgs.cs"> | |||||
<Link>Events\ChannelUserEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Events\DisconnectedEventArgs.cs"> | |||||
<Link>Events\DisconnectedEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Events\LogMessageEventArgs.cs"> | |||||
<Link>Events\LogMessageEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Events\MessageEventArgs.cs"> | |||||
<Link>Events\MessageEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Events\ProfileEventArgs.cs"> | |||||
<Link>Events\ProfileEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Events\RoleEventArgs.cs"> | |||||
<Link>Events\RoleEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Events\ServerEventArgs.cs"> | |||||
<Link>Events\ServerEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Events\UserEventArgs.cs"> | |||||
<Link>Events\UserEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Extensions.cs"> | <Compile Include="..\Discord.Net\Extensions.cs"> | ||||
<Link>Extensions.cs</Link> | <Link>Extensions.cs</Link> | ||||
</Compile> | </Compile> | ||||
@@ -457,12 +439,21 @@ | |||||
<Compile Include="..\Discord.Net\IService.cs"> | <Compile Include="..\Discord.Net\IService.cs"> | ||||
<Link>IService.cs</Link> | <Link>IService.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\Legacy.cs"> | |||||
<Link>Legacy.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Logging\Logger.cs"> | <Compile Include="..\Discord.Net\Logging\Logger.cs"> | ||||
<Link>Logging\Logger.cs</Link> | <Link>Logging\Logger.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\Logging\LogManager.cs"> | <Compile Include="..\Discord.Net\Logging\LogManager.cs"> | ||||
<Link>Logging\LogManager.cs</Link> | <Link>Logging\LogManager.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\LogMessageEventArgs.cs"> | |||||
<Link>LogMessageEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\MessageEventArgs.cs"> | |||||
<Link>MessageEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\MessageQueue.cs"> | <Compile Include="..\Discord.Net\MessageQueue.cs"> | ||||
<Link>MessageQueue.cs</Link> | <Link>MessageQueue.cs</Link> | ||||
</Compile> | </Compile> | ||||
@@ -502,6 +493,9 @@ | |||||
<Compile Include="..\Discord.Net\Net\Rest\IRestEngine.cs"> | <Compile Include="..\Discord.Net\Net\Rest\IRestEngine.cs"> | ||||
<Link>Net\Rest\IRestEngine.cs</Link> | <Link>Net\Rest\IRestEngine.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\Net\Rest\RequestEventArgs.cs"> | |||||
<Link>Net\Rest\RequestEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Net\Rest\RestClient.cs"> | <Compile Include="..\Discord.Net\Net\Rest\RestClient.cs"> | ||||
<Link>Net\Rest\RestClient.cs</Link> | <Link>Net\Rest\RestClient.cs</Link> | ||||
</Compile> | </Compile> | ||||
@@ -514,6 +508,9 @@ | |||||
<Compile Include="..\Discord.Net\Net\WebSocketException.cs"> | <Compile Include="..\Discord.Net\Net\WebSocketException.cs"> | ||||
<Link>Net\WebSockets\WebSocketException.cs</Link> | <Link>Net\WebSockets\WebSocketException.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\Net\WebSockets\BinaryMessageEventArgs.cs"> | |||||
<Link>Net\WebSockets\BinaryMessageEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Net\WebSockets\BuiltInEngine.cs"> | <Compile Include="..\Discord.Net\Net\WebSockets\BuiltInEngine.cs"> | ||||
<Link>Net\WebSockets\BuiltInEngine.cs</Link> | <Link>Net\WebSockets\BuiltInEngine.cs</Link> | ||||
</Compile> | </Compile> | ||||
@@ -523,30 +520,39 @@ | |||||
<Compile Include="..\Discord.Net\Net\WebSockets\IWebSocketEngine.cs"> | <Compile Include="..\Discord.Net\Net\WebSockets\IWebSocketEngine.cs"> | ||||
<Link>Net\WebSockets\IWebSocketEngine.cs</Link> | <Link>Net\WebSockets\IWebSocketEngine.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\Net\WebSockets\WebSocket.BuiltIn.cs"> | |||||
<Link>Net\WebSockets\WebSocket.BuiltIn.cs</Link> | |||||
<Compile Include="..\Discord.Net\Net\WebSockets\TextMessageEventArgs.cs"> | |||||
<Link>Net\WebSockets\TextMessageEventArgs.cs</Link> | |||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\Net\WebSockets\WebSocket.cs"> | <Compile Include="..\Discord.Net\Net\WebSockets\WebSocket.cs"> | ||||
<Link>Net\WebSockets\WebSocket.cs</Link> | <Link>Net\WebSockets\WebSocket.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\Net\WebSockets\WebSocketSharpEngine.cs"> | |||||
<Link>Net\WebSockets\WebSocketSharpEngine.cs</Link> | |||||
<Compile Include="..\Discord.Net\Net\WebSockets\WebSocketEventEventArgs.cs"> | |||||
<Link>Net\WebSockets\WebSocketEventEventArgs.cs</Link> | |||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\Net\WebSockets\WS4NetEngine.cs"> | <Compile Include="..\Discord.Net\Net\WebSockets\WS4NetEngine.cs"> | ||||
<Link>Net\WebSockets\WS4NetEngine.cs</Link> | <Link>Net\WebSockets\WS4NetEngine.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\Reference.cs"> | |||||
<Link>Reference.cs</Link> | |||||
<Compile Include="..\Discord.Net\ProfileEventArgs.cs"> | |||||
<Link>ProfileEventArgs.cs</Link> | |||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\RelativeDirection.cs"> | <Compile Include="..\Discord.Net\RelativeDirection.cs"> | ||||
<Link>RelativeDirection.cs</Link> | <Link>RelativeDirection.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\RoleEventArgs.cs"> | |||||
<Link>RoleEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\ServerEventArgs.cs"> | |||||
<Link>ServerEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\ServiceManager.cs"> | <Compile Include="..\Discord.Net\ServiceManager.cs"> | ||||
<Link>ServiceManager.cs</Link> | <Link>ServiceManager.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\TaskManager.cs"> | <Compile Include="..\Discord.Net\TaskManager.cs"> | ||||
<Link>TaskManager.cs</Link> | <Link>TaskManager.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\UserEventArgs.cs"> | |||||
<Link>UserEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="Properties\AssemblyInfo.cs" /> | <Compile Include="Properties\AssemblyInfo.cs" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
@@ -4,7 +4,7 @@ using System.Collections.Generic; | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
[JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
internal sealed class IdentifyCommand : IWebSocketMessage | |||||
public sealed class IdentifyCommand : IWebSocketMessage | |||||
{ | { | ||||
int IWebSocketMessage.OpCode => (int)OpCodes.Identify; | int IWebSocketMessage.OpCode => (int)OpCodes.Identify; | ||||
object IWebSocketMessage.Payload => this; | object IWebSocketMessage.Payload => this; | ||||
@@ -4,7 +4,7 @@ using Newtonsoft.Json; | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
[JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
internal sealed class RequestMembersCommand : IWebSocketMessage | |||||
public sealed class RequestMembersCommand : IWebSocketMessage | |||||
{ | { | ||||
int IWebSocketMessage.OpCode => (int)OpCodes.RequestGuildMembers; | int IWebSocketMessage.OpCode => (int)OpCodes.RequestGuildMembers; | ||||
object IWebSocketMessage.Payload => this; | object IWebSocketMessage.Payload => this; | ||||
@@ -1,9 +1,4 @@ | |||||
using Discord.API.Converters; | |||||
using Newtonsoft.Json; | |||||
namespace Discord.API.Client.GatewaySocket | |||||
namespace Discord.API.Client.GatewaySocket | |||||
{ | { | ||||
public sealed class GuildBanAddEvent : MemberReference | |||||
{ | |||||
} | |||||
public sealed class GuildBanAddEvent : MemberReference { } | |||||
} | } |
@@ -1,9 +1,4 @@ | |||||
using Discord.API.Converters; | |||||
using Newtonsoft.Json; | |||||
namespace Discord.API.Client.GatewaySocket | |||||
namespace Discord.API.Client.GatewaySocket | |||||
{ | { | ||||
public sealed class GuildBanRemoveEvent : MemberReference | |||||
{ | |||||
} | |||||
public sealed class GuildBanRemoveEvent : MemberReference { } | |||||
} | } |
@@ -1,4 +1,4 @@ | |||||
namespace Discord.API.Client.GatewaySocket.Events | namespace Discord.API.Client.GatewaySocket.Events | ||||
{ | { | ||||
//public sealed class GuildEmojisUpdate { } | |||||
//public sealed class GuildEmojisUpdateEvent { } | |||||
} | } |
@@ -8,6 +8,6 @@ namespace Discord.API.Client.GatewaySocket | |||||
[JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] | [JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] | ||||
public ulong GuildId { get; set; } | public ulong GuildId { get; set; } | ||||
[JsonProperty("members")] | [JsonProperty("members")] | ||||
public Member[] Members; | |||||
public Member[] Members { get; set; } | |||||
} | } | ||||
} | } |
@@ -8,7 +8,7 @@ namespace Discord.API.Client | |||||
object Payload { get; } | object Payload { get; } | ||||
bool IsPrivate { get; } | bool IsPrivate { get; } | ||||
} | } | ||||
public class WebSocketMessage | |||||
public sealed class WebSocketMessage | |||||
{ | { | ||||
[JsonProperty("op")] | [JsonProperty("op")] | ||||
public int? Operation { get; set; } | public int? Operation { get; set; } | ||||
@@ -4,7 +4,7 @@ using System.Collections.Generic; | |||||
namespace Discord.API.Converters | namespace Discord.API.Converters | ||||
{ | { | ||||
public class LongStringConverter : JsonConverter | |||||
public sealed class LongStringConverter : JsonConverter | |||||
{ | { | ||||
public override bool CanConvert(Type objectType) | public override bool CanConvert(Type objectType) | ||||
=> objectType == typeof(ulong); | => objectType == typeof(ulong); | ||||
@@ -14,7 +14,7 @@ namespace Discord.API.Converters | |||||
=> writer.WriteValue(((ulong)value).ToIdString()); | => writer.WriteValue(((ulong)value).ToIdString()); | ||||
} | } | ||||
public class NullableLongStringConverter : JsonConverter | |||||
public sealed class NullableLongStringConverter : JsonConverter | |||||
{ | { | ||||
public override bool CanConvert(Type objectType) | public override bool CanConvert(Type objectType) | ||||
=> objectType == typeof(ulong?); | => objectType == typeof(ulong?); | ||||
@@ -24,7 +24,7 @@ namespace Discord.API.Converters | |||||
=> writer.WriteValue(((ulong?)value).ToIdString()); | => writer.WriteValue(((ulong?)value).ToIdString()); | ||||
} | } | ||||
/*public class LongStringEnumerableConverter : JsonConverter | |||||
/*public sealed class LongStringEnumerableConverter : JsonConverter | |||||
{ | { | ||||
public override bool CanConvert(Type objectType) => objectType == typeof(IEnumerable<ulong>); | public override bool CanConvert(Type objectType) => objectType == typeof(IEnumerable<ulong>); | ||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) | ||||
@@ -55,7 +55,7 @@ namespace Discord.API.Converters | |||||
} | } | ||||
}*/ | }*/ | ||||
internal class LongStringArrayConverter : JsonConverter | |||||
internal sealed class LongStringArrayConverter : JsonConverter | |||||
{ | { | ||||
public override bool CanConvert(Type objectType) => objectType == typeof(IEnumerable<ulong[]>); | public override bool CanConvert(Type objectType) => objectType == typeof(IEnumerable<ulong[]>); | ||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) | ||||
@@ -5,6 +5,7 @@ namespace Discord | |||||
public class ChannelEventArgs : EventArgs | public class ChannelEventArgs : EventArgs | ||||
{ | { | ||||
public Channel Channel { get; } | public Channel Channel { get; } | ||||
public Server Server => Channel.Server; | public Server Server => Channel.Server; | ||||
public ChannelEventArgs(Channel channel) { Channel = channel; } | public ChannelEventArgs(Channel channel) { Channel = channel; } |
@@ -0,0 +1,24 @@ | |||||
using System; | |||||
namespace Discord | |||||
{ | |||||
public abstract class Config<T> | |||||
where T : Config<T> | |||||
{ | |||||
protected bool _isLocked; | |||||
protected internal void Lock() { _isLocked = true; } | |||||
protected void SetValue<U>(ref U storage, U value) | |||||
{ | |||||
if (_isLocked) | |||||
throw new InvalidOperationException("Unable to modify a discord client's configuration after it has been created."); | |||||
storage = value; | |||||
} | |||||
public T Clone() | |||||
{ | |||||
var config = MemberwiseClone() as T; | |||||
config._isLocked = false; | |||||
return config; | |||||
} | |||||
} | |||||
} |
@@ -13,26 +13,6 @@ namespace Discord | |||||
Verbose = 4, | Verbose = 4, | ||||
Debug = 5 | Debug = 5 | ||||
} | } | ||||
public abstract class Config<T> | |||||
where T : Config<T> | |||||
{ | |||||
protected bool _isLocked; | |||||
protected internal void Lock() { _isLocked = true; } | |||||
protected void SetValue<U>(ref U storage, U value) | |||||
{ | |||||
if (_isLocked) | |||||
throw new InvalidOperationException("Unable to modify a discord client's configuration after it has been created."); | |||||
storage = value; | |||||
} | |||||
public T Clone() | |||||
{ | |||||
var config = MemberwiseClone() as T; | |||||
config._isLocked = false; | |||||
return config; | |||||
} | |||||
} | |||||
public class DiscordConfig : Config<DiscordConfig> | public class DiscordConfig : Config<DiscordConfig> | ||||
{ | { | ||||
@@ -1,6 +1,6 @@ | |||||
namespace Discord | namespace Discord | ||||
{ | { | ||||
public class ChannelType : StringEnum | |||||
public sealed class ChannelType : StringEnum | |||||
{ | { | ||||
/// <summary> A text-only channel. </summary> | /// <summary> A text-only channel. </summary> | ||||
public static ChannelType Text { get; } = new ChannelType("text"); | public static ChannelType Text { get; } = new ChannelType("text"); | ||||
@@ -1,6 +1,6 @@ | |||||
namespace Discord | namespace Discord | ||||
{ | { | ||||
public class PermissionTarget : StringEnum | |||||
public sealed class PermissionTarget : StringEnum | |||||
{ | { | ||||
/// <summary> A text-only channel. </summary> | /// <summary> A text-only channel. </summary> | ||||
public static PermissionTarget Role { get; } = new PermissionTarget("role"); | public static PermissionTarget Role { get; } = new PermissionTarget("role"); | ||||
@@ -1,6 +1,6 @@ | |||||
namespace Discord | namespace Discord | ||||
{ | { | ||||
public class UserStatus : StringEnum | |||||
public sealed class UserStatus : StringEnum | |||||
{ | { | ||||
/// <summary> User is currently online and active. </summary> | /// <summary> User is currently online and active. </summary> | ||||
public static UserStatus Online { get; } = new UserStatus("online"); | public static UserStatus Online { get; } = new UserStatus("online"); | ||||
@@ -3,7 +3,7 @@ using System.Collections.Generic; | |||||
using System.IO; | using System.IO; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
namespace Discord.Legacy | |||||
namespace Discord | |||||
{ | { | ||||
public static class Mention | public static class Mention | ||||
{ | { | ||||
@@ -31,19 +31,19 @@ namespace Discord.Legacy | |||||
if (server == null) throw new ArgumentNullException(nameof(server)); | if (server == null) throw new ArgumentNullException(nameof(server)); | ||||
return server.FindChannels(name, type, exactMatch); | return server.FindChannels(name, type, exactMatch); | ||||
} | } | ||||
[Obsolete("Use Server.CreateChannel")] | [Obsolete("Use Server.CreateChannel")] | ||||
public static Task<Channel> CreateChannel(this DiscordClient client, Server server, string name, ChannelType type) | public static Task<Channel> CreateChannel(this DiscordClient client, Server server, string name, ChannelType type) | ||||
{ | { | ||||
if (server == null) throw new ArgumentNullException(nameof(server)); | if (server == null) throw new ArgumentNullException(nameof(server)); | ||||
return server.CreateChannel(name, type); | return server.CreateChannel(name, type); | ||||
} | |||||
} | |||||
[Obsolete("Use User.CreateChannel")] | [Obsolete("Use User.CreateChannel")] | ||||
public static Task<Channel> CreatePMChannel(this DiscordClient client, User user) | public static Task<Channel> CreatePMChannel(this DiscordClient client, User user) | ||||
{ | { | ||||
if (user == null) throw new ArgumentNullException(nameof(user)); | if (user == null) throw new ArgumentNullException(nameof(user)); | ||||
return user.CreatePMChannel(); | return user.CreatePMChannel(); | ||||
} | |||||
} | |||||
[Obsolete("Use Channel.Edit")] | [Obsolete("Use Channel.Edit")] | ||||
public static Task EditChannel(this DiscordClient client, Channel channel, string name = null, string topic = null, int? position = null) | public static Task EditChannel(this DiscordClient client, Channel channel, string name = null, string topic = null, int? position = null) | ||||
{ | { | ||||
@@ -62,15 +62,15 @@ namespace Discord.Legacy | |||||
{ | { | ||||
if (server == null) throw new ArgumentNullException(nameof(server)); | if (server == null) throw new ArgumentNullException(nameof(server)); | ||||
return server.ReorderChannels(channels, after); | return server.ReorderChannels(channels, after); | ||||
} | |||||
} | |||||
[Obsolete("Use Server.GetInvites")] | [Obsolete("Use Server.GetInvites")] | ||||
public static Task<IEnumerable<Invite>> GetInvites(this DiscordClient client, Server server) | public static Task<IEnumerable<Invite>> GetInvites(this DiscordClient client, Server server) | ||||
{ | { | ||||
if (server == null) throw new ArgumentNullException(nameof(server)); | if (server == null) throw new ArgumentNullException(nameof(server)); | ||||
return server.GetInvites(); | return server.GetInvites(); | ||||
} | } | ||||
[Obsolete("Use Server.CreateInvite")] | [Obsolete("Use Server.CreateInvite")] | ||||
public static Task<Invite> CreateInvite(this DiscordClient client, Server server, int? maxAge = 1800, int? maxUses = null, bool tempMembership = false, bool withXkcd = false) | public static Task<Invite> CreateInvite(this DiscordClient client, Server server, int? maxAge = 1800, int? maxUses = null, bool tempMembership = false, bool withXkcd = false) | ||||
{ | { | ||||
@@ -83,20 +83,20 @@ namespace Discord.Legacy | |||||
if (channel == null) throw new ArgumentNullException(nameof(channel)); | if (channel == null) throw new ArgumentNullException(nameof(channel)); | ||||
return channel.CreateInvite(maxAge, maxUses, tempMembership, withXkcd); | return channel.CreateInvite(maxAge, maxUses, tempMembership, withXkcd); | ||||
} | } | ||||
[Obsolete("Use Invite.Delete")] | [Obsolete("Use Invite.Delete")] | ||||
public static Task DeleteInvite(this DiscordClient client, Invite invite) | public static Task DeleteInvite(this DiscordClient client, Invite invite) | ||||
{ | { | ||||
if (invite == null) throw new ArgumentNullException(nameof(invite)); | if (invite == null) throw new ArgumentNullException(nameof(invite)); | ||||
return invite.Delete(); | return invite.Delete(); | ||||
} | |||||
} | |||||
[Obsolete("Use Invite.Accept")] | [Obsolete("Use Invite.Accept")] | ||||
public static Task AcceptInvite(this DiscordClient client, Invite invite) | public static Task AcceptInvite(this DiscordClient client, Invite invite) | ||||
{ | { | ||||
if (invite == null) throw new ArgumentNullException(nameof(invite)); | if (invite == null) throw new ArgumentNullException(nameof(invite)); | ||||
return invite.Accept(); | return invite.Accept(); | ||||
} | } | ||||
[Obsolete("Use Channel.SendMessage")] | [Obsolete("Use Channel.SendMessage")] | ||||
public static Task<Message> SendMessage(this DiscordClient client, Channel channel, string text) | public static Task<Message> SendMessage(this DiscordClient client, Channel channel, string text) | ||||
{ | { | ||||
@@ -139,14 +139,14 @@ namespace Discord.Legacy | |||||
if (user == null) throw new ArgumentNullException(nameof(user)); | if (user == null) throw new ArgumentNullException(nameof(user)); | ||||
return user.SendFile(filename, stream); | return user.SendFile(filename, stream); | ||||
} | } | ||||
[Obsolete("Use Message.Edit")] | [Obsolete("Use Message.Edit")] | ||||
public static Task EditMessage(this DiscordClient client, Message message, string text) | public static Task EditMessage(this DiscordClient client, Message message, string text) | ||||
{ | { | ||||
if (message == null) throw new ArgumentNullException(nameof(message)); | if (message == null) throw new ArgumentNullException(nameof(message)); | ||||
return message.Edit(text); | return message.Edit(text); | ||||
} | } | ||||
[Obsolete("Use Message.Delete")] | [Obsolete("Use Message.Delete")] | ||||
public static Task DeleteMessage(this DiscordClient client, Message message) | public static Task DeleteMessage(this DiscordClient client, Message message) | ||||
{ | { | ||||
@@ -161,14 +161,14 @@ namespace Discord.Legacy | |||||
foreach (var message in messages) | foreach (var message in messages) | ||||
await message.Delete().ConfigureAwait(false); | await message.Delete().ConfigureAwait(false); | ||||
} | } | ||||
[Obsolete("Use Channel.DownloadMessages")] | [Obsolete("Use Channel.DownloadMessages")] | ||||
public static Task<Message[]> DownloadMessages(this DiscordClient client, Channel channel, int limit = 100, ulong? relativeMessageId = null, RelativeDirection relativeDir = RelativeDirection.Before, bool useCache = true) | public static Task<Message[]> DownloadMessages(this DiscordClient client, Channel channel, int limit = 100, ulong? relativeMessageId = null, RelativeDirection relativeDir = RelativeDirection.Before, bool useCache = true) | ||||
{ | { | ||||
if (channel == null) throw new ArgumentNullException(nameof(channel)); | if (channel == null) throw new ArgumentNullException(nameof(channel)); | ||||
return channel.DownloadMessages(limit, relativeMessageId, relativeDir, useCache); | return channel.DownloadMessages(limit, relativeMessageId, relativeDir, useCache); | ||||
} | } | ||||
[Obsolete("Use Message.Acknowledge")] | [Obsolete("Use Message.Acknowledge")] | ||||
public static Task AckMessage(this DiscordClient client, Message message) | public static Task AckMessage(this DiscordClient client, Message message) | ||||
{ | { | ||||
@@ -212,7 +212,7 @@ namespace Discord.Legacy | |||||
return JsonConvert.SerializeObject(channel.Messages); | return JsonConvert.SerializeObject(channel.Messages); | ||||
}*/ | }*/ | ||||
[Obsolete("Use Server.GetUser")] | [Obsolete("Use Server.GetUser")] | ||||
public static User GetUser(this DiscordClient client, Server server, ulong userId) | public static User GetUser(this DiscordClient client, Server server, ulong userId) | ||||
{ | { | ||||
@@ -225,7 +225,7 @@ namespace Discord.Legacy | |||||
if (server == null) throw new ArgumentNullException(nameof(server)); | if (server == null) throw new ArgumentNullException(nameof(server)); | ||||
return server.GetUser(username, discriminator); | return server.GetUser(username, discriminator); | ||||
} | } | ||||
[Obsolete("Use Server.FindUsers")] | [Obsolete("Use Server.FindUsers")] | ||||
public static IEnumerable<User> FindUsers(this DiscordClient client, Server server, string name, bool exactMatch = false) | public static IEnumerable<User> FindUsers(this DiscordClient client, Server server, string name, bool exactMatch = false) | ||||
{ | { | ||||
@@ -287,20 +287,20 @@ namespace Discord.Legacy | |||||
string username = null, string email = null, string password = null, | string username = null, string email = null, string password = null, | ||||
Stream avatar = null, ImageType avatarType = ImageType.Png) | Stream avatar = null, ImageType avatarType = ImageType.Png) | ||||
=> client.CurrentUser.Edit(currentPassword, username, email, password, avatar, avatarType); | => client.CurrentUser.Edit(currentPassword, username, email, password, avatar, avatarType); | ||||
[Obsolete("Use Server.GetRole")] | [Obsolete("Use Server.GetRole")] | ||||
public static Role GetRole(this DiscordClient client, Server server, ulong id) | public static Role GetRole(this DiscordClient client, Server server, ulong id) | ||||
{ | { | ||||
if (server == null) throw new ArgumentNullException(nameof(server)); | if (server == null) throw new ArgumentNullException(nameof(server)); | ||||
return server.GetRole(id); | return server.GetRole(id); | ||||
} | |||||
} | |||||
[Obsolete("Use Server.FindRoles")] | [Obsolete("Use Server.FindRoles")] | ||||
public static IEnumerable<Role> FindRoles(this DiscordClient client, Server server, string name) | public static IEnumerable<Role> FindRoles(this DiscordClient client, Server server, string name) | ||||
{ | { | ||||
if (server == null) throw new ArgumentNullException(nameof(server)); | if (server == null) throw new ArgumentNullException(nameof(server)); | ||||
return server.FindRoles(name); | return server.FindRoles(name); | ||||
} | } | ||||
[Obsolete("Use Server.CreateRole")] | [Obsolete("Use Server.CreateRole")] | ||||
public static Task<Role> CreateRole(this DiscordClient client, Server server, string name, ServerPermissions permissions = null, Color color = null, bool isHoisted = false) | public static Task<Role> CreateRole(this DiscordClient client, Server server, string name, ServerPermissions permissions = null, Color color = null, bool isHoisted = false) | ||||
{ | { | ||||
@@ -320,21 +320,21 @@ namespace Discord.Legacy | |||||
if (role == null) throw new ArgumentNullException(nameof(role)); | if (role == null) throw new ArgumentNullException(nameof(role)); | ||||
return role.Delete(); | return role.Delete(); | ||||
} | } | ||||
[Obsolete("Use Server.ReorderRoles")] | [Obsolete("Use Server.ReorderRoles")] | ||||
public static Task ReorderRoles(this DiscordClient client, Server server, IEnumerable<Role> roles, Role after = null) | public static Task ReorderRoles(this DiscordClient client, Server server, IEnumerable<Role> roles, Role after = null) | ||||
{ | { | ||||
if (server == null) throw new ArgumentNullException(nameof(server)); | if (server == null) throw new ArgumentNullException(nameof(server)); | ||||
return server.ReorderRoles(roles, after); | return server.ReorderRoles(roles, after); | ||||
} | } | ||||
[Obsolete("Use Server.Edit")] | [Obsolete("Use Server.Edit")] | ||||
public static Task EditServer(this DiscordClient client, Server server, string name = null, string region = null, Stream icon = null, ImageType iconType = ImageType.Png) | public static Task EditServer(this DiscordClient client, Server server, string name = null, string region = null, Stream icon = null, ImageType iconType = ImageType.Png) | ||||
{ | { | ||||
if (server == null) throw new ArgumentNullException(nameof(server)); | if (server == null) throw new ArgumentNullException(nameof(server)); | ||||
return server.Edit(name, region, icon, iconType); | return server.Edit(name, region, icon, iconType); | ||||
} | } | ||||
[Obsolete("Use Server.Leave")] | [Obsolete("Use Server.Leave")] | ||||
public static Task LeaveServer(this DiscordClient client, Server server) | public static Task LeaveServer(this DiscordClient client, Server server) | ||||
{ | { |
@@ -2,7 +2,7 @@ | |||||
namespace Discord | namespace Discord | ||||
{ | { | ||||
public sealed class LogMessageEventArgs : EventArgs | |||||
public class LogMessageEventArgs : EventArgs | |||||
{ | { | ||||
public LogSeverity Severity { get; } | public LogSeverity Severity { get; } | ||||
public string Source { get; } | public string Source { get; } |
@@ -2,7 +2,7 @@ | |||||
namespace Discord.Logging | namespace Discord.Logging | ||||
{ | { | ||||
public class LogManager | |||||
public sealed class LogManager | |||||
{ | { | ||||
private readonly DiscordClient _client; | private readonly DiscordClient _client; | ||||
@@ -2,7 +2,7 @@ | |||||
namespace Discord.Logging | namespace Discord.Logging | ||||
{ | { | ||||
public class Logger | |||||
public sealed class Logger | |||||
{ | { | ||||
private readonly LogManager _manager; | private readonly LogManager _manager; | ||||
@@ -5,6 +5,7 @@ namespace Discord | |||||
public class MessageEventArgs : EventArgs | public class MessageEventArgs : EventArgs | ||||
{ | { | ||||
public Message Message { get; } | public Message Message { get; } | ||||
public User User => Message.User; | public User User => Message.User; | ||||
public Channel Channel => Message.Channel; | public Channel Channel => Message.Channel; | ||||
public Server Server => Message.Server; | public Server Server => Message.Server; |
@@ -9,9 +9,9 @@ using System.Threading.Tasks; | |||||
namespace Discord.Net | namespace Discord.Net | ||||
{ | { | ||||
/// <summary> Manages an outgoing message queue for DiscordClient. </summary> | /// <summary> Manages an outgoing message queue for DiscordClient. </summary> | ||||
public class MessageQueue | |||||
public sealed class MessageQueue | |||||
{ | { | ||||
private class MessageQueueItem | |||||
private struct MessageQueueItem | |||||
{ | { | ||||
public readonly ulong Id, ChannelId; | public readonly ulong Id, ChannelId; | ||||
public readonly string Text; | public readonly string Text; | ||||
@@ -2,7 +2,7 @@ | |||||
namespace Discord | namespace Discord | ||||
{ | { | ||||
public class Color | |||||
public sealed class Color | |||||
{ | { | ||||
public static readonly Color Default = PresetColor(0); | public static readonly Color Default = PresetColor(0); | ||||
@@ -65,8 +65,8 @@ namespace Discord | |||||
//Bypasses isLocked for API changes. | //Bypasses isLocked for API changes. | ||||
_rawValue = rawValue; | _rawValue = rawValue; | ||||
} | } | ||||
protected byte GetByte(int pos) => (byte)((_rawValue >> (8 * (pos - 1))) & 0xFF); | |||||
protected void SetByte(int pos, byte value) | |||||
private byte GetByte(int pos) => (byte)((_rawValue >> (8 * (pos - 1))) & 0xFF); | |||||
private void SetByte(int pos, byte value) | |||||
{ | { | ||||
if (_isLocked) | if (_isLocked) | ||||
throw new InvalidOperationException("Unable to edit cached colors directly, use Copy() to make an editable copy."); | throw new InvalidOperationException("Unable to edit cached colors directly, use Copy() to make an editable copy."); | ||||
@@ -9,7 +9,7 @@ using APIMember = Discord.API.Client.Member; | |||||
namespace Discord | namespace Discord | ||||
{ | { | ||||
public class User | |||||
public sealed class User | |||||
{ | { | ||||
internal static string GetAvatarUrl(ulong userId, string avatarId) | internal static string GetAvatarUrl(ulong userId, string avatarId) | ||||
=> avatarId != null ? $"{DiscordConfig.CDNUrl}avatars/{userId}/{avatarId}.jpg" : null; | => avatarId != null ? $"{DiscordConfig.CDNUrl}avatars/{userId}/{avatarId}.jpg" : null; | ||||
@@ -7,7 +7,7 @@ namespace Discord.Net | |||||
#if NET46 | #if NET46 | ||||
[Serializable] | [Serializable] | ||||
#endif | #endif | ||||
public class HttpException : Exception | |||||
public sealed class HttpException : Exception | |||||
{ | { | ||||
public HttpStatusCode StatusCode { get; } | public HttpStatusCode StatusCode { get; } | ||||
@@ -0,0 +1,20 @@ | |||||
using System; | |||||
namespace Discord.Net.Rest | |||||
{ | |||||
public class RequestEventArgs : EventArgs | |||||
{ | |||||
public string Method { get; } | |||||
public string Path { get; } | |||||
public string Payload { get; } | |||||
public double ElapsedMilliseconds { get; } | |||||
public RequestEventArgs(string method, string path, string payload, double milliseconds) | |||||
{ | |||||
Method = method; | |||||
Path = path; | |||||
Payload = payload; | |||||
ElapsedMilliseconds = milliseconds; | |||||
} | |||||
} | |||||
} |
@@ -8,21 +8,6 @@ using System.Threading.Tasks; | |||||
namespace Discord.Net.Rest | namespace Discord.Net.Rest | ||||
{ | { | ||||
public class RequestEventArgs : EventArgs | |||||
{ | |||||
public string Method { get; } | |||||
public string Path { get; } | |||||
public string Payload { get; } | |||||
public double ElapsedMilliseconds { get; } | |||||
public RequestEventArgs(string method, string path, string payload, double milliseconds) | |||||
{ | |||||
Method = method; | |||||
Path = path; | |||||
Payload = payload; | |||||
ElapsedMilliseconds = milliseconds; | |||||
} | |||||
} | |||||
public sealed partial class RestClient | public sealed partial class RestClient | ||||
{ | { | ||||
private readonly DiscordConfig _config; | private readonly DiscordConfig _config; | ||||
@@ -2,7 +2,7 @@ | |||||
namespace Discord.Net | namespace Discord.Net | ||||
{ | { | ||||
public class WebSocketException : Exception | |||||
public sealed class WebSocketException : Exception | |||||
{ | { | ||||
public int Code { get; } | public int Code { get; } | ||||
public string Reason { get; } | public string Reason { get; } | ||||
@@ -0,0 +1,11 @@ | |||||
using System; | |||||
namespace Discord.Net.WebSockets | |||||
{ | |||||
public class BinaryMessageEventArgs : EventArgs | |||||
{ | |||||
public byte[] Data { get; } | |||||
public BinaryMessageEventArgs(byte[] data) { Data = data; } | |||||
} | |||||
} |
@@ -12,7 +12,7 @@ using WebSocketClient = System.Net.WebSockets.ClientWebSocket; | |||||
namespace Discord.Net.WebSockets | namespace Discord.Net.WebSockets | ||||
{ | { | ||||
internal class BuiltInEngine : IWebSocketEngine | |||||
internal sealed class BuiltInEngine : IWebSocketEngine | |||||
{ | { | ||||
private const int ReceiveChunkSize = 12 * 1024; //12KB | private const int ReceiveChunkSize = 12 * 1024; //12KB | ||||
private const int SendChunkSize = 4 * 1024; //4KB | private const int SendChunkSize = 4 * 1024; //4KB | ||||
@@ -23,12 +23,12 @@ namespace Discord.Net.WebSockets | |||||
private WebSocketClient _webSocket; | private WebSocketClient _webSocket; | ||||
private Task _tempTask; | private Task _tempTask; | ||||
public event EventHandler<WebSocketBinaryMessageEventArgs> BinaryMessage = delegate { }; | |||||
public event EventHandler<WebSocketTextMessageEventArgs> TextMessage = delegate { }; | |||||
public event EventHandler<BinaryMessageEventArgs> BinaryMessage = delegate { }; | |||||
public event EventHandler<TextMessageEventArgs> TextMessage = delegate { }; | |||||
private void OnBinaryMessage(byte[] data) | private void OnBinaryMessage(byte[] data) | ||||
=> BinaryMessage(this, new WebSocketBinaryMessageEventArgs(data)); | |||||
=> BinaryMessage(this, new BinaryMessageEventArgs(data)); | |||||
private void OnTextMessage(string msg) | private void OnTextMessage(string msg) | ||||
=> TextMessage(this, new WebSocketTextMessageEventArgs(msg)); | |||||
=> TextMessage(this, new TextMessageEventArgs(msg)); | |||||
internal BuiltInEngine(DiscordConfig config) | internal BuiltInEngine(DiscordConfig config) | ||||
{ | { | ||||
@@ -10,18 +10,7 @@ using System.Threading.Tasks; | |||||
namespace Discord.Net.WebSockets | namespace Discord.Net.WebSockets | ||||
{ | { | ||||
public sealed class WebSocketEventEventArgs : EventArgs | |||||
{ | |||||
public readonly string Type; | |||||
public readonly JToken Payload; | |||||
internal WebSocketEventEventArgs(string type, JToken data) | |||||
{ | |||||
Type = type; | |||||
Payload = data; | |||||
} | |||||
} | |||||
public partial class GatewaySocket : WebSocket | |||||
public sealed class GatewaySocket : WebSocket | |||||
{ | { | ||||
private uint _lastSequence; | private uint _lastSequence; | ||||
private string _sessionId; | private string _sessionId; | ||||
@@ -5,21 +5,10 @@ using System.Threading.Tasks; | |||||
namespace Discord.Net.WebSockets | namespace Discord.Net.WebSockets | ||||
{ | { | ||||
public class WebSocketBinaryMessageEventArgs : EventArgs | |||||
{ | |||||
public readonly byte[] Data; | |||||
public WebSocketBinaryMessageEventArgs(byte[] data) { Data = data; } | |||||
} | |||||
public class WebSocketTextMessageEventArgs : EventArgs | |||||
{ | |||||
public readonly string Message; | |||||
public WebSocketTextMessageEventArgs(string msg) { Message = msg; } | |||||
} | |||||
public interface IWebSocketEngine | public interface IWebSocketEngine | ||||
{ | { | ||||
event EventHandler<WebSocketBinaryMessageEventArgs> BinaryMessage; | |||||
event EventHandler<WebSocketTextMessageEventArgs> TextMessage; | |||||
event EventHandler<BinaryMessageEventArgs> BinaryMessage; | |||||
event EventHandler<TextMessageEventArgs> TextMessage; | |||||
Task Connect(string host, CancellationToken cancelToken); | Task Connect(string host, CancellationToken cancelToken); | ||||
Task Disconnect(); | Task Disconnect(); | ||||
@@ -0,0 +1,11 @@ | |||||
using System; | |||||
namespace Discord.Net.WebSockets | |||||
{ | |||||
public class TextMessageEventArgs : EventArgs | |||||
{ | |||||
public string Message { get; } | |||||
public TextMessageEventArgs(string msg) { Message = msg; } | |||||
} | |||||
} |
@@ -10,7 +10,7 @@ using WebSocketClient = WebSocket4Net.WebSocket; | |||||
namespace Discord.Net.WebSockets | namespace Discord.Net.WebSockets | ||||
{ | { | ||||
internal class WS4NetEngine : IWebSocketEngine | |||||
internal sealed class WS4NetEngine : IWebSocketEngine | |||||
{ | { | ||||
private readonly DiscordConfig _config; | private readonly DiscordConfig _config; | ||||
private readonly ConcurrentQueue<string> _sendQueue; | private readonly ConcurrentQueue<string> _sendQueue; | ||||
@@ -18,12 +18,12 @@ namespace Discord.Net.WebSockets | |||||
private WebSocketClient _webSocket; | private WebSocketClient _webSocket; | ||||
private ManualResetEventSlim _waitUntilConnect, _waitUntilDisconnect; | private ManualResetEventSlim _waitUntilConnect, _waitUntilDisconnect; | ||||
public event EventHandler<WebSocketBinaryMessageEventArgs> BinaryMessage = delegate { }; | |||||
public event EventHandler<WebSocketTextMessageEventArgs> TextMessage = delegate { }; | |||||
public event EventHandler<BinaryMessageEventArgs> BinaryMessage = delegate { }; | |||||
public event EventHandler<TextMessageEventArgs> TextMessage = delegate { }; | |||||
private void OnBinaryMessage(byte[] data) | private void OnBinaryMessage(byte[] data) | ||||
=> BinaryMessage(this, new WebSocketBinaryMessageEventArgs(data)); | |||||
=> BinaryMessage(this, new BinaryMessageEventArgs(data)); | |||||
private void OnTextMessage(string msg) | private void OnTextMessage(string msg) | ||||
=> TextMessage(this, new WebSocketTextMessageEventArgs(msg)); | |||||
=> TextMessage(this, new TextMessageEventArgs(msg)); | |||||
internal WS4NetEngine(DiscordConfig config, TaskManager taskManager) | internal WS4NetEngine(DiscordConfig config, TaskManager taskManager) | ||||
{ | { | ||||
@@ -1,152 +0,0 @@ | |||||
#if DOTNET5_4 | |||||
/*using System; | |||||
using System.Collections.Concurrent; | |||||
using System.Collections.Generic; | |||||
using System.ComponentModel; | |||||
using System.Net.WebSockets; | |||||
using System.Text; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
using State = System.Net.WebSockets.WebSocketState; | |||||
namespace Discord.Net.WebSockets | |||||
{ | |||||
internal class BuiltInWebSocketEngine : IWebSocketEngine | |||||
{ | |||||
private const int ReceiveChunkSize = 4096; | |||||
private const int SendChunkSize = 4096; | |||||
private const int HR_TIMEOUT = -2147012894; | |||||
private readonly ConcurrentQueue<string> _sendQueue; | |||||
private readonly int _sendInterval; | |||||
private ClientWebSocket _webSocket; | |||||
public event EventHandler<WebSocketMessageEventArgs> ProcessMessage; | |||||
private void RaiseProcessMessage(string msg) | |||||
{ | |||||
if (ProcessMessage != null) | |||||
ProcessMessage(this, new WebSocketMessageEventArgs(msg)); | |||||
} | |||||
public BuiltInWebSocketEngine(int sendInterval) | |||||
{ | |||||
_sendInterval = sendInterval; | |||||
_sendQueue = new ConcurrentQueue<string>(); | |||||
} | |||||
public Task Connect(string host, CancellationToken cancelToken) | |||||
{ | |||||
_webSocket = new ClientWebSocket(); | |||||
return _webSocket.ConnectAsync(new Uri(host), cancelToken); | |||||
} | |||||
public Task Disconnect() | |||||
{ | |||||
string ignored; | |||||
while (_sendQueue.TryDequeue(out ignored)) { } | |||||
_webSocket.Dispose(); | |||||
_webSocket = new ClientWebSocket(); | |||||
return TaskHelper.CompletedTask; | |||||
} | |||||
public IEnumerable<Task> GetTasks(CancellationToken cancelToken) | |||||
{ | |||||
return new Task[] | |||||
{ | |||||
ReceiveAsync(cancelToken), | |||||
SendAsync(cancelToken) | |||||
}; | |||||
} | |||||
private Task ReceiveAsync(CancellationToken cancelToken) | |||||
{ | |||||
return Task.Run(async () => | |||||
{ | |||||
var buffer = new ArraySegment<byte>(new byte[ReceiveChunkSize]); | |||||
var builder = new StringBuilder(); | |||||
try | |||||
{ | |||||
while (_webSocket.State == State.Open && !cancelToken.IsCancellationRequested) | |||||
{ | |||||
WebSocketReceiveResult result = null; | |||||
do | |||||
{ | |||||
if (_webSocket.State != State.Open || cancelToken.IsCancellationRequested) | |||||
return; | |||||
try | |||||
{ | |||||
result = await _webSocket.ReceiveAsync(buffer, cancelToken).ConfigureAwait(false); | |||||
} | |||||
catch (Win32Exception ex) when (ex.HResult == HR_TIMEOUT) | |||||
{ | |||||
throw new Exception($"Connection timed out."); | |||||
} | |||||
if (result.MessageType == WebSocketMessageType.Close) | |||||
throw new Exception($"Got Close Message ({result.CloseStatus?.ToString() ?? "Unexpected"}): " + | |||||
result.CloseStatusDescription != "" ? result.CloseStatusDescription : "No Reason"); | |||||
else | |||||
builder.Append(Encoding.UTF8.GetString(buffer.Array, buffer.Offset, result.Count)); | |||||
} | |||||
while (result == null || !result.EndOfMessage); | |||||
RaiseProcessMessage(builder.ToString()); | |||||
builder.Clear(); | |||||
} | |||||
} | |||||
catch (OperationCanceledException) { } | |||||
}); | |||||
} | |||||
private Task SendAsync(CancellationToken cancelToken) | |||||
{ | |||||
return Task.Run(async () => | |||||
{ | |||||
try | |||||
{ | |||||
while (_webSocket.State == State.Open && !cancelToken.IsCancellationRequested) | |||||
{ | |||||
string json; | |||||
while (_sendQueue.TryDequeue(out json)) | |||||
{ | |||||
byte[] bytes = Encoding.UTF8.GetBytes(json); | |||||
int 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; | |||||
try | |||||
{ | |||||
await _webSocket.SendAsync(new ArraySegment<byte>(bytes, offset, count), WebSocketMessageType.Text, isLast, cancelToken).ConfigureAwait(false); | |||||
} | |||||
catch (Win32Exception ex) when (ex.HResult == HR_TIMEOUT) | |||||
{ | |||||
return; | |||||
} | |||||
} | |||||
} | |||||
await Task.Delay(_sendInterval, cancelToken).ConfigureAwait(false); | |||||
} | |||||
} | |||||
catch (OperationCanceledException) { } | |||||
}); | |||||
} | |||||
public void QueueMessage(string message) | |||||
{ | |||||
_sendQueue.Enqueue(message); | |||||
} | |||||
} | |||||
}*/ | |||||
#endif |
@@ -0,0 +1,17 @@ | |||||
using Newtonsoft.Json.Linq; | |||||
using System; | |||||
namespace Discord.Net.WebSockets | |||||
{ | |||||
public class WebSocketEventEventArgs : EventArgs | |||||
{ | |||||
public string Type { get; } | |||||
public JToken Payload { get; } | |||||
internal WebSocketEventEventArgs(string type, JToken data) | |||||
{ | |||||
Type = type; | |||||
Payload = data; | |||||
} | |||||
} | |||||
} |
@@ -1,118 +0,0 @@ | |||||
/*#if !DOTNET5_4 | |||||
using System; | |||||
using System.Collections.Concurrent; | |||||
using System.Collections.Generic; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
using WSSharpWebSocket = WebSocketSharp.WebSocket; | |||||
namespace Discord.Net.WebSockets | |||||
{ | |||||
internal class WebSocketSharpEngine : IWebSocketEngine | |||||
{ | |||||
private readonly DiscordConfig _config; | |||||
private readonly Logger _logger; | |||||
private readonly ConcurrentQueue<string> _sendQueue; | |||||
private readonly WebSocket _parent; | |||||
private WSSharpWebSocket _webSocket; | |||||
public event EventHandler<WebSocketBinaryMessageEventArgs> BinaryMessage; | |||||
public event EventHandler<WebSocketTextMessageEventArgs> TextMessage; | |||||
private void RaiseBinaryMessage(byte[] data) | |||||
{ | |||||
if (BinaryMessage != null) | |||||
BinaryMessage(this, new WebSocketBinaryMessageEventArgs(data)); | |||||
} | |||||
private void RaiseTextMessage(string msg) | |||||
{ | |||||
if (TextMessage != null) | |||||
TextMessage(this, new WebSocketTextMessageEventArgs(msg)); | |||||
} | |||||
internal WebSocketSharpEngine(WebSocket parent, DiscordConfig config, Logger logger) | |||||
{ | |||||
_parent = parent; | |||||
_config = config; | |||||
_logger = logger; | |||||
_sendQueue = new ConcurrentQueue<string>(); | |||||
} | |||||
public Task Connect(string host, CancellationToken cancelToken) | |||||
{ | |||||
_webSocket = new WSSharpWebSocket(host); | |||||
_webSocket.EmitOnPing = false; | |||||
_webSocket.EnableRedirection = true; | |||||
//_webSocket.Compression = WebSocketSharp.CompressionMethod.Deflate; | |||||
_webSocket.SetProxy(null, null, null); //Disable | |||||
//_webSocket.SetProxy(_config.ProxyUrl, _config.ProxyCredentials?.UserName, _config.ProxyCredentials?.Password); | |||||
_webSocket.OnMessage += (s, e) => | |||||
{ | |||||
if (e.IsBinary) | |||||
RaiseBinaryMessage(e.RawData); | |||||
else if (e.IsText) | |||||
RaiseTextMessage(e.Data); | |||||
}; | |||||
_webSocket.OnError += async (s, e) => | |||||
{ | |||||
_logger.Log(LogSeverity.Error, "WebSocket Error", e.Exception); | |||||
await _parent.SignalDisconnect(e.Exception, isUnexpected: true).ConfigureAwait(false); | |||||
}; | |||||
_webSocket.OnClose += async (s, e) => | |||||
{ | |||||
string code = e.WasClean ? e.Code.ToString() : "Unexpected"; | |||||
string reason = e.Reason != "" ? e.Reason : "No Reason"; | |||||
var ex = new Exception($"Got Close Message ({code}): {reason}"); | |||||
await _parent.SignalDisconnect(ex, isUnexpected: true).ConfigureAwait(false); | |||||
}; | |||||
_webSocket.Log.Output = (e, m) => { }; //Dont let websocket-sharp print to console directly | |||||
_webSocket.Connect(); | |||||
return TaskHelper.CompletedTask; | |||||
} | |||||
public Task Disconnect() | |||||
{ | |||||
string ignored; | |||||
while (_sendQueue.TryDequeue(out ignored)) { } | |||||
var socket = _webSocket; | |||||
_webSocket = null; | |||||
if (socket != null) | |||||
socket.Close(); | |||||
return TaskHelper.CompletedTask; | |||||
} | |||||
public IEnumerable<Task> GetTasks(CancellationToken cancelToken) | |||||
{ | |||||
return new Task[] | |||||
{ | |||||
SendAsync(cancelToken) | |||||
}; | |||||
} | |||||
private Task SendAsync(CancellationToken cancelToken) | |||||
{ | |||||
var sendInterval = _config.WebSocketInterval; | |||||
return Task.Run(async () => | |||||
{ | |||||
try | |||||
{ | |||||
while (!cancelToken.IsCancellationRequested) | |||||
{ | |||||
string json; | |||||
while (_sendQueue.TryDequeue(out json)) | |||||
_webSocket.Send(json); | |||||
await Task.Delay(sendInterval, cancelToken).ConfigureAwait(false); | |||||
} | |||||
} | |||||
catch (OperationCanceledException) { } | |||||
}); | |||||
} | |||||
public void QueueMessage(string message) | |||||
{ | |||||
_sendQueue.Enqueue(message); | |||||
} | |||||
} | |||||
} | |||||
#endif*/ |
@@ -1,71 +0,0 @@ | |||||
using System; | |||||
namespace Discord | |||||
{ | |||||
/*internal class Reference<T> | |||||
where T : CachedObject<ulong> | |||||
{ | |||||
private Action<T> _onCache, _onUncache; | |||||
private Func<ulong, T> _getItem; | |||||
private ulong? _id; | |||||
public ulong? Id | |||||
{ | |||||
get { return _id; } | |||||
set | |||||
{ | |||||
_id = value; | |||||
_value = null; | |||||
} | |||||
} | |||||
private T _value; | |||||
public T Value | |||||
{ | |||||
get | |||||
{ | |||||
} | |||||
} | |||||
public T Load() | |||||
{ | |||||
var v = _value; //A little trickery to make this threadsafe | |||||
var id = _id; | |||||
if (v != null && !_value.IsCached) | |||||
{ | |||||
v = null; | |||||
_value = null; | |||||
} | |||||
if (v == null && id != null) | |||||
{ | |||||
v = _getItem(id.Value); | |||||
if (v != null && _onCache != null) | |||||
_onCache(v); | |||||
_value = v; | |||||
} | |||||
return v; | |||||
return Value != null; //Used for precaching | |||||
} | |||||
public void Unload() | |||||
{ | |||||
if (_onUncache != null) | |||||
{ | |||||
var v = _value; | |||||
if (v != null && _onUncache != null) | |||||
_onUncache(v); | |||||
} | |||||
} | |||||
public Reference(Func<ulong, T> onUpdate, Action<T> onCache = null, Action<T> onUncache = null) | |||||
: this(null, onUpdate, onCache, onUncache) | |||||
{ } | |||||
public Reference(ulong? id, Func<ulong, T> getItem, Action<T> onCache = null, Action<T> onUncache = null) | |||||
{ | |||||
_id = id; | |||||
_getItem = getItem; | |||||
_onCache = onCache; | |||||
_onUncache = onUncache; | |||||
_value = null; | |||||
} | |||||
}*/ | |||||
} |
@@ -5,6 +5,7 @@ namespace Discord | |||||
public class RoleEventArgs : EventArgs | public class RoleEventArgs : EventArgs | ||||
{ | { | ||||
public Role Role { get; } | public Role Role { get; } | ||||
public Server Server => Role.Server; | public Server Server => Role.Server; | ||||
public RoleEventArgs(Role role) { Role = role; } | public RoleEventArgs(Role role) { Role = role; } |
@@ -3,7 +3,7 @@ using System.Collections.Generic; | |||||
namespace Discord | namespace Discord | ||||
{ | { | ||||
public class ServiceManager | |||||
public sealed class ServiceManager | |||||
{ | { | ||||
private readonly Dictionary<Type, IService> _services; | private readonly Dictionary<Type, IService> _services; | ||||
@@ -8,7 +8,7 @@ using System.Threading.Tasks; | |||||
namespace Discord | namespace Discord | ||||
{ | { | ||||
/// <summary> Helper class used to manage several tasks and keep them in sync. If any single task errors or stops, all other tasks will also be stopped. </summary> | /// <summary> Helper class used to manage several tasks and keep them in sync. If any single task errors or stops, all other tasks will also be stopped. </summary> | ||||
public class TaskManager | |||||
public sealed class TaskManager | |||||
{ | { | ||||
private readonly object _lock; | private readonly object _lock; | ||||
private readonly Func<Task> _stopAction; | private readonly Func<Task> _stopAction; | ||||
@@ -22,7 +22,7 @@ namespace Discord | |||||
public Exception Exception => _stopReason?.SourceException; | public Exception Exception => _stopReason?.SourceException; | ||||
private ExceptionDispatchInfo _stopReason; | private ExceptionDispatchInfo _stopReason; | ||||
public TaskManager() | |||||
internal TaskManager() | |||||
{ | { | ||||
_lock = new object(); | _lock = new object(); | ||||
} | } | ||||
@@ -4,6 +4,7 @@ namespace Discord | |||||
public class UserEventArgs : EventArgs | public class UserEventArgs : EventArgs | ||||
{ | { | ||||
public User User { get; } | public User User { get; } | ||||
public Server Server => User.Server; | public Server Server => User.Server; | ||||
public UserEventArgs(User user) { User = user; } | public UserEventArgs(User user) { User = user; } |
@@ -1,5 +1,4 @@ | |||||
using Discord.Legacy; | |||||
using Microsoft.VisualStudio.TestTools.UnitTesting; | |||||
using Microsoft.VisualStudio.TestTools.UnitTesting; | |||||
using System; | using System; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Linq; | using System.Linq; | ||||