@@ -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; | |||