| @@ -84,9 +84,18 @@ | |||
| <Compile Include="..\Discord.Net.Audio\Sodium\SecretBox.cs"> | |||
| <Link>Sodium\SecretBox.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net.Audio\UserIsTalkingEventArgs.cs"> | |||
| <Link>UserIsTalkingEventArgs.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net.Audio\VoiceBuffer.cs"> | |||
| <Link>VoiceBuffer.cs</Link> | |||
| </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" /> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| @@ -1,49 +1,10 @@ | |||
| using Discord.Net.WebSockets; | |||
| using System; | |||
| using System; | |||
| using System.Collections.Concurrent; | |||
| using System.Linq; | |||
| using System.Threading.Tasks; | |||
| 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 | |||
| { | |||
| private AudioClient _defaultClient; | |||
| @@ -51,46 +12,33 @@ namespace Discord.Audio | |||
| private ConcurrentDictionary<User, bool> _talkingUsers; | |||
| //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) | |||
| { | |||
| _config = config; | |||
| _config.Lock(); | |||
| Config = config; | |||
| } | |||
| public void Install(DiscordClient client) | |||
| { | |||
| _client = client; | |||
| if (Config.EnableMultiserver) | |||
| Client = client; | |||
| Config.Lock(); | |||
| if (Config.EnableMultiserver) | |||
| _voiceClients = new ConcurrentDictionary<ulong, IAudioClient>(); | |||
| else | |||
| { | |||
| @@ -113,7 +61,7 @@ namespace Discord.Audio | |||
| { | |||
| bool 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"> | |||
| <Link>CommandBuilder.cs</Link> | |||
| </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"> | |||
| <Link>CommandExtensions.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net.Commands\CommandMap.cs"> | |||
| <Link>CommandMap.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net.Commands\CommandParameter.cs"> | |||
| <Link>CommandParameter.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net.Commands\CommandParser.cs"> | |||
| <Link>CommandParser.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net.Commands\CommandService.cs"> | |||
| <Link>CommandService.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net.Commands\CommandService.Events.cs"> | |||
| <Link>CommandService.Events.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net.Commands\CommandServiceConfig.cs"> | |||
| <Link>CommandServiceConfig.cs</Link> | |||
| </Compile> | |||
| @@ -5,48 +5,24 @@ using System.Threading.Tasks; | |||
| 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 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 bool IsHidden { get; internal set; } | |||
| public string Description { get; internal set; } | |||
| public IEnumerable<string> Aliases => _aliases; | |||
| private string[] _aliases; | |||
| 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; | |||
| IsHidden = false; | |||
| @@ -55,7 +31,6 @@ namespace Discord.Commands | |||
| _parametersByName = new Dictionary<string, CommandParameter>(); | |||
| } | |||
| public CommandParameter this[string name] => _parametersByName[name]; | |||
| 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 | |||
| { | |||
| /// <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 | |||
| 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 | |||
| 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>(); | |||
| _map = new CommandMap(null, "", ""); | |||
| _categories = new Dictionary<string, CommandMap>(); | |||
| _root = new CommandGroupBuilder(this, "", null); | |||
| Root = new CommandGroupBuilder(this, "", null); | |||
| } | |||
| 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") | |||
| .Parameter("command", ParameterType.Multiple) | |||
| @@ -49,7 +52,7 @@ namespace Discord.Commands | |||
| .Description("Returns information about commands.") | |||
| .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 | |||
| { | |||
| var map = _map.GetItem(string.Join(" ", e.Args)); | |||
| @@ -66,13 +69,13 @@ namespace Discord.Commands | |||
| client.MessageReceived += async (s, e) => | |||
| { | |||
| 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; | |||
| if (msg.Length == 0) return; | |||
| //Check for command char if one is provided | |||
| var chars = _config.CommandChars; | |||
| var chars = Config.CommandChars; | |||
| if (chars.Length > 0) | |||
| { | |||
| if (!chars.Contains(msg[0])) | |||
| @@ -87,7 +90,7 @@ namespace Discord.Commands | |||
| if (commands == null) | |||
| { | |||
| CommandEventArgs errorArgs = new CommandEventArgs(e.Message, null, null); | |||
| RaiseCommandError(CommandErrorType.UnknownCommand, errorArgs); | |||
| OnCommandError(CommandErrorType.UnknownCommand, errorArgs); | |||
| return; | |||
| } | |||
| else | |||
| @@ -104,7 +107,7 @@ namespace Discord.Commands | |||
| else | |||
| { | |||
| var errorArgs = new CommandEventArgs(e.Message, command, null); | |||
| RaiseCommandError(error.Value, errorArgs); | |||
| OnCommandError(error.Value, errorArgs); | |||
| return; | |||
| } | |||
| } | |||
| @@ -115,24 +118,24 @@ namespace Discord.Commands | |||
| string 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; | |||
| } | |||
| // Run the command | |||
| try | |||
| { | |||
| RaiseRanCommand(eventArgs); | |||
| OnCommand(eventArgs); | |||
| await command.Run(eventArgs).ConfigureAwait(false); | |||
| } | |||
| catch (Exception ex) | |||
| { | |||
| RaiseCommandError(CommandErrorType.Exception, eventArgs, ex); | |||
| OnCommandError(CommandErrorType.Exception, eventArgs, ex); | |||
| } | |||
| return; | |||
| } | |||
| 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"); | |||
| var chars = _config.CommandChars; | |||
| var chars = Config.CommandChars; | |||
| if (chars.Length > 0) | |||
| { | |||
| if (chars.Length == 1) | |||
| @@ -294,8 +297,8 @@ namespace Discord.Commands | |||
| 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) | |||
| { | |||
| @@ -6,7 +6,7 @@ using System.Linq; | |||
| namespace Discord.Modules | |||
| { | |||
| public class ModuleManager | |||
| public sealed class ModuleManager | |||
| { | |||
| public event EventHandler<ServerEventArgs> ServerEnabled = delegate { }; | |||
| public event EventHandler<ServerEventArgs> ServerDisabled = delegate { }; | |||
| @@ -5,21 +5,19 @@ namespace Discord.Modules | |||
| { | |||
| public class ModuleService : IService | |||
| { | |||
| private DiscordClient _client; | |||
| //ModuleServiceConfig Config { get; } | |||
| public DiscordClient Client { get; private set; } | |||
| public IEnumerable<ModuleManager> Modules => _modules.Values; | |||
| private readonly Dictionary<IModule, ModuleManager> _modules; | |||
| public ModuleService(/*ModuleServiceConfig config*/) | |||
| public ModuleService() | |||
| { | |||
| //Config = config; | |||
| _modules = new Dictionary<IModule, ModuleManager>(); | |||
| } | |||
| void IService.Install(DiscordClient client) | |||
| { | |||
| _client = client; | |||
| Client = client; | |||
| } | |||
| 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 (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); | |||
| module.Install(manager); | |||
| } | |||
| @@ -391,15 +391,24 @@ | |||
| <Compile Include="..\Discord.Net\API\Status\Rest\Upcoming.cs"> | |||
| <Link>API\Status\Rest\Upcoming.cs</Link> | |||
| </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"> | |||
| <Link>DiscordClient.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\DiscordClient.Events.cs"> | |||
| <Link>DiscordClient.Events.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\DiscordClient.Obsolete.cs"> | |||
| <Link>DiscordClient.Obsolete.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\DiscordConfig.cs"> | |||
| <Link>DiscordConfig.cs</Link> | |||
| </Compile> | |||
| @@ -421,33 +430,6 @@ | |||
| <Compile Include="..\Discord.Net\Enums\UserStatus.cs"> | |||
| <Link>Enums\UserStatus.cs</Link> | |||
| </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"> | |||
| <Link>Extensions.cs</Link> | |||
| </Compile> | |||
| @@ -457,12 +439,21 @@ | |||
| <Compile Include="..\Discord.Net\IService.cs"> | |||
| <Link>IService.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\Legacy.cs"> | |||
| <Link>Legacy.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\Logging\Logger.cs"> | |||
| <Link>Logging\Logger.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\Logging\LogManager.cs"> | |||
| <Link>Logging\LogManager.cs</Link> | |||
| </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"> | |||
| <Link>MessageQueue.cs</Link> | |||
| </Compile> | |||
| @@ -502,6 +493,9 @@ | |||
| <Compile Include="..\Discord.Net\Net\Rest\IRestEngine.cs"> | |||
| <Link>Net\Rest\IRestEngine.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\Net\Rest\RequestEventArgs.cs"> | |||
| <Link>Net\Rest\RequestEventArgs.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\Net\Rest\RestClient.cs"> | |||
| <Link>Net\Rest\RestClient.cs</Link> | |||
| </Compile> | |||
| @@ -514,6 +508,9 @@ | |||
| <Compile Include="..\Discord.Net\Net\WebSocketException.cs"> | |||
| <Link>Net\WebSockets\WebSocketException.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\Net\WebSockets\BinaryMessageEventArgs.cs"> | |||
| <Link>Net\WebSockets\BinaryMessageEventArgs.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\Net\WebSockets\BuiltInEngine.cs"> | |||
| <Link>Net\WebSockets\BuiltInEngine.cs</Link> | |||
| </Compile> | |||
| @@ -523,30 +520,39 @@ | |||
| <Compile Include="..\Discord.Net\Net\WebSockets\IWebSocketEngine.cs"> | |||
| <Link>Net\WebSockets\IWebSocketEngine.cs</Link> | |||
| </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 Include="..\Discord.Net\Net\WebSockets\WebSocket.cs"> | |||
| <Link>Net\WebSockets\WebSocket.cs</Link> | |||
| </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 Include="..\Discord.Net\Net\WebSockets\WS4NetEngine.cs"> | |||
| <Link>Net\WebSockets\WS4NetEngine.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\Reference.cs"> | |||
| <Link>Reference.cs</Link> | |||
| <Compile Include="..\Discord.Net\ProfileEventArgs.cs"> | |||
| <Link>ProfileEventArgs.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\RelativeDirection.cs"> | |||
| <Link>RelativeDirection.cs</Link> | |||
| </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"> | |||
| <Link>ServiceManager.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\TaskManager.cs"> | |||
| <Link>TaskManager.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\UserEventArgs.cs"> | |||
| <Link>UserEventArgs.cs</Link> | |||
| </Compile> | |||
| <Compile Include="Properties\AssemblyInfo.cs" /> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| @@ -4,7 +4,7 @@ using System.Collections.Generic; | |||
| namespace Discord.API.Client.GatewaySocket | |||
| { | |||
| [JsonObject(MemberSerialization.OptIn)] | |||
| internal sealed class IdentifyCommand : IWebSocketMessage | |||
| public sealed class IdentifyCommand : IWebSocketMessage | |||
| { | |||
| int IWebSocketMessage.OpCode => (int)OpCodes.Identify; | |||
| object IWebSocketMessage.Payload => this; | |||
| @@ -4,7 +4,7 @@ using Newtonsoft.Json; | |||
| namespace Discord.API.Client.GatewaySocket | |||
| { | |||
| [JsonObject(MemberSerialization.OptIn)] | |||
| internal sealed class RequestMembersCommand : IWebSocketMessage | |||
| public sealed class RequestMembersCommand : IWebSocketMessage | |||
| { | |||
| int IWebSocketMessage.OpCode => (int)OpCodes.RequestGuildMembers; | |||
| 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 | |||
| { | |||
| //public sealed class GuildEmojisUpdate { } | |||
| //public sealed class GuildEmojisUpdateEvent { } | |||
| } | |||
| @@ -8,6 +8,6 @@ namespace Discord.API.Client.GatewaySocket | |||
| [JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] | |||
| public ulong GuildId { get; set; } | |||
| [JsonProperty("members")] | |||
| public Member[] Members; | |||
| public Member[] Members { get; set; } | |||
| } | |||
| } | |||
| @@ -8,7 +8,7 @@ namespace Discord.API.Client | |||
| object Payload { get; } | |||
| bool IsPrivate { get; } | |||
| } | |||
| public class WebSocketMessage | |||
| public sealed class WebSocketMessage | |||
| { | |||
| [JsonProperty("op")] | |||
| public int? Operation { get; set; } | |||
| @@ -4,7 +4,7 @@ using System.Collections.Generic; | |||
| namespace Discord.API.Converters | |||
| { | |||
| public class LongStringConverter : JsonConverter | |||
| public sealed class LongStringConverter : JsonConverter | |||
| { | |||
| public override bool CanConvert(Type objectType) | |||
| => objectType == typeof(ulong); | |||
| @@ -14,7 +14,7 @@ namespace Discord.API.Converters | |||
| => writer.WriteValue(((ulong)value).ToIdString()); | |||
| } | |||
| public class NullableLongStringConverter : JsonConverter | |||
| public sealed class NullableLongStringConverter : JsonConverter | |||
| { | |||
| public override bool CanConvert(Type objectType) | |||
| => objectType == typeof(ulong?); | |||
| @@ -24,7 +24,7 @@ namespace Discord.API.Converters | |||
| => 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 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 object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) | |||
| @@ -5,6 +5,7 @@ namespace Discord | |||
| public class ChannelEventArgs : EventArgs | |||
| { | |||
| public Channel Channel { get; } | |||
| public Server Server => Channel.Server; | |||
| 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, | |||
| 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> | |||
| { | |||
| @@ -1,6 +1,6 @@ | |||
| namespace Discord | |||
| { | |||
| public class ChannelType : StringEnum | |||
| public sealed class ChannelType : StringEnum | |||
| { | |||
| /// <summary> A text-only channel. </summary> | |||
| public static ChannelType Text { get; } = new ChannelType("text"); | |||
| @@ -1,6 +1,6 @@ | |||
| namespace Discord | |||
| { | |||
| public class PermissionTarget : StringEnum | |||
| public sealed class PermissionTarget : StringEnum | |||
| { | |||
| /// <summary> A text-only channel. </summary> | |||
| public static PermissionTarget Role { get; } = new PermissionTarget("role"); | |||
| @@ -1,6 +1,6 @@ | |||
| namespace Discord | |||
| { | |||
| public class UserStatus : StringEnum | |||
| public sealed class UserStatus : StringEnum | |||
| { | |||
| /// <summary> User is currently online and active. </summary> | |||
| public static UserStatus Online { get; } = new UserStatus("online"); | |||
| @@ -3,7 +3,7 @@ using System.Collections.Generic; | |||
| using System.IO; | |||
| using System.Threading.Tasks; | |||
| namespace Discord.Legacy | |||
| namespace Discord | |||
| { | |||
| public static class Mention | |||
| { | |||
| @@ -31,19 +31,19 @@ namespace Discord.Legacy | |||
| if (server == null) throw new ArgumentNullException(nameof(server)); | |||
| return server.FindChannels(name, type, exactMatch); | |||
| } | |||
| [Obsolete("Use Server.CreateChannel")] | |||
| public static Task<Channel> CreateChannel(this DiscordClient client, Server server, string name, ChannelType type) | |||
| { | |||
| if (server == null) throw new ArgumentNullException(nameof(server)); | |||
| return server.CreateChannel(name, type); | |||
| } | |||
| } | |||
| [Obsolete("Use User.CreateChannel")] | |||
| public static Task<Channel> CreatePMChannel(this DiscordClient client, User user) | |||
| { | |||
| if (user == null) throw new ArgumentNullException(nameof(user)); | |||
| return user.CreatePMChannel(); | |||
| } | |||
| } | |||
| [Obsolete("Use Channel.Edit")] | |||
| 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)); | |||
| return server.ReorderChannels(channels, after); | |||
| } | |||
| } | |||
| [Obsolete("Use Server.GetInvites")] | |||
| public static Task<IEnumerable<Invite>> GetInvites(this DiscordClient client, Server server) | |||
| { | |||
| if (server == null) throw new ArgumentNullException(nameof(server)); | |||
| return server.GetInvites(); | |||
| } | |||
| [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) | |||
| { | |||
| @@ -83,20 +83,20 @@ namespace Discord.Legacy | |||
| if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||
| return channel.CreateInvite(maxAge, maxUses, tempMembership, withXkcd); | |||
| } | |||
| [Obsolete("Use Invite.Delete")] | |||
| public static Task DeleteInvite(this DiscordClient client, Invite invite) | |||
| { | |||
| if (invite == null) throw new ArgumentNullException(nameof(invite)); | |||
| return invite.Delete(); | |||
| } | |||
| } | |||
| [Obsolete("Use Invite.Accept")] | |||
| public static Task AcceptInvite(this DiscordClient client, Invite invite) | |||
| { | |||
| if (invite == null) throw new ArgumentNullException(nameof(invite)); | |||
| return invite.Accept(); | |||
| } | |||
| [Obsolete("Use Channel.SendMessage")] | |||
| 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)); | |||
| return user.SendFile(filename, stream); | |||
| } | |||
| [Obsolete("Use Message.Edit")] | |||
| public static Task EditMessage(this DiscordClient client, Message message, string text) | |||
| { | |||
| if (message == null) throw new ArgumentNullException(nameof(message)); | |||
| return message.Edit(text); | |||
| } | |||
| [Obsolete("Use Message.Delete")] | |||
| public static Task DeleteMessage(this DiscordClient client, Message message) | |||
| { | |||
| @@ -161,14 +161,14 @@ namespace Discord.Legacy | |||
| foreach (var message in messages) | |||
| await message.Delete().ConfigureAwait(false); | |||
| } | |||
| [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) | |||
| { | |||
| if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||
| return channel.DownloadMessages(limit, relativeMessageId, relativeDir, useCache); | |||
| } | |||
| [Obsolete("Use Message.Acknowledge")] | |||
| public static Task AckMessage(this DiscordClient client, Message message) | |||
| { | |||
| @@ -212,7 +212,7 @@ namespace Discord.Legacy | |||
| return JsonConvert.SerializeObject(channel.Messages); | |||
| }*/ | |||
| [Obsolete("Use Server.GetUser")] | |||
| 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)); | |||
| return server.GetUser(username, discriminator); | |||
| } | |||
| [Obsolete("Use Server.FindUsers")] | |||
| 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, | |||
| Stream avatar = null, ImageType avatarType = ImageType.Png) | |||
| => client.CurrentUser.Edit(currentPassword, username, email, password, avatar, avatarType); | |||
| [Obsolete("Use Server.GetRole")] | |||
| public static Role GetRole(this DiscordClient client, Server server, ulong id) | |||
| { | |||
| if (server == null) throw new ArgumentNullException(nameof(server)); | |||
| return server.GetRole(id); | |||
| } | |||
| } | |||
| [Obsolete("Use Server.FindRoles")] | |||
| public static IEnumerable<Role> FindRoles(this DiscordClient client, Server server, string name) | |||
| { | |||
| if (server == null) throw new ArgumentNullException(nameof(server)); | |||
| return server.FindRoles(name); | |||
| } | |||
| [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) | |||
| { | |||
| @@ -320,21 +320,21 @@ namespace Discord.Legacy | |||
| if (role == null) throw new ArgumentNullException(nameof(role)); | |||
| return role.Delete(); | |||
| } | |||
| [Obsolete("Use Server.ReorderRoles")] | |||
| public static Task ReorderRoles(this DiscordClient client, Server server, IEnumerable<Role> roles, Role after = null) | |||
| { | |||
| if (server == null) throw new ArgumentNullException(nameof(server)); | |||
| return server.ReorderRoles(roles, after); | |||
| } | |||
| [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) | |||
| { | |||
| if (server == null) throw new ArgumentNullException(nameof(server)); | |||
| return server.Edit(name, region, icon, iconType); | |||
| } | |||
| [Obsolete("Use Server.Leave")] | |||
| public static Task LeaveServer(this DiscordClient client, Server server) | |||
| { | |||
| @@ -2,7 +2,7 @@ | |||
| namespace Discord | |||
| { | |||
| public sealed class LogMessageEventArgs : EventArgs | |||
| public class LogMessageEventArgs : EventArgs | |||
| { | |||
| public LogSeverity Severity { get; } | |||
| public string Source { get; } | |||
| @@ -2,7 +2,7 @@ | |||
| namespace Discord.Logging | |||
| { | |||
| public class LogManager | |||
| public sealed class LogManager | |||
| { | |||
| private readonly DiscordClient _client; | |||
| @@ -2,7 +2,7 @@ | |||
| namespace Discord.Logging | |||
| { | |||
| public class Logger | |||
| public sealed class Logger | |||
| { | |||
| private readonly LogManager _manager; | |||
| @@ -5,6 +5,7 @@ namespace Discord | |||
| public class MessageEventArgs : EventArgs | |||
| { | |||
| public Message Message { get; } | |||
| public User User => Message.User; | |||
| public Channel Channel => Message.Channel; | |||
| public Server Server => Message.Server; | |||
| @@ -9,9 +9,9 @@ using System.Threading.Tasks; | |||
| namespace Discord.Net | |||
| { | |||
| /// <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 string Text; | |||
| @@ -2,7 +2,7 @@ | |||
| namespace Discord | |||
| { | |||
| public class Color | |||
| public sealed class Color | |||
| { | |||
| public static readonly Color Default = PresetColor(0); | |||
| @@ -65,8 +65,8 @@ namespace Discord | |||
| //Bypasses isLocked for API changes. | |||
| _rawValue = rawValue; | |||
| } | |||
| protected byte GetByte(int pos) => (byte)((_rawValue >> (8 * (pos - 1))) & 0xFF); | |||
| protected void SetByte(int pos, byte value) | |||
| private byte GetByte(int pos) => (byte)((_rawValue >> (8 * (pos - 1))) & 0xFF); | |||
| private void SetByte(int pos, byte value) | |||
| { | |||
| if (_isLocked) | |||
| 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 | |||
| { | |||
| public class User | |||
| public sealed class User | |||
| { | |||
| internal static string GetAvatarUrl(ulong userId, string avatarId) | |||
| => avatarId != null ? $"{DiscordConfig.CDNUrl}avatars/{userId}/{avatarId}.jpg" : null; | |||
| @@ -7,7 +7,7 @@ namespace Discord.Net | |||
| #if NET46 | |||
| [Serializable] | |||
| #endif | |||
| public class HttpException : Exception | |||
| public sealed class HttpException : Exception | |||
| { | |||
| 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 | |||
| { | |||
| 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 | |||
| { | |||
| private readonly DiscordConfig _config; | |||
| @@ -2,7 +2,7 @@ | |||
| namespace Discord.Net | |||
| { | |||
| public class WebSocketException : Exception | |||
| public sealed class WebSocketException : Exception | |||
| { | |||
| public int Code { 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 | |||
| { | |||
| internal class BuiltInEngine : IWebSocketEngine | |||
| internal sealed class BuiltInEngine : IWebSocketEngine | |||
| { | |||
| private const int ReceiveChunkSize = 12 * 1024; //12KB | |||
| private const int SendChunkSize = 4 * 1024; //4KB | |||
| @@ -23,12 +23,12 @@ namespace Discord.Net.WebSockets | |||
| private WebSocketClient _webSocket; | |||
| 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) | |||
| => BinaryMessage(this, new WebSocketBinaryMessageEventArgs(data)); | |||
| => BinaryMessage(this, new BinaryMessageEventArgs(data)); | |||
| private void OnTextMessage(string msg) | |||
| => TextMessage(this, new WebSocketTextMessageEventArgs(msg)); | |||
| => TextMessage(this, new TextMessageEventArgs(msg)); | |||
| internal BuiltInEngine(DiscordConfig config) | |||
| { | |||
| @@ -10,18 +10,7 @@ using System.Threading.Tasks; | |||
| 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 string _sessionId; | |||
| @@ -5,21 +5,10 @@ using System.Threading.Tasks; | |||
| 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 | |||
| { | |||
| event EventHandler<WebSocketBinaryMessageEventArgs> BinaryMessage; | |||
| event EventHandler<WebSocketTextMessageEventArgs> TextMessage; | |||
| event EventHandler<BinaryMessageEventArgs> BinaryMessage; | |||
| event EventHandler<TextMessageEventArgs> TextMessage; | |||
| Task Connect(string host, CancellationToken cancelToken); | |||
| 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 | |||
| { | |||
| internal class WS4NetEngine : IWebSocketEngine | |||
| internal sealed class WS4NetEngine : IWebSocketEngine | |||
| { | |||
| private readonly DiscordConfig _config; | |||
| private readonly ConcurrentQueue<string> _sendQueue; | |||
| @@ -18,12 +18,12 @@ namespace Discord.Net.WebSockets | |||
| private WebSocketClient _webSocket; | |||
| 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) | |||
| => BinaryMessage(this, new WebSocketBinaryMessageEventArgs(data)); | |||
| => BinaryMessage(this, new BinaryMessageEventArgs(data)); | |||
| private void OnTextMessage(string msg) | |||
| => TextMessage(this, new WebSocketTextMessageEventArgs(msg)); | |||
| => TextMessage(this, new TextMessageEventArgs(msg)); | |||
| 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 Role Role { get; } | |||
| public Server Server => Role.Server; | |||
| public RoleEventArgs(Role role) { Role = role; } | |||
| @@ -3,7 +3,7 @@ using System.Collections.Generic; | |||
| namespace Discord | |||
| { | |||
| public class ServiceManager | |||
| public sealed class ServiceManager | |||
| { | |||
| private readonly Dictionary<Type, IService> _services; | |||
| @@ -8,7 +8,7 @@ using System.Threading.Tasks; | |||
| 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> | |||
| public class TaskManager | |||
| public sealed class TaskManager | |||
| { | |||
| private readonly object _lock; | |||
| private readonly Func<Task> _stopAction; | |||
| @@ -22,7 +22,7 @@ namespace Discord | |||
| public Exception Exception => _stopReason?.SourceException; | |||
| private ExceptionDispatchInfo _stopReason; | |||
| public TaskManager() | |||
| internal TaskManager() | |||
| { | |||
| _lock = new object(); | |||
| } | |||
| @@ -4,6 +4,7 @@ namespace Discord | |||
| public class UserEventArgs : EventArgs | |||
| { | |||
| public User User { get; } | |||
| public Server Server => User.Server; | |||
| 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.Collections.Generic; | |||
| using System.Linq; | |||