| @@ -7,7 +7,6 @@ using Newtonsoft.Json; | |||||
| using Nito.AsyncEx; | using Nito.AsyncEx; | ||||
| using System; | using System; | ||||
| using System.Diagnostics; | using System.Diagnostics; | ||||
| using System.IO; | |||||
| using System.Threading; | using System.Threading; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| @@ -15,35 +14,6 @@ namespace Discord.Audio | |||||
| { | { | ||||
| internal class AudioClient : IAudioClient | internal class AudioClient : IAudioClient | ||||
| { | { | ||||
| private class OutStream : Stream | |||||
| { | |||||
| public override bool CanRead => false; | |||||
| public override bool CanSeek => false; | |||||
| public override bool CanWrite => true; | |||||
| private readonly AudioClient _client; | |||||
| internal OutStream(AudioClient client) | |||||
| { | |||||
| _client = client; | |||||
| } | |||||
| public override long Length { get { throw new InvalidOperationException(); } } | |||||
| public override long Position | |||||
| { | |||||
| get { throw new InvalidOperationException(); } | |||||
| set { throw new InvalidOperationException(); } | |||||
| } | |||||
| public override void Flush() { throw new InvalidOperationException(); } | |||||
| public override long Seek(long offset, SeekOrigin origin) { throw new InvalidOperationException(); } | |||||
| public override void SetLength(long value) { throw new InvalidOperationException(); } | |||||
| public override int Read(byte[] buffer, int offset, int count) { throw new InvalidOperationException(); } | |||||
| public override void Write(byte[] buffer, int offset, int count) | |||||
| { | |||||
| _client.Send(buffer, offset, count); | |||||
| } | |||||
| } | |||||
| private readonly DiscordConfig _config; | private readonly DiscordConfig _config; | ||||
| private readonly AsyncLock _connectionLock; | private readonly AsyncLock _connectionLock; | ||||
| private readonly TaskManager _taskManager; | private readonly TaskManager _taskManager; | ||||
| @@ -58,14 +28,13 @@ namespace Discord.Audio | |||||
| public GatewaySocket GatewaySocket { get; } | public GatewaySocket GatewaySocket { get; } | ||||
| public VoiceSocket VoiceSocket { get; } | public VoiceSocket VoiceSocket { get; } | ||||
| public JsonSerializer Serializer { get; } | public JsonSerializer Serializer { get; } | ||||
| public Stream OutputStream { get; } | |||||
| public CancellationToken CancelToken { get; private set; } | public CancellationToken CancelToken { get; private set; } | ||||
| public string SessionId => GatewaySocket.SessionId; | public string SessionId => GatewaySocket.SessionId; | ||||
| public ConnectionState State => VoiceSocket.State; | public ConnectionState State => VoiceSocket.State; | ||||
| public Server Server => VoiceSocket.Server; | public Server Server => VoiceSocket.Server; | ||||
| public Channel Channel => VoiceSocket.Channel; | |||||
| public VoiceChannel Channel => VoiceSocket.Channel; | |||||
| public AudioClient(DiscordClient client, Server server, int id) | public AudioClient(DiscordClient client, Server server, int id) | ||||
| { | { | ||||
| @@ -121,7 +90,6 @@ namespace Discord.Audio | |||||
| GatewaySocket.ReceivedDispatch += (s, e) => OnReceivedEvent(e); | GatewaySocket.ReceivedDispatch += (s, e) => OnReceivedEvent(e); | ||||
| VoiceSocket = new VoiceSocket(_config, Config, client.Serializer, client.Log.CreateLogger($"Voice #{id}")); | VoiceSocket = new VoiceSocket(_config, Config, client.Serializer, client.Log.CreateLogger($"Voice #{id}")); | ||||
| VoiceSocket.Server = server; | VoiceSocket.Server = server; | ||||
| OutputStream = new OutStream(this); | |||||
| } | } | ||||
| public async Task Connect() | public async Task Connect() | ||||
| @@ -216,7 +184,7 @@ namespace Discord.Audio | |||||
| _gatewayState = (int)ConnectionState.Disconnected; | _gatewayState = (int)ConnectionState.Disconnected; | ||||
| } | } | ||||
| public async Task Join(Channel channel) | |||||
| public async Task Join(VoiceChannel channel) | |||||
| { | { | ||||
| if (channel == null) throw new ArgumentNullException(nameof(channel)); | if (channel == null) throw new ArgumentNullException(nameof(channel)); | ||||
| if (channel.Type != ChannelType.Voice) | if (channel.Type != ChannelType.Voice) | ||||
| @@ -248,7 +216,7 @@ namespace Discord.Audio | |||||
| await Disconnect().ConfigureAwait(false); | await Disconnect().ConfigureAwait(false); | ||||
| else | else | ||||
| { | { | ||||
| var channel = Service.Client.GetChannel(data.ChannelId.Value); | |||||
| var channel = Service.Client.GetChannel(data.ChannelId.Value) as VoiceChannel; | |||||
| if (channel != null) | if (channel != null) | ||||
| VoiceSocket.Channel = channel; | VoiceSocket.Channel = channel; | ||||
| else | else | ||||
| @@ -18,8 +18,8 @@ namespace Discord.Audio | |||||
| return client; | return client; | ||||
| } | } | ||||
| public static Task<IAudioClient> JoinAudio(this Channel channel) => channel.Client.GetService<AudioService>().Join(channel); | |||||
| public static Task LeaveAudio(this Channel channel) => channel.Client.GetService<AudioService>().Leave(channel); | |||||
| public static Task<IAudioClient> JoinAudio(this VoiceChannel channel) => channel.Client.GetService<AudioService>().Join(channel); | |||||
| public static Task LeaveAudio(this VoiceChannel channel) => channel.Client.GetService<AudioService>().Leave(channel); | |||||
| public static Task LeaveAudio(this Server server) => server.Client.GetService<AudioService>().Leave(server); | public static Task LeaveAudio(this Server server) => server.Client.GetService<AudioService>().Leave(server); | ||||
| public static IAudioClient GetAudioClient(this Server server) => server.Client.GetService<AudioService>().GetClient(server); | public static IAudioClient GetAudioClient(this Server server) => server.Client.GetService<AudioService>().GetClient(server); | ||||
| } | } | ||||
| @@ -113,7 +113,7 @@ namespace Discord.Audio | |||||
| } | } | ||||
| } | } | ||||
| public async Task<IAudioClient> Join(Channel channel) | |||||
| public async Task<IAudioClient> Join(VoiceChannel channel) | |||||
| { | { | ||||
| if (channel == null) throw new ArgumentNullException(nameof(channel)); | if (channel == null) throw new ArgumentNullException(nameof(channel)); | ||||
| @@ -163,8 +163,8 @@ namespace Discord.Audio | |||||
| } | } | ||||
| public Task Leave(Server server) => Leave(server, null); | public Task Leave(Server server) => Leave(server, null); | ||||
| public Task Leave(Channel channel) => Leave(channel.Server, channel); | |||||
| private async Task Leave(Server server, Channel channel) | |||||
| public Task Leave(VoiceChannel channel) => Leave(channel.Server, channel); | |||||
| private async Task Leave(Server server, VoiceChannel channel) | |||||
| { | { | ||||
| if (server == null) throw new ArgumentNullException(nameof(server)); | if (server == null) throw new ArgumentNullException(nameof(server)); | ||||
| @@ -15,11 +15,9 @@ namespace Discord.Audio | |||||
| /// <summary> Gets the current state of this client. </summary> | /// <summary> Gets the current state of this client. </summary> | ||||
| ConnectionState State { get; } | ConnectionState State { get; } | ||||
| /// <summary> Gets the channel this client is currently a member of. </summary> | /// <summary> Gets the channel this client is currently a member of. </summary> | ||||
| Channel Channel { get; } | |||||
| VoiceChannel Channel { get; } | |||||
| /// <summary> Gets the server this client is bound to. </summary> | /// <summary> Gets the server this client is bound to. </summary> | ||||
| Server Server { get; } | Server Server { get; } | ||||
| /// <summary> Gets a stream object that wraps the Send() function. </summary> | |||||
| Stream OutputStream { get; } | |||||
| /// <summary> Gets a cancellation token that triggers when the client is manually disconnected. </summary> | /// <summary> Gets a cancellation token that triggers when the client is manually disconnected. </summary> | ||||
| CancellationToken CancelToken { get; } | CancellationToken CancelToken { get; } | ||||
| @@ -31,7 +29,7 @@ namespace Discord.Audio | |||||
| VoiceSocket VoiceSocket { get; } | VoiceSocket VoiceSocket { get; } | ||||
| /// <summary> Moves the client to another channel on the same server. </summary> | /// <summary> Moves the client to another channel on the same server. </summary> | ||||
| Task Join(Channel channel); | |||||
| Task Join(VoiceChannel channel); | |||||
| /// <summary> Disconnects from the Discord server, canceling any pending requests. </summary> | /// <summary> Disconnects from the Discord server, canceling any pending requests. </summary> | ||||
| Task Disconnect(); | Task Disconnect(); | ||||
| @@ -46,7 +46,7 @@ namespace Discord.Net.WebSockets | |||||
| public string Token { get; internal set; } | public string Token { get; internal set; } | ||||
| public Server Server { get; internal set; } | public Server Server { get; internal set; } | ||||
| public Channel Channel { get; internal set; } | |||||
| public VoiceChannel Channel { get; internal set; } | |||||
| public int Ping => _ping; | public int Ping => _ping; | ||||
| internal VoiceBuffer OutputBuffer => _sendBuffer; | internal VoiceBuffer OutputBuffer => _sendBuffer; | ||||
| @@ -16,8 +16,7 @@ namespace Discord.Audio | |||||
| public string SessionId => _client.Server == Server ? _client.SessionId : null; | public string SessionId => _client.Server == Server ? _client.SessionId : null; | ||||
| public ConnectionState State => _client.Server == Server ? _client.State : ConnectionState.Disconnected; | public ConnectionState State => _client.Server == Server ? _client.State : ConnectionState.Disconnected; | ||||
| public Channel Channel => _client.Server == Server ? _client.Channel : null; | |||||
| public Stream OutputStream => _client.Server == Server ? _client.OutputStream : null; | |||||
| public VoiceChannel Channel => _client.Server == Server ? _client.Channel : null; | |||||
| public CancellationToken CancelToken => _client.Server == Server ? _client.CancelToken : CancellationToken.None; | public CancellationToken CancelToken => _client.Server == Server ? _client.CancelToken : CancellationToken.None; | ||||
| public RestClient ClientAPI => _client.Server == Server ? _client.ClientAPI : null; | public RestClient ClientAPI => _client.Server == Server ? _client.ClientAPI : null; | ||||
| @@ -31,7 +30,7 @@ namespace Discord.Audio | |||||
| } | } | ||||
| public Task Disconnect() => _client.Service.Leave(Server); | public Task Disconnect() => _client.Service.Leave(Server); | ||||
| public Task Join(Channel channel) => _client.Join(channel); | |||||
| public Task Join(VoiceChannel channel) => _client.Join(channel); | |||||
| public void Send(byte[] data, int offset, int count) => _client.Send(data, offset, count); | public void Send(byte[] data, int offset, int count) => _client.Send(data, offset, count); | ||||
| public void Clear() => _client.Clear(); | public void Clear() => _client.Clear(); | ||||
| @@ -1,5 +1,5 @@ | |||||
| { | { | ||||
| "version": "0.9.0-rc3", | |||||
| "version": "1.0.0-alpha1", | |||||
| "description": "A Discord.Net extension adding voice support.", | "description": "A Discord.Net extension adding voice support.", | ||||
| "authors": [ "RogueException" ], | "authors": [ "RogueException" ], | ||||
| "tags": [ "discord", "discordapp" ], | "tags": [ "discord", "discordapp" ], | ||||
| @@ -18,7 +18,7 @@ | |||||
| }, | }, | ||||
| "dependencies": { | "dependencies": { | ||||
| "Discord.Net": "0.9.0-rc3-3" | |||||
| "Discord.Net": "1.0.0-alpha1" | |||||
| }, | }, | ||||
| "frameworks": { | "frameworks": { | ||||
| "net45": { }, | "net45": { }, | ||||
| @@ -52,7 +52,7 @@ namespace Discord.Commands | |||||
| _checks = checks; | _checks = checks; | ||||
| } | } | ||||
| internal bool CanRun(User user, Channel channel, out string error) | |||||
| internal bool CanRun(User user, ITextChannel channel, out string error) | |||||
| { | { | ||||
| for (int i = 0; i < _checks.Length; i++) | for (int i = 0; i < _checks.Length; i++) | ||||
| { | { | ||||
| @@ -79,7 +79,7 @@ namespace Discord.Commands | |||||
| _checks.Add(check); | _checks.Add(check); | ||||
| return this; | return this; | ||||
| } | } | ||||
| public CommandBuilder AddCheck(Func<Command, User, Channel, bool> checkFunc, string errorMsg = null) | |||||
| public CommandBuilder AddCheck(Func<Command, User, ITextChannel, bool> checkFunc, string errorMsg = null) | |||||
| { | { | ||||
| _checks.Add(new GenericPermissionChecker(checkFunc, errorMsg)); | _checks.Add(new GenericPermissionChecker(checkFunc, errorMsg)); | ||||
| return this; | return this; | ||||
| @@ -145,7 +145,7 @@ namespace Discord.Commands | |||||
| { | { | ||||
| _checks.Add(checker); | _checks.Add(checker); | ||||
| } | } | ||||
| public void AddCheck(Func<Command, User, Channel, bool> checkFunc, string errorMsg = null) | |||||
| public void AddCheck(Func<Command, User, ITextChannel, bool> checkFunc, string errorMsg = null) | |||||
| { | { | ||||
| _checks.Add(new GenericPermissionChecker(checkFunc, errorMsg)); | _checks.Add(new GenericPermissionChecker(checkFunc, errorMsg)); | ||||
| } | } | ||||
| @@ -10,8 +10,7 @@ namespace Discord.Commands | |||||
| public Command Command { get; } | public Command Command { get; } | ||||
| public User User => Message.User; | public User User => Message.User; | ||||
| public Channel Channel => Message.Channel; | |||||
| public Server Server => Message.Channel.Server; | |||||
| public ITextChannel Channel => Message.Channel; | |||||
| public CommandEventArgs(Message message, Command command, string[] args) | public CommandEventArgs(Message message, Command command, string[] args) | ||||
| { | { | ||||
| @@ -116,7 +116,7 @@ namespace Discord.Commands | |||||
| } | } | ||||
| } | } | ||||
| public bool CanRun(User user, Channel channel, out string error) | |||||
| public bool CanRun(User user, ITextChannel channel, out string error) | |||||
| { | { | ||||
| error = null; | error = null; | ||||
| if (_commands.Count > 0) | if (_commands.Count > 0) | ||||
| @@ -63,7 +63,7 @@ namespace Discord.Commands | |||||
| .Description("Returns information about commands.") | .Description("Returns information about commands.") | ||||
| .Do(async e => | .Do(async e => | ||||
| { | { | ||||
| Channel replyChannel = Config.HelpMode == HelpMode.Public ? e.Channel : await e.User.CreatePMChannel().ConfigureAwait(false); | |||||
| ITextChannel replyChannel = Config.HelpMode == HelpMode.Public ? e.Channel : await e.User.CreatePMChannel().ConfigureAwait(false); | |||||
| if (e.Args.Length > 0) //Show command help | if (e.Args.Length > 0) //Show command help | ||||
| { | { | ||||
| var map = _map.GetItem(string.Join(" ", e.Args)); | var map = _map.GetItem(string.Join(" ", e.Args)); | ||||
| @@ -175,7 +175,7 @@ namespace Discord.Commands | |||||
| }; | }; | ||||
| } | } | ||||
| public Task ShowGeneralHelp(User user, Channel channel, Channel replyChannel = null) | |||||
| public Task ShowGeneralHelp(User user, ITextChannel channel, ITextChannel replyChannel = null) | |||||
| { | { | ||||
| StringBuilder output = new StringBuilder(); | StringBuilder output = new StringBuilder(); | ||||
| bool isFirstCategory = true; | bool isFirstCategory = true; | ||||
| @@ -219,32 +219,12 @@ namespace Discord.Commands | |||||
| if (output.Length == 0) | if (output.Length == 0) | ||||
| output.Append("There are no commands you have permission to run."); | output.Append("There are no commands you have permission to run."); | ||||
| else | else | ||||
| { | |||||
| output.Append("\n\n"); | |||||
| //TODO: Should prefix be stated in the help message or not? | |||||
| /*StringBuilder builder = new StringBuilder(); | |||||
| if (Config.PrefixChar != null) | |||||
| { | |||||
| builder.Append('`'); | |||||
| builder.Append(Config.PrefixChar.Value); | |||||
| builder.Append('`'); | |||||
| } | |||||
| if (Config.AllowMentionPrefix) | |||||
| { | |||||
| if (builder.Length > 0) | |||||
| builder.Append(" or "); | |||||
| builder.Append(Client.CurrentUser.Mention); | |||||
| } | |||||
| if (builder.Length > 0) | |||||
| output.AppendLine($"Start your message with {builder.ToString()} to run a command.");*/ | |||||
| output.AppendLine($"Run `help <command>` for more information."); | |||||
| } | |||||
| output.AppendLine("\n\nRun `help <command>` for more information."); | |||||
| return (replyChannel ?? channel).SendMessage(output.ToString()); | return (replyChannel ?? channel).SendMessage(output.ToString()); | ||||
| } | } | ||||
| private Task ShowCommandHelp(CommandMap map, User user, Channel channel, Channel replyChannel = null) | |||||
| private Task ShowCommandHelp(CommandMap map, User user, ITextChannel channel, ITextChannel replyChannel = null) | |||||
| { | { | ||||
| StringBuilder output = new StringBuilder(); | StringBuilder output = new StringBuilder(); | ||||
| @@ -255,9 +235,7 @@ namespace Discord.Commands | |||||
| { | { | ||||
| foreach (var cmd in cmds) | foreach (var cmd in cmds) | ||||
| { | { | ||||
| if (!cmd.CanRun(user, channel, out error)) { } | |||||
| //output.AppendLine(error ?? DefaultPermissionError); | |||||
| else | |||||
| if (cmd.CanRun(user, channel, out error)) | |||||
| { | { | ||||
| if (isFirstCmd) | if (isFirstCmd) | ||||
| isFirstCmd = false; | isFirstCmd = false; | ||||
| @@ -299,7 +277,7 @@ namespace Discord.Commands | |||||
| return (replyChannel ?? channel).SendMessage(output.ToString()); | return (replyChannel ?? channel).SendMessage(output.ToString()); | ||||
| } | } | ||||
| public Task ShowCommandHelp(Command command, User user, Channel channel, Channel replyChannel = null) | |||||
| public Task ShowCommandHelp(Command command, User user, ITextChannel channel, ITextChannel replyChannel = null) | |||||
| { | { | ||||
| StringBuilder output = new StringBuilder(); | StringBuilder output = new StringBuilder(); | ||||
| string error; | string error; | ||||
| @@ -309,7 +287,7 @@ namespace Discord.Commands | |||||
| ShowCommandHelpInternal(command, user, channel, output); | ShowCommandHelpInternal(command, user, channel, output); | ||||
| return (replyChannel ?? channel).SendMessage(output.ToString()); | return (replyChannel ?? channel).SendMessage(output.ToString()); | ||||
| } | } | ||||
| private void ShowCommandHelpInternal(Command command, User user, Channel channel, StringBuilder output) | |||||
| private void ShowCommandHelpInternal(Command command, User user, ITextChannel channel, StringBuilder output) | |||||
| { | { | ||||
| output.Append('`'); | output.Append('`'); | ||||
| output.Append(command.Text); | output.Append(command.Text); | ||||
| @@ -4,16 +4,16 @@ namespace Discord.Commands.Permissions | |||||
| { | { | ||||
| internal class GenericPermissionChecker : IPermissionChecker | internal class GenericPermissionChecker : IPermissionChecker | ||||
| { | { | ||||
| private readonly Func<Command, User, Channel, bool> _checkFunc; | |||||
| private readonly Func<Command, User, ITextChannel, bool> _checkFunc; | |||||
| private readonly string _error; | private readonly string _error; | ||||
| public GenericPermissionChecker(Func<Command, User, Channel, bool> checkFunc, string error = null) | |||||
| public GenericPermissionChecker(Func<Command, User, ITextChannel, bool> checkFunc, string error = null) | |||||
| { | { | ||||
| _checkFunc = checkFunc; | _checkFunc = checkFunc; | ||||
| _error = error; | _error = error; | ||||
| } | } | ||||
| public bool CanRun(Command command, User user, Channel channel, out string error) | |||||
| public bool CanRun(Command command, User user, ITextChannel channel, out string error) | |||||
| { | { | ||||
| error = _error; | error = _error; | ||||
| return _checkFunc(command, user, channel); | return _checkFunc(command, user, channel); | ||||
| @@ -2,6 +2,6 @@ | |||||
| { | { | ||||
| public interface IPermissionChecker | public interface IPermissionChecker | ||||
| { | { | ||||
| bool CanRun(Command command, User user, Channel channel, out string error); | |||||
| bool CanRun(Command command, User user, ITextChannel channel, out string error); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,24 +0,0 @@ | |||||
| namespace Discord.Commands.Permissions.Levels | |||||
| { | |||||
| public class PermissionLevelChecker : IPermissionChecker | |||||
| { | |||||
| private readonly PermissionLevelService _service; | |||||
| private readonly int _minPermissions; | |||||
| public PermissionLevelService Service => _service; | |||||
| public int MinPermissions => _minPermissions; | |||||
| internal PermissionLevelChecker(DiscordClient client, int minPermissions) | |||||
| { | |||||
| _service = client.GetService<PermissionLevelService>(true); | |||||
| _minPermissions = minPermissions; | |||||
| } | |||||
| public bool CanRun(Command command, User user, Channel channel, out string error) | |||||
| { | |||||
| error = null; //Use default error text. | |||||
| int permissions = _service.GetPermissionLevel(user, channel); | |||||
| return permissions >= _minPermissions; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,29 +0,0 @@ | |||||
| using System; | |||||
| namespace Discord.Commands.Permissions.Levels | |||||
| { | |||||
| public static class PermissionLevelExtensions | |||||
| { | |||||
| public static DiscordClient UsingPermissionLevels(this DiscordClient client, Func<User, Channel, int> permissionResolver) | |||||
| { | |||||
| client.AddService(new PermissionLevelService(permissionResolver)); | |||||
| return client; | |||||
| } | |||||
| public static CommandBuilder MinPermissions(this CommandBuilder builder, int minPermissions) | |||||
| { | |||||
| builder.AddCheck(new PermissionLevelChecker(builder.Service.Client, minPermissions)); | |||||
| return builder; | |||||
| } | |||||
| public static CommandGroupBuilder MinPermissions(this CommandGroupBuilder builder, int minPermissions) | |||||
| { | |||||
| builder.AddCheck(new PermissionLevelChecker(builder.Service.Client, minPermissions)); | |||||
| return builder; | |||||
| } | |||||
| public static CommandService MinPermissions(this CommandService service, int minPermissions) | |||||
| { | |||||
| service.Root.AddCheck(new PermissionLevelChecker(service.Client, minPermissions)); | |||||
| return service; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,23 +0,0 @@ | |||||
| using System; | |||||
| namespace Discord.Commands.Permissions.Levels | |||||
| { | |||||
| public class PermissionLevelService : IService | |||||
| { | |||||
| private readonly Func<User, Channel, int> _getPermissionsFunc; | |||||
| private DiscordClient _client; | |||||
| public DiscordClient Client => _client; | |||||
| public PermissionLevelService(Func<User, Channel, int> getPermissionsFunc) | |||||
| { | |||||
| _getPermissionsFunc = getPermissionsFunc; | |||||
| } | |||||
| public void Install(DiscordClient client) | |||||
| { | |||||
| _client = client; | |||||
| } | |||||
| public int GetPermissionLevel(User user, Channel channel) => _getPermissionsFunc(user, channel); | |||||
| } | |||||
| } | |||||
| @@ -1,18 +0,0 @@ | |||||
| namespace Discord.Commands.Permissions.Userlist | |||||
| { | |||||
| public class BlacklistChecker : IPermissionChecker | |||||
| { | |||||
| private readonly BlacklistService _service; | |||||
| internal BlacklistChecker(DiscordClient client) | |||||
| { | |||||
| _service = client.GetService<BlacklistService>(true); | |||||
| } | |||||
| public bool CanRun(Command command, User user, Channel channel, out string error) | |||||
| { | |||||
| error = null; //Use default error text. | |||||
| return _service.CanRun(user); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,48 +0,0 @@ | |||||
| using System.Collections.Generic; | |||||
| namespace Discord.Commands.Permissions.Userlist | |||||
| { | |||||
| public static class BlacklistExtensions | |||||
| { | |||||
| public static DiscordClient UsingGlobalBlacklist(this DiscordClient client, params ulong[] initialUserIds) | |||||
| { | |||||
| client.AddService(new BlacklistService(initialUserIds)); | |||||
| return client; | |||||
| } | |||||
| public static CommandBuilder UseGlobalBlacklist(this CommandBuilder builder) | |||||
| { | |||||
| builder.AddCheck(new BlacklistChecker(builder.Service.Client)); | |||||
| return builder; | |||||
| } | |||||
| public static CommandGroupBuilder UseGlobalBlacklist(this CommandGroupBuilder builder) | |||||
| { | |||||
| builder.AddCheck(new BlacklistChecker(builder.Service.Client)); | |||||
| return builder; | |||||
| } | |||||
| public static CommandService UseGlobalBlacklist(this CommandService service) | |||||
| { | |||||
| service.Root.AddCheck(new BlacklistChecker(service.Client)); | |||||
| return service; | |||||
| } | |||||
| public static IEnumerable<ulong> GetBlacklistedUserIds(this DiscordClient client) | |||||
| => client.GetService<BlacklistService>().UserIds; | |||||
| public static void BlacklistUser(this DiscordClient client, User user) | |||||
| { | |||||
| client.GetService<BlacklistService>().Add(user.Id); | |||||
| } | |||||
| public static void BlacklistUser(this DiscordClient client, ulong userId) | |||||
| { | |||||
| client.GetService<BlacklistService>().Add(userId); | |||||
| } | |||||
| public static void UnBlacklistUser(this DiscordClient client, User user) | |||||
| { | |||||
| client.GetService<BlacklistService>().Remove(user.Id); | |||||
| } | |||||
| public static void UnBlacklistUser(this DiscordClient client, ulong userId) | |||||
| { | |||||
| client.GetService<BlacklistService>().Remove(userId); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,13 +0,0 @@ | |||||
| namespace Discord.Commands.Permissions.Userlist | |||||
| { | |||||
| public class BlacklistService : UserlistService | |||||
| { | |||||
| public BlacklistService(params ulong[] initialList) | |||||
| : base(initialList) | |||||
| { | |||||
| } | |||||
| public bool CanRun(User user) | |||||
| => !_userList.ContainsKey(user.Id); | |||||
| } | |||||
| } | |||||
| @@ -1,52 +0,0 @@ | |||||
| using System; | |||||
| using System.Collections.Concurrent; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| namespace Discord.Commands.Permissions.Userlist | |||||
| { | |||||
| public class UserlistService : IService | |||||
| { | |||||
| protected readonly ConcurrentDictionary<ulong, bool> _userList; | |||||
| private DiscordClient _client; | |||||
| public DiscordClient Client => _client; | |||||
| public IEnumerable<ulong> UserIds => _userList.Select(x => x.Key); | |||||
| public UserlistService(params ulong[] initialUserIds) | |||||
| { | |||||
| _userList = new ConcurrentDictionary<ulong, bool>(); | |||||
| for (int i = 0; i < initialUserIds.Length; i++) | |||||
| _userList.TryAdd(initialUserIds[i], true); | |||||
| } | |||||
| public void Add(User user) | |||||
| { | |||||
| if (user == null) throw new ArgumentNullException(nameof(user)); | |||||
| _userList[user.Id] = true; | |||||
| } | |||||
| public void Add(ulong userId) | |||||
| { | |||||
| _userList[userId] = true; | |||||
| } | |||||
| public bool Remove(User user) | |||||
| { | |||||
| if (user == null) throw new ArgumentNullException(nameof(user)); | |||||
| bool ignored; | |||||
| return _userList.TryRemove(user.Id, out ignored); | |||||
| } | |||||
| public bool Remove(ulong userId) | |||||
| { | |||||
| bool ignored; | |||||
| return _userList.TryRemove(userId, out ignored); | |||||
| } | |||||
| void IService.Install(DiscordClient client) | |||||
| { | |||||
| _client = client; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,18 +0,0 @@ | |||||
| namespace Discord.Commands.Permissions.Userlist | |||||
| { | |||||
| public class WhitelistChecker : IPermissionChecker | |||||
| { | |||||
| private readonly WhitelistService _service; | |||||
| internal WhitelistChecker(DiscordClient client) | |||||
| { | |||||
| _service = client.GetService<WhitelistService>(true); | |||||
| } | |||||
| public bool CanRun(Command command, User user, Channel channel, out string error) | |||||
| { | |||||
| error = null; //Use default error text. | |||||
| return _service.CanRun(user); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,48 +0,0 @@ | |||||
| using System.Collections.Generic; | |||||
| namespace Discord.Commands.Permissions.Userlist | |||||
| { | |||||
| public static class WhitelistExtensions | |||||
| { | |||||
| public static DiscordClient UsingGlobalWhitelist(this DiscordClient client, params ulong[] initialUserIds) | |||||
| { | |||||
| client.AddService(new WhitelistService(initialUserIds)); | |||||
| return client; | |||||
| } | |||||
| public static CommandBuilder UseGlobalWhitelist(this CommandBuilder builder) | |||||
| { | |||||
| builder.AddCheck(new WhitelistChecker(builder.Service.Client)); | |||||
| return builder; | |||||
| } | |||||
| public static CommandGroupBuilder UseGlobalWhitelist(this CommandGroupBuilder builder) | |||||
| { | |||||
| builder.AddCheck(new WhitelistChecker(builder.Service.Client)); | |||||
| return builder; | |||||
| } | |||||
| public static CommandService UseGlobalWhitelist(this CommandService service) | |||||
| { | |||||
| service.Root.AddCheck(new BlacklistChecker(service.Client)); | |||||
| return service; | |||||
| } | |||||
| public static IEnumerable<ulong> GetWhitelistedUserIds(this DiscordClient client) | |||||
| => client.GetService<WhitelistService>().UserIds; | |||||
| public static void WhitelistUser(this DiscordClient client, User user) | |||||
| { | |||||
| client.GetService<WhitelistService>().Add(user.Id); | |||||
| } | |||||
| public static void WhitelistUser(this DiscordClient client, ulong userId) | |||||
| { | |||||
| client.GetService<WhitelistService>().Add(userId); | |||||
| } | |||||
| public static void UnWhitelistUser(this DiscordClient client, User user) | |||||
| { | |||||
| client.GetService<WhitelistService>().Remove(user.Id); | |||||
| } | |||||
| public static void RemoveFromWhitelist(this DiscordClient client, ulong userId) | |||||
| { | |||||
| client.GetService<WhitelistService>().Remove(userId); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,13 +0,0 @@ | |||||
| namespace Discord.Commands.Permissions.Userlist | |||||
| { | |||||
| public class WhitelistService : UserlistService | |||||
| { | |||||
| public WhitelistService(params ulong[] initialList) | |||||
| : base(initialList) | |||||
| { | |||||
| } | |||||
| public bool CanRun(User user) | |||||
| => _userList.ContainsKey(user.Id); | |||||
| } | |||||
| } | |||||
| @@ -1,21 +0,0 @@ | |||||
| namespace Discord.Commands.Permissions.Visibility | |||||
| { | |||||
| public class PrivateChecker : IPermissionChecker | |||||
| { | |||||
| internal PrivateChecker() { } | |||||
| public bool CanRun(Command command, User user, Channel channel, out string error) | |||||
| { | |||||
| if (user.Server != null) | |||||
| { | |||||
| error = "This command may only be run in a private chat."; | |||||
| return false; | |||||
| } | |||||
| else | |||||
| { | |||||
| error = null; | |||||
| return true; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,21 +0,0 @@ | |||||
| namespace Discord.Commands.Permissions.Visibility | |||||
| { | |||||
| public static class PrivateExtensions | |||||
| { | |||||
| public static CommandBuilder PrivateOnly(this CommandBuilder builder) | |||||
| { | |||||
| builder.AddCheck(new PrivateChecker()); | |||||
| return builder; | |||||
| } | |||||
| public static CommandGroupBuilder PrivateOnly(this CommandGroupBuilder builder) | |||||
| { | |||||
| builder.AddCheck(new PrivateChecker()); | |||||
| return builder; | |||||
| } | |||||
| public static CommandService PrivateOnly(this CommandService service) | |||||
| { | |||||
| service.Root.AddCheck(new PrivateChecker()); | |||||
| return service; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,21 +0,0 @@ | |||||
| namespace Discord.Commands.Permissions.Visibility | |||||
| { | |||||
| public class PublicChecker : IPermissionChecker | |||||
| { | |||||
| internal PublicChecker() { } | |||||
| public bool CanRun(Command command, User user, Channel channel, out string error) | |||||
| { | |||||
| if (user.Server == null) | |||||
| { | |||||
| error = "This command can't be run in a private chat."; | |||||
| return false; | |||||
| } | |||||
| else | |||||
| { | |||||
| error = null; | |||||
| return true; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,21 +0,0 @@ | |||||
| namespace Discord.Commands.Permissions.Visibility | |||||
| { | |||||
| public static class PublicExtensions | |||||
| { | |||||
| public static CommandBuilder PublicOnly(this CommandBuilder builder) | |||||
| { | |||||
| builder.AddCheck(new PublicChecker()); | |||||
| return builder; | |||||
| } | |||||
| public static CommandGroupBuilder PublicOnly(this CommandGroupBuilder builder) | |||||
| { | |||||
| builder.AddCheck(new PublicChecker()); | |||||
| return builder; | |||||
| } | |||||
| public static CommandService PublicOnly(this CommandService service) | |||||
| { | |||||
| service.Root.AddCheck(new PublicChecker()); | |||||
| return service; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,5 +1,5 @@ | |||||
| { | { | ||||
| "version": "0.9.0-rc3-1", | |||||
| "version": "1.0.0-alpha1", | |||||
| "description": "A Discord.Net extension adding basic command support.", | "description": "A Discord.Net extension adding basic command support.", | ||||
| "authors": [ "RogueException" ], | "authors": [ "RogueException" ], | ||||
| "tags": [ "discord", "discordapp" ], | "tags": [ "discord", "discordapp" ], | ||||
| @@ -16,7 +16,7 @@ | |||||
| }, | }, | ||||
| "dependencies": { | "dependencies": { | ||||
| "Discord.Net": "0.9.0-rc3-3" | |||||
| "Discord.Net": "1.0.0-alpha1" | |||||
| }, | }, | ||||
| "frameworks": { | "frameworks": { | ||||
| "net45": { }, | "net45": { }, | ||||
| @@ -14,9 +14,11 @@ namespace Discord.Modules | |||||
| _filterType = manager.FilterType; | _filterType = manager.FilterType; | ||||
| } | } | ||||
| public bool CanRun(Command command, User user, Channel channel, out string error) | |||||
| public bool CanRun(Command command, User user, ITextChannel channel, out string error) | |||||
| { | { | ||||
| if (_filterType == ModuleFilter.None || _filterType == ModuleFilter.AlwaysAllowPrivate || _manager.HasChannel(channel)) | |||||
| if (_filterType == ModuleFilter.None || | |||||
| _filterType == ModuleFilter.AlwaysAllowPrivate || | |||||
| (channel.IsPublic && _manager.HasChannel(channel))) | |||||
| { | { | ||||
| error = null; | error = null; | ||||
| return true; | return true; | ||||
| @@ -20,11 +20,6 @@ namespace Discord.Modules | |||||
| public class ModuleManager | public class ModuleManager | ||||
| { | { | ||||
| public event EventHandler<ServerEventArgs> ServerEnabled = delegate { }; | |||||
| public event EventHandler<ServerEventArgs> ServerDisabled = delegate { }; | |||||
| public event EventHandler<ChannelEventArgs> ChannelEnabled = delegate { }; | |||||
| public event EventHandler<ChannelEventArgs> ChannelDisabled = delegate { }; | |||||
| public event EventHandler<ServerEventArgs> JoinedServer = delegate { }; | public event EventHandler<ServerEventArgs> JoinedServer = delegate { }; | ||||
| public event EventHandler<ServerEventArgs> LeftServer = delegate { }; | public event EventHandler<ServerEventArgs> LeftServer = delegate { }; | ||||
| public event EventHandler<ServerUpdatedEventArgs> ServerUpdated = delegate { }; | public event EventHandler<ServerUpdatedEventArgs> ServerUpdated = delegate { }; | ||||
| @@ -43,10 +38,8 @@ namespace Discord.Modules | |||||
| public event EventHandler<UserEventArgs> UserJoined = delegate { }; | public event EventHandler<UserEventArgs> UserJoined = delegate { }; | ||||
| public event EventHandler<UserEventArgs> UserLeft = delegate { }; | public event EventHandler<UserEventArgs> UserLeft = delegate { }; | ||||
| public event EventHandler<UserUpdatedEventArgs> UserUpdated = delegate { }; | public event EventHandler<UserUpdatedEventArgs> UserUpdated = delegate { }; | ||||
| //public event EventHandler<UserEventArgs> UserPresenceUpdated = delegate { }; | |||||
| //public event EventHandler<UserEventArgs> UserVoiceStateUpdated = delegate { }; | |||||
| public event EventHandler<UserEventArgs> UserUnbanned = delegate { }; | public event EventHandler<UserEventArgs> UserUnbanned = delegate { }; | ||||
| public event EventHandler<ChannelUserEventArgs> UserIsTyping = delegate { }; | |||||
| public event EventHandler<TypingEventArgs> UserIsTyping = delegate { }; | |||||
| public event EventHandler<MessageEventArgs> MessageReceived = delegate { }; | public event EventHandler<MessageEventArgs> MessageReceived = delegate { }; | ||||
| public event EventHandler<MessageEventArgs> MessageSent = delegate { }; | public event EventHandler<MessageEventArgs> MessageSent = delegate { }; | ||||
| @@ -56,7 +49,7 @@ namespace Discord.Modules | |||||
| private readonly bool _useServerWhitelist, _useChannelWhitelist, _allowAll, _allowPrivate; | private readonly bool _useServerWhitelist, _useChannelWhitelist, _allowAll, _allowPrivate; | ||||
| private readonly ConcurrentDictionary<ulong, Server> _enabledServers; | private readonly ConcurrentDictionary<ulong, Server> _enabledServers; | ||||
| private readonly ConcurrentDictionary<ulong, Channel> _enabledChannels; | |||||
| private readonly ConcurrentDictionary<ulong, IChannel> _enabledChannels; | |||||
| private readonly ConcurrentDictionary<ulong, int> _indirectServers; | private readonly ConcurrentDictionary<ulong, int> _indirectServers; | ||||
| private readonly AsyncLock _lock; | private readonly AsyncLock _lock; | ||||
| @@ -67,7 +60,7 @@ namespace Discord.Modules | |||||
| public ModuleFilter FilterType { get; } | public ModuleFilter FilterType { get; } | ||||
| public IEnumerable<Server> EnabledServers => _enabledServers.Select(x => x.Value); | public IEnumerable<Server> EnabledServers => _enabledServers.Select(x => x.Value); | ||||
| public IEnumerable<Channel> EnabledChannels => _enabledChannels.Select(x => x.Value); | |||||
| public IEnumerable<IChannel> EnabledChannels => _enabledChannels.Select(x => x.Value); | |||||
| internal ModuleManager(DiscordClient client, IModule instance, string name, ModuleFilter filterType) | internal ModuleManager(DiscordClient client, IModule instance, string name, ModuleFilter filterType) | ||||
| { | { | ||||
| @@ -85,12 +78,17 @@ namespace Discord.Modules | |||||
| _allowPrivate = filterType.HasFlag(ModuleFilter.AlwaysAllowPrivate); | _allowPrivate = filterType.HasFlag(ModuleFilter.AlwaysAllowPrivate); | ||||
| _enabledServers = new ConcurrentDictionary<ulong, Server>(); | _enabledServers = new ConcurrentDictionary<ulong, Server>(); | ||||
| _enabledChannels = new ConcurrentDictionary<ulong, Channel>(); | |||||
| _enabledChannels = new ConcurrentDictionary<ulong, IChannel>(); | |||||
| _indirectServers = new ConcurrentDictionary<ulong, int>(); | _indirectServers = new ConcurrentDictionary<ulong, int>(); | ||||
| if (_allowAll || _useServerWhitelist) //Server-only events | if (_allowAll || _useServerWhitelist) //Server-only events | ||||
| { | { | ||||
| client.ChannelCreated += (s, e) => { if (e.Server != null && HasServer(e.Server)) ChannelCreated(s, e); }; | |||||
| client.ChannelCreated += (s, e) => | |||||
| { | |||||
| var server = (e.Channel as PublicChannel)?.Server; | |||||
| if (HasServer(server)) | |||||
| ChannelCreated(s, e); | |||||
| }; | |||||
| //TODO: This *is* a channel update if the before/after voice channel is whitelisted | //TODO: This *is* a channel update if the before/after voice channel is whitelisted | ||||
| //client.UserVoiceStateUpdated += (s, e) => { if (HasServer(e.Server)) UserVoiceStateUpdated(s, e); }; | //client.UserVoiceStateUpdated += (s, e) => { if (HasServer(e.Server)) UserVoiceStateUpdated(s, e); }; | ||||
| } | } | ||||
| @@ -116,10 +114,9 @@ namespace Discord.Modules | |||||
| client.UserJoined += (s, e) => { if (HasIndirectServer(e.Server)) UserJoined(s, e); }; | client.UserJoined += (s, e) => { if (HasIndirectServer(e.Server)) UserJoined(s, e); }; | ||||
| client.UserLeft += (s, e) => { if (HasIndirectServer(e.Server)) UserLeft(s, e); }; | client.UserLeft += (s, e) => { if (HasIndirectServer(e.Server)) UserLeft(s, e); }; | ||||
| //TODO: We aren't getting events from UserPresence if AllowPrivate is enabled, but the server we know that user through isn't on the whitelist | |||||
| client.UserUpdated += (s, e) => { if (HasIndirectServer(e.Server)) UserUpdated(s, e); }; | client.UserUpdated += (s, e) => { if (HasIndirectServer(e.Server)) UserUpdated(s, e); }; | ||||
| client.UserIsTyping += (s, e) => { if (HasChannel(e.Channel)) UserIsTyping(s, e); }; | client.UserIsTyping += (s, e) => { if (HasChannel(e.Channel)) UserIsTyping(s, e); }; | ||||
| //TODO: We aren't getting events from UserPresence if AllowPrivate is enabled, but the server we know that user through isn't on the whitelist | |||||
| //client.UserPresenceUpdated += (s, e) => { if (HasIndirectServer(e.Server)) UserPresenceUpdated(s, e); }; | |||||
| client.UserBanned += (s, e) => { if (HasIndirectServer(e.Server)) UserBanned(s, e); }; | client.UserBanned += (s, e) => { if (HasIndirectServer(e.Server)) UserBanned(s, e); }; | ||||
| client.UserUnbanned += (s, e) => { if (HasIndirectServer(e.Server)) UserUnbanned(s, e); }; | client.UserUnbanned += (s, e) => { if (HasIndirectServer(e.Server)) UserUnbanned(s, e); }; | ||||
| } | } | ||||
| @@ -155,16 +152,7 @@ namespace Discord.Modules | |||||
| EnableServerInternal(server); | EnableServerInternal(server); | ||||
| } | } | ||||
| } | } | ||||
| private bool EnableServerInternal(Server server) | |||||
| { | |||||
| if (_enabledServers.TryAdd(server.Id, server)) | |||||
| { | |||||
| if (ServerEnabled != null) | |||||
| ServerEnabled(this, new ServerEventArgs(server)); | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| private bool EnableServerInternal(Server server) => _enabledServers.TryAdd(server.Id, server); | |||||
| public bool DisableServer(Server server) | public bool DisableServer(Server server) | ||||
| { | { | ||||
| @@ -172,34 +160,18 @@ namespace Discord.Modules | |||||
| if (!_useServerWhitelist) return false; | if (!_useServerWhitelist) return false; | ||||
| using (_lock.Lock()) | using (_lock.Lock()) | ||||
| { | |||||
| if (_enabledServers.TryRemove(server.Id, out server)) | |||||
| { | |||||
| if (ServerDisabled != null) | |||||
| ServerDisabled(this, new ServerEventArgs(server)); | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| return _enabledServers.TryRemove(server.Id, out server); | |||||
| } | } | ||||
| public void DisableAllServers() | public void DisableAllServers() | ||||
| { | { | ||||
| if (!_useServerWhitelist) throw new InvalidOperationException("This module is not configured to use a server whitelist."); | if (!_useServerWhitelist) throw new InvalidOperationException("This module is not configured to use a server whitelist."); | ||||
| if (!_useServerWhitelist) return; | if (!_useServerWhitelist) return; | ||||
| using (_lock.Lock()) | |||||
| { | |||||
| if (ServerDisabled != null) | |||||
| { | |||||
| foreach (var server in _enabledServers) | |||||
| ServerDisabled(this, new ServerEventArgs(server.Value)); | |||||
| } | |||||
| using (_lock.Lock()) | |||||
| _enabledServers.Clear(); | _enabledServers.Clear(); | ||||
| } | |||||
| } | } | ||||
| public bool EnableChannel(Channel channel) | |||||
| public bool EnableChannel(ITextChannel channel) | |||||
| { | { | ||||
| if (channel == null) throw new ArgumentNullException(nameof(channel)); | if (channel == null) throw new ArgumentNullException(nameof(channel)); | ||||
| if (!_useChannelWhitelist) throw new InvalidOperationException("This module is not configured to use a channel whitelist."); | if (!_useChannelWhitelist) throw new InvalidOperationException("This module is not configured to use a channel whitelist."); | ||||
| @@ -207,7 +179,7 @@ namespace Discord.Modules | |||||
| using (_lock.Lock()) | using (_lock.Lock()) | ||||
| return EnableChannelInternal(channel); | return EnableChannelInternal(channel); | ||||
| } | } | ||||
| public void EnableChannels(IEnumerable<Channel> channels) | |||||
| public void EnableChannels(IEnumerable<ITextChannel> channels) | |||||
| { | { | ||||
| if (channels == null) throw new ArgumentNullException(nameof(channels)); | if (channels == null) throw new ArgumentNullException(nameof(channels)); | ||||
| if (channels.Contains(null)) throw new ArgumentException("Collection cannot contain null.", nameof(channels)); | if (channels.Contains(null)) throw new ArgumentException("Collection cannot contain null.", nameof(channels)); | ||||
| @@ -219,65 +191,55 @@ namespace Discord.Modules | |||||
| EnableChannelInternal(channel); | EnableChannelInternal(channel); | ||||
| } | } | ||||
| } | } | ||||
| private bool EnableChannelInternal(Channel channel) | |||||
| private bool EnableChannelInternal(ITextChannel channel) | |||||
| { | { | ||||
| if (_enabledChannels.TryAdd(channel.Id, channel)) | if (_enabledChannels.TryAdd(channel.Id, channel)) | ||||
| { | |||||
| var server = channel.Server; | |||||
| if (server != null) | |||||
| { | |||||
| int value = 0; | |||||
| _indirectServers.TryGetValue(server.Id, out value); | |||||
| value++; | |||||
| _indirectServers[server.Id] = value; | |||||
| } | |||||
| if (ChannelEnabled != null) | |||||
| ChannelEnabled(this, new ChannelEventArgs(channel)); | |||||
| { | |||||
| if (channel.Type != ChannelType.Private) | |||||
| { | |||||
| var server = (channel as PublicChannel)?.Server; | |||||
| int value = 0; | |||||
| _indirectServers.TryGetValue(server.Id, out value); | |||||
| value++; | |||||
| _indirectServers[server.Id] = value; | |||||
| } | |||||
| return true; | return true; | ||||
| } | } | ||||
| return false; | return false; | ||||
| } | } | ||||
| public bool DisableChannel(Channel channel) | |||||
| public bool DisableChannel(IChannel channel) | |||||
| { | { | ||||
| if (channel == null) throw new ArgumentNullException(nameof(channel)); | if (channel == null) throw new ArgumentNullException(nameof(channel)); | ||||
| if (!_useChannelWhitelist) return false; | if (!_useChannelWhitelist) return false; | ||||
| using (_lock.Lock()) | |||||
| IChannel ignored; | |||||
| if (_enabledChannels.TryRemove(channel.Id, out ignored)) | |||||
| { | { | ||||
| Channel ignored; | |||||
| if (_enabledChannels.TryRemove(channel.Id, out ignored)) | |||||
| { | |||||
| var server = channel.Server; | |||||
| if (server != null) | |||||
| { | |||||
| int value = 0; | |||||
| _indirectServers.TryGetValue(server.Id, out value); | |||||
| value--; | |||||
| if (value <= 0) | |||||
| _indirectServers.TryRemove(server.Id, out value); | |||||
| else | |||||
| _indirectServers[server.Id] = value; | |||||
| } | |||||
| if (ChannelDisabled != null) | |||||
| ChannelDisabled(this, new ChannelEventArgs(channel)); | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| } | |||||
| using (_lock.Lock()) | |||||
| { | |||||
| if (channel.Type != ChannelType.Private) | |||||
| { | |||||
| var server = (channel as PublicChannel)?.Server; | |||||
| int value = 0; | |||||
| _indirectServers.TryGetValue(server.Id, out value); | |||||
| value--; | |||||
| if (value <= 0) | |||||
| _indirectServers.TryRemove(server.Id, out value); | |||||
| else | |||||
| _indirectServers[server.Id] = value; | |||||
| } | |||||
| return true; | |||||
| } | |||||
| } | |||||
| return false; | |||||
| } | |||||
| public void DisableAllChannels() | public void DisableAllChannels() | ||||
| { | { | ||||
| if (!_useChannelWhitelist) return; | if (!_useChannelWhitelist) return; | ||||
| using (_lock.Lock()) | using (_lock.Lock()) | ||||
| { | { | ||||
| if (ChannelDisabled != null) | |||||
| { | |||||
| foreach (var channel in _enabledChannels) | |||||
| ChannelDisabled(this, new ChannelEventArgs(channel.Value)); | |||||
| } | |||||
| _enabledChannels.Clear(); | _enabledChannels.Clear(); | ||||
| _indirectServers.Clear(); | _indirectServers.Clear(); | ||||
| } | } | ||||
| @@ -293,20 +255,20 @@ namespace Discord.Modules | |||||
| internal bool HasServer(Server server) => | internal bool HasServer(Server server) => | ||||
| _allowAll || | _allowAll || | ||||
| _useServerWhitelist && _enabledServers.ContainsKey(server.Id); | |||||
| (_useServerWhitelist && _enabledServers.ContainsKey(server.Id)); | |||||
| internal bool HasIndirectServer(Server server) => | internal bool HasIndirectServer(Server server) => | ||||
| _allowAll || | _allowAll || | ||||
| (_useServerWhitelist && _enabledServers.ContainsKey(server.Id)) || | (_useServerWhitelist && _enabledServers.ContainsKey(server.Id)) || | ||||
| (_useChannelWhitelist && _indirectServers.ContainsKey(server.Id)); | (_useChannelWhitelist && _indirectServers.ContainsKey(server.Id)); | ||||
| internal bool HasChannel(Channel channel) | |||||
| internal bool HasChannel(IChannel channel) | |||||
| { | { | ||||
| if (_allowAll) return true; | if (_allowAll) return true; | ||||
| if (channel.IsPrivate) return _allowPrivate; | |||||
| if (channel.Type == ChannelType.Private) return _allowPrivate; | |||||
| if (_useChannelWhitelist && _enabledChannels.ContainsKey(channel.Id)) return true; | if (_useChannelWhitelist && _enabledChannels.ContainsKey(channel.Id)) return true; | ||||
| if (_useServerWhitelist) | |||||
| if (_useServerWhitelist && channel.IsPublic) | |||||
| { | { | ||||
| var server = channel.Server; | |||||
| var server = (channel as PublicChannel).Server; | |||||
| if (server == null) return false; | if (server == null) return false; | ||||
| if (_enabledServers.ContainsKey(server.Id)) return true; | if (_enabledServers.ContainsKey(server.Id)) return true; | ||||
| } | } | ||||
| @@ -1,5 +1,5 @@ | |||||
| { | { | ||||
| "version": "0.9.0-rc3", | |||||
| "version": "1.0.0-alpha1", | |||||
| "description": "A Discord.Net extension adding basic plugin support.", | "description": "A Discord.Net extension adding basic plugin support.", | ||||
| "authors": [ "RogueException" ], | "authors": [ "RogueException" ], | ||||
| "tags": [ "discord", "discordapp" ], | "tags": [ "discord", "discordapp" ], | ||||
| @@ -16,8 +16,8 @@ | |||||
| }, | }, | ||||
| "dependencies": { | "dependencies": { | ||||
| "Discord.Net": "0.9.0-rc3-3", | |||||
| "Discord.Net.Commands": "0.9.0-rc3-1" | |||||
| "Discord.Net": "1.0.0-alpha1", | |||||
| "Discord.Net.Commands": "1.0.0-alpha1" | |||||
| }, | }, | ||||
| "frameworks": { | "frameworks": { | ||||
| "net45": { }, | "net45": { }, | ||||
| @@ -526,9 +526,15 @@ | |||||
| <Compile Include="..\Discord.Net\Models\Permissions.cs"> | <Compile Include="..\Discord.Net\Models\Permissions.cs"> | ||||
| <Link>Models\Permissions.cs</Link> | <Link>Models\Permissions.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net\Models\PrivateChannel.cs"> | |||||
| <Link>Models\PrivateChannel.cs</Link> | |||||
| </Compile> | |||||
| <Compile Include="..\Discord.Net\Models\Profile.cs"> | <Compile Include="..\Discord.Net\Models\Profile.cs"> | ||||
| <Link>Models\Profile.cs</Link> | <Link>Models\Profile.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net\Models\PublicChannel.cs"> | |||||
| <Link>Models\PublicChannel.cs</Link> | |||||
| </Compile> | |||||
| <Compile Include="..\Discord.Net\Models\Region.cs"> | <Compile Include="..\Discord.Net\Models\Region.cs"> | ||||
| <Link>Models\Region.cs</Link> | <Link>Models\Region.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| @@ -538,9 +544,15 @@ | |||||
| <Compile Include="..\Discord.Net\Models\Server.cs"> | <Compile Include="..\Discord.Net\Models\Server.cs"> | ||||
| <Link>Models\Server.cs</Link> | <Link>Models\Server.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net\Models\TextChannel.cs"> | |||||
| <Link>Models\TextChannel.cs</Link> | |||||
| </Compile> | |||||
| <Compile Include="..\Discord.Net\Models\User.cs"> | <Compile Include="..\Discord.Net\Models\User.cs"> | ||||
| <Link>Models\User.cs</Link> | <Link>Models\User.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net\Models\VoiceChannel.cs"> | |||||
| <Link>Models\VoiceChannel.cs</Link> | |||||
| </Compile> | |||||
| <Compile Include="..\Discord.Net\Net\HttpException.cs"> | <Compile Include="..\Discord.Net\Net\HttpException.cs"> | ||||
| <Link>Net\HttpException.cs</Link> | <Link>Net\HttpException.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| @@ -29,5 +29,7 @@ namespace Discord.API.Client | |||||
| public PermissionOverwrite[] PermissionOverwrites { get; set; } | public PermissionOverwrite[] PermissionOverwrites { get; set; } | ||||
| [JsonProperty("recipient")] | [JsonProperty("recipient")] | ||||
| public UserReference Recipient { get; set; } | public UserReference Recipient { get; set; } | ||||
| [JsonProperty("bitrate")] | |||||
| public int Bitrate { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -4,7 +4,7 @@ using Newtonsoft.Json; | |||||
| namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
| { | { | ||||
| [JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
| public class AddChannelPermissionsRequest : IRestRequest | |||||
| public class AddOrUpdateChannelPermissionsRequest : IRestRequest | |||||
| { | { | ||||
| string IRestRequest.Method => "PUT"; | string IRestRequest.Method => "PUT"; | ||||
| string IRestRequest.Endpoint => $"channels/{ChannelId}/permissions/{TargetId}"; | string IRestRequest.Endpoint => $"channels/{ChannelId}/permissions/{TargetId}"; | ||||
| @@ -21,7 +21,7 @@ namespace Discord.API.Client.Rest | |||||
| [JsonProperty("deny")] | [JsonProperty("deny")] | ||||
| public uint Deny { get; set; } | public uint Deny { get; set; } | ||||
| public AddChannelPermissionsRequest(ulong channelId) | |||||
| public AddOrUpdateChannelPermissionsRequest(ulong channelId) | |||||
| { | { | ||||
| ChannelId = channelId; | ChannelId = channelId; | ||||
| } | } | ||||
| @@ -14,7 +14,7 @@ namespace Discord.API.Client.Rest | |||||
| [JsonProperty("name")] | [JsonProperty("name")] | ||||
| public string Name { get; set; } | public string Name { get; set; } | ||||
| [JsonProperty("type")] | [JsonProperty("type")] | ||||
| public string Type { get; set; } | |||||
| public ChannelType Type { get; set; } | |||||
| public CreateChannelRequest(ulong guildId) | public CreateChannelRequest(ulong guildId) | ||||
| { | { | ||||
| @@ -17,6 +17,8 @@ namespace Discord.API.Client.Rest | |||||
| public string Topic { get; set; } | public string Topic { get; set; } | ||||
| [JsonProperty("position")] | [JsonProperty("position")] | ||||
| public int Position { get; set; } | public int Position { get; set; } | ||||
| [JsonProperty("bitrate")] | |||||
| public int Bitrate { get; set; } | |||||
| public UpdateChannelRequest(ulong channelId) | public UpdateChannelRequest(ulong channelId) | ||||
| { | { | ||||
| @@ -25,7 +25,7 @@ namespace Discord | |||||
| public event EventHandler<ServerUpdatedEventArgs> ServerUpdated = delegate { }; | public event EventHandler<ServerUpdatedEventArgs> ServerUpdated = delegate { }; | ||||
| public event EventHandler<ServerEventArgs> ServerUnavailable = delegate { }; | public event EventHandler<ServerEventArgs> ServerUnavailable = delegate { }; | ||||
| public event EventHandler<UserEventArgs> UserBanned = delegate { }; | public event EventHandler<UserEventArgs> UserBanned = delegate { }; | ||||
| public event EventHandler<ChannelUserEventArgs> UserIsTyping = delegate { }; | |||||
| public event EventHandler<TypingEventArgs> UserIsTyping = delegate { }; | |||||
| public event EventHandler<UserEventArgs> UserJoined = delegate { }; | public event EventHandler<UserEventArgs> UserJoined = delegate { }; | ||||
| public event EventHandler<UserEventArgs> UserLeft = delegate { }; | public event EventHandler<UserEventArgs> UserLeft = delegate { }; | ||||
| public event EventHandler<UserUpdatedEventArgs> UserUpdated = delegate { }; | public event EventHandler<UserUpdatedEventArgs> UserUpdated = delegate { }; | ||||
| @@ -36,11 +36,11 @@ namespace Discord | |||||
| /*private void OnLoggedOut(bool wasUnexpected, Exception ex) | /*private void OnLoggedOut(bool wasUnexpected, Exception ex) | ||||
| => OnEvent(LoggedOut, new DisconnectedEventArgs(wasUnexpected, ex));*/ | => OnEvent(LoggedOut, new DisconnectedEventArgs(wasUnexpected, ex));*/ | ||||
| private void OnChannelCreated(Channel channel) | |||||
| private void OnChannelCreated(IChannel channel) | |||||
| => OnEvent(ChannelCreated, new ChannelEventArgs(channel)); | => OnEvent(ChannelCreated, new ChannelEventArgs(channel)); | ||||
| private void OnChannelDestroyed(Channel channel) | |||||
| private void OnChannelDestroyed(IChannel channel) | |||||
| => OnEvent(ChannelDestroyed, new ChannelEventArgs(channel)); | => OnEvent(ChannelDestroyed, new ChannelEventArgs(channel)); | ||||
| private void OnChannelUpdated(Channel before, Channel after) | |||||
| private void OnChannelUpdated(IChannel before, IChannel after) | |||||
| => OnEvent(ChannelUpdated, new ChannelUpdatedEventArgs(before, after)); | => OnEvent(ChannelUpdated, new ChannelUpdatedEventArgs(before, after)); | ||||
| private void OnMessageAcknowledged(Message msg) | private void OnMessageAcknowledged(Message msg) | ||||
| @@ -77,8 +77,8 @@ namespace Discord | |||||
| private void OnUserBanned(User user) | private void OnUserBanned(User user) | ||||
| => OnEvent(UserBanned, new UserEventArgs(user)); | => OnEvent(UserBanned, new UserEventArgs(user)); | ||||
| private void OnUserIsTypingUpdated(Channel channel, User user) | |||||
| => OnEvent(UserIsTyping, new ChannelUserEventArgs(channel, user)); | |||||
| private void OnUserIsTypingUpdated(ITextChannel channel, User user) | |||||
| => OnEvent(UserIsTyping, new TypingEventArgs(channel, user)); | |||||
| private void OnUserJoined(User user) | private void OnUserJoined(User user) | ||||
| => OnEvent(UserJoined, new UserEventArgs(user)); | => OnEvent(UserJoined, new UserEventArgs(user)); | ||||
| private void OnUserLeft(User user) | private void OnUserLeft(User user) | ||||
| @@ -1,4 +1,5 @@ | |||||
| using Discord.API.Client.GatewaySocket; | |||||
| using APIChannel = Discord.API.Client.Channel; | |||||
| using Discord.API.Client.GatewaySocket; | |||||
| using Discord.API.Client.Rest; | using Discord.API.Client.Rest; | ||||
| using Discord.Logging; | using Discord.Logging; | ||||
| using Discord.Net; | using Discord.Net; | ||||
| @@ -29,8 +30,8 @@ namespace Discord | |||||
| private readonly TaskManager _taskManager; | private readonly TaskManager _taskManager; | ||||
| private readonly ServiceCollection _services; | private readonly ServiceCollection _services; | ||||
| private ConcurrentDictionary<ulong, Server> _servers; | private ConcurrentDictionary<ulong, Server> _servers; | ||||
| private ConcurrentDictionary<ulong, Channel> _channels; | |||||
| private ConcurrentDictionary<ulong, Channel> _privateChannels; //Key = RecipientId | |||||
| private ConcurrentDictionary<ulong, IChannel> _channels; | |||||
| private ConcurrentDictionary<ulong, PrivateChannel> _privateChannels; //Key = RecipientId | |||||
| private Dictionary<string, Region> _regions; | private Dictionary<string, Region> _regions; | ||||
| private Stopwatch _connectionStopwatch; | private Stopwatch _connectionStopwatch; | ||||
| @@ -71,7 +72,7 @@ namespace Discord | |||||
| /// <summary> Gets a collection of all servers this client is a member of. </summary> | /// <summary> Gets a collection of all servers this client is a member of. </summary> | ||||
| public IEnumerable<Server> Servers => _servers.Select(x => x.Value); | public IEnumerable<Server> Servers => _servers.Select(x => x.Value); | ||||
| /// <summary> Gets a collection of all private channels this client is a member of. </summary> | /// <summary> Gets a collection of all private channels this client is a member of. </summary> | ||||
| public IEnumerable<Channel> PrivateChannels => _privateChannels.Select(x => x.Value); | |||||
| public IEnumerable<PrivateChannel> PrivateChannels => _privateChannels.Select(x => x.Value); | |||||
| /// <summary> Gets a collection of all voice regions currently offered by Discord. </summary> | /// <summary> Gets a collection of all voice regions currently offered by Discord. </summary> | ||||
| public IEnumerable<Region> Regions => _regions.Select(x => x.Value); | public IEnumerable<Region> Regions => _regions.Select(x => x.Value); | ||||
| @@ -123,8 +124,8 @@ namespace Discord | |||||
| //Cache | //Cache | ||||
| //ConcurrentLevel = 2 (only REST and WebSocket can add/remove) | //ConcurrentLevel = 2 (only REST and WebSocket can add/remove) | ||||
| _servers = new ConcurrentDictionary<ulong, Server>(2, 0); | _servers = new ConcurrentDictionary<ulong, Server>(2, 0); | ||||
| _channels = new ConcurrentDictionary<ulong, Channel>(2, 0); | |||||
| _privateChannels = new ConcurrentDictionary<ulong, Channel>(2, 0); | |||||
| _channels = new ConcurrentDictionary<ulong, IChannel>(2, 0); | |||||
| _privateChannels = new ConcurrentDictionary<ulong, PrivateChannel>(2, 0); | |||||
| //Serialization | //Serialization | ||||
| Serializer = new JsonSerializer(); | Serializer = new JsonSerializer(); | ||||
| @@ -335,45 +336,47 @@ namespace Discord | |||||
| } | } | ||||
| #region Channels | #region Channels | ||||
| internal void AddChannel(Channel channel) | |||||
| internal void AddChannel(IChannel channel) | |||||
| { | { | ||||
| _channels.GetOrAdd(channel.Id, channel); | _channels.GetOrAdd(channel.Id, channel); | ||||
| } | } | ||||
| private Channel RemoveChannel(ulong id) | |||||
| private IChannel RemoveChannel(ulong id) | |||||
| { | { | ||||
| Channel channel; | |||||
| IChannel channel; | |||||
| if (_channels.TryRemove(id, out channel)) | if (_channels.TryRemove(id, out channel)) | ||||
| { | { | ||||
| if (channel.IsPrivate) | if (channel.IsPrivate) | ||||
| _privateChannels.TryRemove(channel.Recipient.Id, out channel); | |||||
| { | |||||
| PrivateChannel removed; | |||||
| _privateChannels.TryRemove((channel as PrivateChannel).Recipient.Id, out removed); | |||||
| } | |||||
| else | else | ||||
| channel.Server.RemoveChannel(id); | |||||
| (channel as PublicChannel).Server.RemoveChannel(id); | |||||
| } | } | ||||
| return channel; | return channel; | ||||
| } | } | ||||
| public Channel GetChannel(ulong id) | |||||
| public IChannel GetChannel(ulong id) | |||||
| { | { | ||||
| Channel channel; | |||||
| IChannel channel; | |||||
| _channels.TryGetValue(id, out channel); | _channels.TryGetValue(id, out channel); | ||||
| return channel; | return channel; | ||||
| } | } | ||||
| private Channel AddPrivateChannel(ulong id, ulong recipientId) | |||||
| private PrivateChannel AddPrivateChannel(APIChannel model) | |||||
| { | { | ||||
| Channel channel; | |||||
| if (_channels.TryGetOrAdd(id, x => new Channel(this, x, new User(this, recipientId, null)), out channel)) | |||||
| _privateChannels[recipientId] = channel; | |||||
| return channel; | |||||
| IChannel channel; | |||||
| if (_channels.TryGetOrAdd(model.Id, x => new PrivateChannel(x, new User(this, model.Recipient.Id, null), model), out channel)) | |||||
| _privateChannels[model.Recipient.Id] = channel as PrivateChannel; | |||||
| return channel as PrivateChannel; | |||||
| } | } | ||||
| internal Channel GetPrivateChannel(ulong recipientId) | |||||
| internal PrivateChannel GetPrivateChannel(ulong recipientId) | |||||
| { | { | ||||
| Channel channel; | |||||
| PrivateChannel channel; | |||||
| _privateChannels.TryGetValue(recipientId, out channel); | _privateChannels.TryGetValue(recipientId, out channel); | ||||
| return channel; | return channel; | ||||
| } | } | ||||
| internal Task<Channel> CreatePMChannel(User user) | |||||
| => CreatePrivateChannel(user.Id); | |||||
| public async Task<Channel> CreatePrivateChannel(ulong userId) | |||||
| public Task<PrivateChannel> CreatePrivateChannel(User user) => CreatePrivateChannel(user.Id); | |||||
| public async Task<PrivateChannel> CreatePrivateChannel(ulong userId) | |||||
| { | { | ||||
| var channel = GetPrivateChannel(userId); | var channel = GetPrivateChannel(userId); | ||||
| if (channel != null) return channel; | if (channel != null) return channel; | ||||
| @@ -381,9 +384,7 @@ namespace Discord | |||||
| var request = new CreatePrivateChannelRequest() { RecipientId = userId }; | var request = new CreatePrivateChannelRequest() { RecipientId = userId }; | ||||
| var response = await ClientAPI.Send(request).ConfigureAwait(false); | var response = await ClientAPI.Send(request).ConfigureAwait(false); | ||||
| channel = AddPrivateChannel(response.Id, userId); | |||||
| channel.Update(response); | |||||
| return channel; | |||||
| return AddPrivateChannel(response); | |||||
| } | } | ||||
| #endregion | #endregion | ||||
| @@ -429,8 +430,7 @@ namespace Discord | |||||
| #endregion | #endregion | ||||
| #region Servers | #region Servers | ||||
| private Server AddServer(ulong id) | |||||
| => _servers.GetOrAdd(id, x => new Server(this, x)); | |||||
| private Server AddServer(ulong id) => _servers.GetOrAdd(id, x => new Server(this, x)); | |||||
| private Server RemoveServer(ulong id) | private Server RemoveServer(ulong id) | ||||
| { | { | ||||
| Server server; | Server server; | ||||
| @@ -448,11 +448,6 @@ namespace Discord | |||||
| _servers.TryGetValue(id, out server); | _servers.TryGetValue(id, out server); | ||||
| return server; | return server; | ||||
| } | } | ||||
| public IEnumerable<Server> FindServers(string name) | |||||
| { | |||||
| if (name == null) throw new ArgumentNullException(nameof(name)); | |||||
| return _servers.Select(x => x.Value).Find(name); | |||||
| } | |||||
| /// <summary> Creates a new server with the provided name and region. </summary> | /// <summary> Creates a new server with the provided name and region. </summary> | ||||
| public async Task<Server> CreateServer(string name, Region region, ImageType iconType = ImageType.None, Stream icon = null) | public async Task<Server> CreateServer(string name, Region region, ImageType iconType = ImageType.None, Stream icon = null) | ||||
| @@ -494,8 +489,8 @@ namespace Discord | |||||
| //ConcurrencyLevel = 2 (only REST and WebSocket can add/remove) | //ConcurrencyLevel = 2 (only REST and WebSocket can add/remove) | ||||
| _servers = new ConcurrentDictionary<ulong, Server>(2, (int)(data.Guilds.Length * 1.05)); | _servers = new ConcurrentDictionary<ulong, Server>(2, (int)(data.Guilds.Length * 1.05)); | ||||
| _channels = new ConcurrentDictionary<ulong, Channel>(2, (int)(channelCount * 1.05)); | |||||
| _privateChannels = new ConcurrentDictionary<ulong, Channel>(2, (int)(data.PrivateChannels.Length * 1.05)); | |||||
| _channels = new ConcurrentDictionary<ulong, IChannel>(2, (int)(channelCount * 1.05)); | |||||
| _privateChannels = new ConcurrentDictionary<ulong, PrivateChannel>(2, (int)(data.PrivateChannels.Length * 1.05)); | |||||
| List<ulong> largeServers = new List<ulong>(); | List<ulong> largeServers = new List<ulong>(); | ||||
| SessionId = data.SessionId; | SessionId = data.SessionId; | ||||
| @@ -516,11 +511,7 @@ namespace Discord | |||||
| } | } | ||||
| } | } | ||||
| for (int i = 0; i < data.PrivateChannels.Length; i++) | for (int i = 0; i < data.PrivateChannels.Length; i++) | ||||
| { | |||||
| var model = data.PrivateChannels[i]; | |||||
| var channel = AddPrivateChannel(model.Id, model.Recipient.Id); | |||||
| channel.Update(model); | |||||
| } | |||||
| AddPrivateChannel(data.PrivateChannels[i]); | |||||
| if (largeServers.Count > 0) | if (largeServers.Count > 0) | ||||
| GatewaySocket.SendRequestMembers(largeServers, "", 0); | GatewaySocket.SendRequestMembers(largeServers, "", 0); | ||||
| else | else | ||||
| @@ -538,9 +529,9 @@ namespace Discord | |||||
| server.Update(data); | server.Update(data); | ||||
| if (data.Unavailable != false) | if (data.Unavailable != false) | ||||
| Logger.Info($"GUILD_CREATE: {server.Path}"); | |||||
| Logger.Info($"GUILD_CREATE: {server}"); | |||||
| else | else | ||||
| Logger.Info($"GUILD_AVAILABLE: {server.Path}"); | |||||
| Logger.Info($"GUILD_AVAILABLE: {server}"); | |||||
| if (data.Unavailable != false) | if (data.Unavailable != false) | ||||
| OnJoinedServer(server); | OnJoinedServer(server); | ||||
| @@ -556,7 +547,7 @@ namespace Discord | |||||
| { | { | ||||
| var before = Config.EnablePreUpdateEvents ? server.Clone() : null; | var before = Config.EnablePreUpdateEvents ? server.Clone() : null; | ||||
| server.Update(data); | server.Update(data); | ||||
| Logger.Info($"GUILD_UPDATE: {server.Path}"); | |||||
| Logger.Info($"GUILD_UPDATE: {server}"); | |||||
| OnServerUpdated(before, server); | OnServerUpdated(before, server); | ||||
| } | } | ||||
| else | else | ||||
| @@ -570,9 +561,9 @@ namespace Discord | |||||
| if (server != null) | if (server != null) | ||||
| { | { | ||||
| if (data.Unavailable != true) | if (data.Unavailable != true) | ||||
| Logger.Info($"GUILD_DELETE: {server.Path}"); | |||||
| Logger.Info($"GUILD_DELETE: {server}"); | |||||
| else | else | ||||
| Logger.Info($"GUILD_UNAVAILABLE: {server.Path}"); | |||||
| Logger.Info($"GUILD_UNAVAILABLE: {server}"); | |||||
| OnServerUnavailable(server); | OnServerUnavailable(server); | ||||
| if (data.Unavailable != true) | if (data.Unavailable != true) | ||||
| @@ -588,23 +579,22 @@ namespace Discord | |||||
| { | { | ||||
| var data = e.Payload.ToObject<ChannelCreateEvent>(Serializer); | var data = e.Payload.ToObject<ChannelCreateEvent>(Serializer); | ||||
| Channel channel = null; | |||||
| IChannel channel = null; | |||||
| if (data.GuildId != null) | if (data.GuildId != null) | ||||
| { | { | ||||
| var server = GetServer(data.GuildId.Value); | var server = GetServer(data.GuildId.Value); | ||||
| if (server != null) | if (server != null) | ||||
| channel = server.AddChannel(data.Id, true); | |||||
| channel = server.AddChannel(data, true); | |||||
| else | else | ||||
| { | |||||
| Logger.Warning("CHANNEL_CREATE referenced an unknown guild."); | Logger.Warning("CHANNEL_CREATE referenced an unknown guild."); | ||||
| break; | |||||
| } | |||||
| } | } | ||||
| else | else | ||||
| channel = AddPrivateChannel(data.Id, data.Recipient.Id); | |||||
| if (channel != null) | |||||
| { | |||||
| channel.Update(data); | |||||
| Logger.Info($"CHANNEL_CREATE: {channel.Path}"); | |||||
| OnChannelCreated(channel); | |||||
| } | |||||
| channel = AddPrivateChannel(data); | |||||
| Logger.Info($"CHANNEL_CREATE: {channel}"); | |||||
| OnChannelCreated(channel); | |||||
| } | } | ||||
| break; | break; | ||||
| case "CHANNEL_UPDATE": | case "CHANNEL_UPDATE": | ||||
| @@ -613,9 +603,9 @@ namespace Discord | |||||
| var channel = GetChannel(data.Id); | var channel = GetChannel(data.Id); | ||||
| if (channel != null) | if (channel != null) | ||||
| { | { | ||||
| var before = Config.EnablePreUpdateEvents ? channel.Clone() : null; | |||||
| channel.Update(data); | |||||
| Logger.Info($"CHANNEL_UPDATE: {channel.Path}"); | |||||
| var before = Config.EnablePreUpdateEvents ? (channel as Channel).Clone() : null; | |||||
| (channel as Channel).Update(data); | |||||
| Logger.Info($"CHANNEL_UPDATE: {channel}"); | |||||
| OnChannelUpdated(before, channel); | OnChannelUpdated(before, channel); | ||||
| } | } | ||||
| else | else | ||||
| @@ -628,7 +618,7 @@ namespace Discord | |||||
| var channel = RemoveChannel(data.Id); | var channel = RemoveChannel(data.Id); | ||||
| if (channel != null) | if (channel != null) | ||||
| { | { | ||||
| Logger.Info($"CHANNEL_DELETE: {channel.Path}"); | |||||
| Logger.Info($"CHANNEL_DELETE: {channel}"); | |||||
| OnChannelDestroyed(channel); | OnChannelDestroyed(channel); | ||||
| } | } | ||||
| else | else | ||||
| @@ -646,7 +636,7 @@ namespace Discord | |||||
| var user = server.AddUser(data.User.Id, true, true); | var user = server.AddUser(data.User.Id, true, true); | ||||
| user.Update(data); | user.Update(data); | ||||
| user.UpdateActivity(); | user.UpdateActivity(); | ||||
| Logger.Info($"GUILD_MEMBER_ADD: {user.Path}"); | |||||
| Logger.Info($"GUILD_MEMBER_ADD: {user}"); | |||||
| OnUserJoined(user); | OnUserJoined(user); | ||||
| } | } | ||||
| else | else | ||||
| @@ -664,7 +654,7 @@ namespace Discord | |||||
| { | { | ||||
| var before = Config.EnablePreUpdateEvents ? user.Clone() : null; | var before = Config.EnablePreUpdateEvents ? user.Clone() : null; | ||||
| user.Update(data); | user.Update(data); | ||||
| Logger.Info($"GUILD_MEMBER_UPDATE: {user.Path}"); | |||||
| Logger.Info($"GUILD_MEMBER_UPDATE: {user}"); | |||||
| OnUserUpdated(before, user); | OnUserUpdated(before, user); | ||||
| } | } | ||||
| else | else | ||||
| @@ -683,7 +673,7 @@ namespace Discord | |||||
| var user = server.RemoveUser(data.User.Id); | var user = server.RemoveUser(data.User.Id); | ||||
| if (user != null) | if (user != null) | ||||
| { | { | ||||
| Logger.Info($"GUILD_MEMBER_REMOVE: {user.Path}"); | |||||
| Logger.Info($"GUILD_MEMBER_REMOVE: {user}"); | |||||
| OnUserLeft(user); | OnUserLeft(user); | ||||
| } | } | ||||
| else | else | ||||
| @@ -732,7 +722,7 @@ namespace Discord | |||||
| { | { | ||||
| var role = server.AddRole(data.Data.Id); | var role = server.AddRole(data.Data.Id); | ||||
| role.Update(data.Data, false); | role.Update(data.Data, false); | ||||
| Logger.Info($"GUILD_ROLE_CREATE: {role.Path}"); | |||||
| Logger.Info($"GUILD_ROLE_CREATE: {role}"); | |||||
| OnRoleCreated(role); | OnRoleCreated(role); | ||||
| } | } | ||||
| else | else | ||||
| @@ -750,7 +740,7 @@ namespace Discord | |||||
| { | { | ||||
| var before = Config.EnablePreUpdateEvents ? role.Clone() : null; | var before = Config.EnablePreUpdateEvents ? role.Clone() : null; | ||||
| role.Update(data.Data, true); | role.Update(data.Data, true); | ||||
| Logger.Info($"GUILD_ROLE_UPDATE: {role.Path}"); | |||||
| Logger.Info($"GUILD_ROLE_UPDATE: {role}"); | |||||
| OnRoleUpdated(before, role); | OnRoleUpdated(before, role); | ||||
| } | } | ||||
| else | else | ||||
| @@ -769,7 +759,7 @@ namespace Discord | |||||
| var role = server.RemoveRole(data.RoleId); | var role = server.RemoveRole(data.RoleId); | ||||
| if (role != null) | if (role != null) | ||||
| { | { | ||||
| Logger.Info($"GUILD_ROLE_DELETE: {role.Path}"); | |||||
| Logger.Info($"GUILD_ROLE_DELETE: {role}"); | |||||
| OnRoleDeleted(role); | OnRoleDeleted(role); | ||||
| } | } | ||||
| else | else | ||||
| @@ -790,7 +780,7 @@ namespace Discord | |||||
| var user = server.GetUser(data.User.Id); | var user = server.GetUser(data.User.Id); | ||||
| if (user != null) | if (user != null) | ||||
| { | { | ||||
| Logger.Info($"GUILD_BAN_ADD: {user.Path}"); | |||||
| Logger.Info($"GUILD_BAN_ADD: {user}"); | |||||
| OnUserBanned(user); | OnUserBanned(user); | ||||
| } | } | ||||
| else | else | ||||
| @@ -808,7 +798,7 @@ namespace Discord | |||||
| { | { | ||||
| var user = new User(this, data.User.Id, server); | var user = new User(this, data.User.Id, server); | ||||
| user.Update(data.User); | user.Update(data.User); | ||||
| Logger.Info($"GUILD_BAN_REMOVE: {user.Path}"); | |||||
| Logger.Info($"GUILD_BAN_REMOVE: {user}"); | |||||
| OnUserUnbanned(user); | OnUserUnbanned(user); | ||||
| } | } | ||||
| else | else | ||||
| @@ -821,40 +811,20 @@ namespace Discord | |||||
| { | { | ||||
| var data = e.Payload.ToObject<MessageCreateEvent>(Serializer); | var data = e.Payload.ToObject<MessageCreateEvent>(Serializer); | ||||
| Channel channel = GetChannel(data.ChannelId); | |||||
| var channel = GetChannel(data.ChannelId) as ITextChannel; | |||||
| if (channel != null) | if (channel != null) | ||||
| { | { | ||||
| var user = channel.GetUserFast(data.Author.Id); | |||||
| var user = (channel as Channel).GetUser(data.Author.Id); | |||||
| if (user != null) | if (user != null) | ||||
| { | { | ||||
| Message msg = null; | Message msg = null; | ||||
| bool isAuthor = data.Author.Id == CurrentUser.Id; | |||||
| //ulong nonce = 0; | |||||
| /*if (data.Author.Id == _privateUser.Id && Config.UseMessageQueue) | |||||
| { | |||||
| if (data.Nonce != null && ulong.TryParse(data.Nonce, out nonce)) | |||||
| msg = _messages[nonce]; | |||||
| }*/ | |||||
| if (msg == null) | |||||
| { | |||||
| msg = channel.AddMessage(data.Id, user, data.Timestamp.Value); | |||||
| //nonce = 0; | |||||
| } | |||||
| //Remapped queued message | |||||
| /*if (nonce != 0) | |||||
| { | |||||
| msg = _messages.Remap(nonce, data.Id); | |||||
| msg.Id = data.Id; | |||||
| RaiseMessageSent(msg); | |||||
| }*/ | |||||
| msg = (channel as Channel).MessageManager.Add(data.Id, user, data.Timestamp.Value); | |||||
| msg.Update(data); | msg.Update(data); | ||||
| user.UpdateActivity(); | user.UpdateActivity(); | ||||
| Logger.Verbose($"MESSAGE_CREATE: {channel.Path} ({user.Name ?? "Unknown"})"); | |||||
| Logger.Verbose($"MESSAGE_CREATE: {channel} ({user})"); | |||||
| OnMessageReceived(msg); | OnMessageReceived(msg); | ||||
| } | } | ||||
| else | else | ||||
| @@ -867,13 +837,13 @@ namespace Discord | |||||
| case "MESSAGE_UPDATE": | case "MESSAGE_UPDATE": | ||||
| { | { | ||||
| var data = e.Payload.ToObject<MessageUpdateEvent>(Serializer); | var data = e.Payload.ToObject<MessageUpdateEvent>(Serializer); | ||||
| var channel = GetChannel(data.ChannelId); | |||||
| var channel = GetChannel(data.ChannelId) as ITextChannel; | |||||
| if (channel != null) | if (channel != null) | ||||
| { | { | ||||
| var msg = channel.GetMessage(data.Id, data.Author?.Id); | |||||
| var msg = (channel as Channel).MessageManager.Get(data.Id, data.Author?.Id); | |||||
| var before = Config.EnablePreUpdateEvents ? msg.Clone() : null; | var before = Config.EnablePreUpdateEvents ? msg.Clone() : null; | ||||
| msg.Update(data); | msg.Update(data); | ||||
| Logger.Verbose($"MESSAGE_UPDATE: {channel.Path} ({data.Author?.Username ?? "Unknown"})"); | |||||
| Logger.Verbose($"MESSAGE_UPDATE: {channel} ({data.Author?.Username ?? "Unknown"})"); | |||||
| OnMessageUpdated(before, msg); | OnMessageUpdated(before, msg); | ||||
| } | } | ||||
| else | else | ||||
| @@ -883,11 +853,11 @@ namespace Discord | |||||
| case "MESSAGE_DELETE": | case "MESSAGE_DELETE": | ||||
| { | { | ||||
| var data = e.Payload.ToObject<MessageDeleteEvent>(Serializer); | var data = e.Payload.ToObject<MessageDeleteEvent>(Serializer); | ||||
| var channel = GetChannel(data.ChannelId); | |||||
| var channel = GetChannel(data.ChannelId) as ITextChannel; | |||||
| if (channel != null) | if (channel != null) | ||||
| { | { | ||||
| var msg = channel.RemoveMessage(data.Id); | |||||
| Logger.Verbose($"MESSAGE_DELETE: {channel.Path} ({msg.User?.Name ?? "Unknown"})"); | |||||
| var msg = (channel as Channel).MessageManager.Remove(data.Id); | |||||
| Logger.Verbose($"MESSAGE_DELETE: {channel} ({msg.User?.Name ?? "Unknown"})"); | |||||
| OnMessageDeleted(msg); | OnMessageDeleted(msg); | ||||
| } | } | ||||
| else | else | ||||
| @@ -921,7 +891,7 @@ namespace Discord | |||||
| if (user != null) | if (user != null) | ||||
| { | { | ||||
| if (Config.LogLevel == LogSeverity.Debug) | if (Config.LogLevel == LogSeverity.Debug) | ||||
| Logger.Debug($"PRESENCE_UPDATE: {user.Path}"); | |||||
| Logger.Debug($"PRESENCE_UPDATE: {user}"); | |||||
| var before = Config.EnablePreUpdateEvents ? user.Clone() : null; | var before = Config.EnablePreUpdateEvents ? user.Clone() : null; | ||||
| user.Update(data); | user.Update(data); | ||||
| OnUserUpdated(before, user); | OnUserUpdated(before, user); | ||||
| @@ -933,29 +903,18 @@ namespace Discord | |||||
| case "TYPING_START": | case "TYPING_START": | ||||
| { | { | ||||
| var data = e.Payload.ToObject<TypingStartEvent>(Serializer); | var data = e.Payload.ToObject<TypingStartEvent>(Serializer); | ||||
| var channel = GetChannel(data.ChannelId); | |||||
| var channel = GetChannel(data.ChannelId) as ITextChannel; | |||||
| if (channel != null) | if (channel != null) | ||||
| { | { | ||||
| User user; | |||||
| if (channel.IsPrivate) | |||||
| { | |||||
| if (channel.Recipient.Id == data.UserId) | |||||
| user = channel.Recipient; | |||||
| else | |||||
| break; | |||||
| } | |||||
| else | |||||
| user = channel.Server.GetUser(data.UserId); | |||||
| User user = (channel as Channel).GetUser(data.UserId); | |||||
| if (user != null) | if (user != null) | ||||
| { | { | ||||
| if (Config.LogLevel == LogSeverity.Debug) | if (Config.LogLevel == LogSeverity.Debug) | ||||
| Logger.Debug($"TYPING_START: {channel.Path} ({user.Name})"); | |||||
| Logger.Debug($"TYPING_START: {user.ToString(channel)}"); | |||||
| OnUserIsTypingUpdated(channel, user); | OnUserIsTypingUpdated(channel, user); | ||||
| user.UpdateActivity(); | user.UpdateActivity(); | ||||
| } | } | ||||
| } | } | ||||
| else | |||||
| Logger.Warning("TYPING_START referenced an unknown channel."); | |||||
| } | } | ||||
| break; | break; | ||||
| @@ -970,7 +929,7 @@ namespace Discord | |||||
| if (user != null) | if (user != null) | ||||
| { | { | ||||
| if (Config.LogLevel == LogSeverity.Debug) | if (Config.LogLevel == LogSeverity.Debug) | ||||
| Logger.Debug($"VOICE_STATE_UPDATE: {user.Path}"); | |||||
| Logger.Debug($"VOICE_STATE_UPDATE: {user}"); | |||||
| var before = Config.EnablePreUpdateEvents ? user.Clone() : null; | var before = Config.EnablePreUpdateEvents ? user.Clone() : null; | ||||
| user.Update(data); | user.Update(data); | ||||
| //Logger.Verbose($"Voice Updated: {server.Name}/{user.Name}"); | //Logger.Verbose($"Voice Updated: {server.Name}/{user.Name}"); | ||||
| @@ -0,0 +1,42 @@ | |||||
| using System; | |||||
| namespace Discord | |||||
| { | |||||
| public static class EnumConverters | |||||
| { | |||||
| public static ChannelType ToChannelType(string value) | |||||
| { | |||||
| switch (value) | |||||
| { | |||||
| case "text": return ChannelType.Text; | |||||
| case "voice": return ChannelType.Voice; | |||||
| default: throw new ArgumentException("Unknown channel type", nameof(value)); | |||||
| } | |||||
| } | |||||
| public static string ToString(ChannelType value) | |||||
| { | |||||
| if ((value & ChannelType.Text) != 0) return "text"; | |||||
| if ((value & ChannelType.Voice) != 0) return "voice"; | |||||
| throw new ArgumentException("Invalid channel tType", nameof(value)); | |||||
| } | |||||
| public static PermissionTarget ToPermissionTarget(string value) | |||||
| { | |||||
| switch (value) | |||||
| { | |||||
| case "member": return PermissionTarget.User; | |||||
| case "role": return PermissionTarget.Role; | |||||
| default: throw new ArgumentException("Unknown permission target", nameof(value)); | |||||
| } | |||||
| } | |||||
| public static string ToString(PermissionTarget value) | |||||
| { | |||||
| switch (value) | |||||
| { | |||||
| case PermissionTarget.User: return "member"; | |||||
| case PermissionTarget.Role: return "role"; | |||||
| default: throw new ArgumentException("Invalid permission target", nameof(value)); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -2,36 +2,12 @@ | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| public class ChannelType : StringEnum, IEquatable<ChannelType> | |||||
| { | |||||
| /// <summary> A text-only channel. </summary> | |||||
| public static ChannelType Text { get; } = new ChannelType("text"); | |||||
| /// <summary> A voice-only channel. </summary> | |||||
| public static ChannelType Voice { get; } = new ChannelType("voice"); | |||||
| private ChannelType(string value) | |||||
| : base(value) { } | |||||
| public static ChannelType FromString(string value) | |||||
| { | |||||
| switch (value) | |||||
| { | |||||
| case null: | |||||
| return null; | |||||
| case "text": | |||||
| return Text; | |||||
| case "voice": | |||||
| return Voice; | |||||
| default: | |||||
| return new ChannelType(value); | |||||
| } | |||||
| } | |||||
| public static implicit operator ChannelType(string value) => FromString(value); | |||||
| public static bool operator ==(ChannelType a, ChannelType b) => ((object)a == null && (object)b == null) || (a?.Equals(b) ?? false); | |||||
| public static bool operator !=(ChannelType a, ChannelType b) => !(a == b); | |||||
| public override int GetHashCode() => Value.GetHashCode(); | |||||
| public override bool Equals(object obj) => (obj as ChannelType)?.Equals(this) ?? false; | |||||
| public bool Equals(ChannelType type) => type != null && type.Value == Value; | |||||
| [Flags] | |||||
| public enum ChannelType : byte | |||||
| { | |||||
| Public = 0x01, | |||||
| Private = 0x02, | |||||
| Text = 0x10, | |||||
| Voice = 0x20 | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,35 +1,8 @@ | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| public class PermissionTarget : StringEnum | |||||
| public enum PermissionTarget : byte | |||||
| { | { | ||||
| /// <summary> A text-only channel. </summary> | |||||
| public static PermissionTarget Role { get; } = new PermissionTarget("role"); | |||||
| /// <summary> A voice-only channel. </summary> | |||||
| public static PermissionTarget User { get; } = new PermissionTarget("member"); | |||||
| private PermissionTarget(string value) | |||||
| : base(value) { } | |||||
| public static PermissionTarget FromString(string value) | |||||
| { | |||||
| switch (value) | |||||
| { | |||||
| case null: | |||||
| return null; | |||||
| case "role": | |||||
| return Role; | |||||
| case "member": | |||||
| return User; | |||||
| default: | |||||
| return new PermissionTarget(value); | |||||
| } | |||||
| } | |||||
| public static implicit operator PermissionTarget(string value) => FromString(value); | |||||
| public static bool operator ==(PermissionTarget a, PermissionTarget b) => ((object)a == null && (object)b == null) || (a?.Equals(b) ?? false); | |||||
| public static bool operator !=(PermissionTarget a, PermissionTarget b) => !(a == b); | |||||
| public override int GetHashCode() => Value.GetHashCode(); | |||||
| public override bool Equals(object obj) => (obj as PermissionTarget)?.Equals(this) ?? false; | |||||
| public bool Equals(PermissionTarget type) => type != null && type.Value == Value; | |||||
| User, | |||||
| Role | |||||
| } | } | ||||
| } | } | ||||
| @@ -4,10 +4,8 @@ namespace Discord | |||||
| { | { | ||||
| public class ChannelEventArgs : EventArgs | public class ChannelEventArgs : EventArgs | ||||
| { | { | ||||
| public Channel Channel { get; } | |||||
| public IChannel Channel { get; } | |||||
| public Server Server => Channel.Server; | |||||
| public ChannelEventArgs(Channel channel) { Channel = channel; } | |||||
| public ChannelEventArgs(IChannel channel) { Channel = channel; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -4,12 +4,10 @@ namespace Discord | |||||
| { | { | ||||
| public class ChannelUpdatedEventArgs : EventArgs | public class ChannelUpdatedEventArgs : EventArgs | ||||
| { | { | ||||
| public Channel Before { get; } | |||||
| public Channel After { get; } | |||||
| public IChannel Before { get; } | |||||
| public IChannel After { get; } | |||||
| public Server Server => After.Server; | |||||
| public ChannelUpdatedEventArgs(Channel before, Channel after) | |||||
| public ChannelUpdatedEventArgs(IChannel before, IChannel after) | |||||
| { | { | ||||
| Before = before; | Before = before; | ||||
| After = after; | After = after; | ||||
| @@ -7,7 +7,7 @@ namespace Discord | |||||
| public Message Message { get; } | public Message Message { get; } | ||||
| public User User => Message.User; | public User User => Message.User; | ||||
| public Channel Channel => Message.Channel; | |||||
| public ITextChannel Channel => Message.Channel; | |||||
| public Server Server => Message.Server; | public Server Server => Message.Server; | ||||
| public MessageEventArgs(Message msg) { Message = msg; } | public MessageEventArgs(Message msg) { Message = msg; } | ||||
| @@ -8,7 +8,7 @@ namespace Discord | |||||
| public Message After { get; } | public Message After { get; } | ||||
| public User User => After.User; | public User User => After.User; | ||||
| public Channel Channel => After.Channel; | |||||
| public ITextChannel Channel => After.Channel; | |||||
| public Server Server => After.Server; | public Server Server => After.Server; | ||||
| public MessageUpdatedEventArgs(Message before, Message after) | public MessageUpdatedEventArgs(Message before, Message after) | ||||
| @@ -1,11 +1,11 @@ | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| public class ChannelUserEventArgs | |||||
| public class TypingEventArgs | |||||
| { | { | ||||
| public Channel Channel { get; } | |||||
| public ITextChannel Channel { get; } | |||||
| public User User { get; } | public User User { get; } | ||||
| public ChannelUserEventArgs(Channel channel, User user) | |||||
| public TypingEventArgs(ITextChannel channel, User user) | |||||
| { | { | ||||
| Channel = channel; | Channel = channel; | ||||
| User = user; | User = user; | ||||
| @@ -0,0 +1,11 @@ | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord | |||||
| { | |||||
| public interface IModel | |||||
| { | |||||
| ulong Id { get; } | |||||
| Task Save(); | |||||
| } | |||||
| } | |||||
| @@ -1,9 +1,7 @@ | |||||
| using System; | using System; | ||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
| using System.Collections.Generic; | |||||
| using System.Globalization; | using System.Globalization; | ||||
| using System.IO; | using System.IO; | ||||
| using System.Linq; | |||||
| using System.Runtime.CompilerServices; | using System.Runtime.CompilerServices; | ||||
| namespace Discord | namespace Discord | ||||
| @@ -63,85 +61,6 @@ namespace Discord | |||||
| } | } | ||||
| } | } | ||||
| public static IEnumerable<Channel> Find(this IEnumerable<Channel> channels, string name, ChannelType type = null, bool exactMatch = false) | |||||
| { | |||||
| //Search by name | |||||
| var query = channels | |||||
| .Where(x => string.Equals(x.Name, name, exactMatch ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)); | |||||
| if (!exactMatch) | |||||
| { | |||||
| if (name.Length >= 2 && name[0] == '<' && name[1] == '#' && name[name.Length - 1] == '>') //Search by raw mention | |||||
| { | |||||
| ulong id; | |||||
| if (name.Substring(2, name.Length - 3).TryToId(out id)) | |||||
| { | |||||
| var channel = channels.Where(x => x.Id == id).FirstOrDefault(); | |||||
| if (channel != null) | |||||
| query = query.Concat(new Channel[] { channel }); | |||||
| } | |||||
| } | |||||
| if (name.Length >= 1 && name[0] == '#' && (type == null || type == ChannelType.Text)) //Search by clean mention | |||||
| { | |||||
| string name2 = name.Substring(1); | |||||
| query = query.Concat(channels | |||||
| .Where(x => x.Type == ChannelType.Text && string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase))); | |||||
| } | |||||
| } | |||||
| if (type != null) | |||||
| query = query.Where(x => x.Type == type); | |||||
| return query; | |||||
| } | |||||
| public static IEnumerable<User> Find(this IEnumerable<User> users, | |||||
| string name, ushort? discriminator = null, bool exactMatch = false) | |||||
| { | |||||
| //Search by name | |||||
| var query = users | |||||
| .Where(x => string.Equals(x.Name, name, exactMatch ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)); | |||||
| if (!exactMatch) | |||||
| { | |||||
| if (name.Length >= 2 && name[0] == '<' && name[1] == '@' && name[name.Length - 1] == '>') //Search by raw mention | |||||
| { | |||||
| ulong id; | |||||
| if (name.Substring(2, name.Length - 3).TryToId(out id)) | |||||
| { | |||||
| var user = users.Where(x => x.Id == id).FirstOrDefault(); | |||||
| if (user != null) | |||||
| query = query.Concat(new User[] { user }); | |||||
| } | |||||
| } | |||||
| if (name.Length >= 1 && name[0] == '@') //Search by clean mention | |||||
| { | |||||
| string name2 = name.Substring(1); | |||||
| query = query.Concat(users | |||||
| .Where(x => string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase))); | |||||
| } | |||||
| } | |||||
| if (discriminator != null) | |||||
| query = query.Where(x => x.Discriminator == discriminator.Value); | |||||
| return query; | |||||
| } | |||||
| public static IEnumerable<Role> Find(this IEnumerable<Role> roles, string name, bool exactMatch = false) | |||||
| { | |||||
| // if (name.StartsWith("@")) | |||||
| // { | |||||
| // string name2 = name.Substring(1); | |||||
| // return _roles.Where(x => x.Server.Id == server.Id && | |||||
| // string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) || | |||||
| // string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); | |||||
| // } | |||||
| // else | |||||
| return roles.Where(x => string.Equals(x.Name, name, exactMatch ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)); | |||||
| } | |||||
| public static IEnumerable<Server> Find(this IEnumerable<Server> servers, string name, bool exactMatch = false) | |||||
| => servers.Where(x => string.Equals(x.Name, name, exactMatch ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)); | |||||
| public static string Base64(this Stream stream, ImageType type, string existingId) | public static string Base64(this Stream stream, ImageType type, string existingId) | ||||
| { | { | ||||
| if (type == ImageType.None) | if (type == ImageType.None) | ||||
| @@ -57,12 +57,12 @@ namespace Discord.Net | |||||
| _pendingSendsByNonce = new ConcurrentDictionary<int, string>(); | _pendingSendsByNonce = new ConcurrentDictionary<int, string>(); | ||||
| } | } | ||||
| internal Message QueueSend(Channel channel, string text, bool isTTS) | |||||
| internal Message QueueSend(ITextChannel channel, string text, bool isTTS) | |||||
| { | { | ||||
| Message msg = new Message(0, channel, channel.IsPrivate ? channel.Client.PrivateUser : channel.Server.CurrentUser); | |||||
| Message msg = new Message(0, channel, (channel as Channel).CurrentUser); | |||||
| msg.IsTTS = isTTS; | msg.IsTTS = isTTS; | ||||
| msg.RawText = text; | msg.RawText = text; | ||||
| msg.Text = msg.Resolve(text); | |||||
| msg.Text = Message.ResolveMentions(msg.Channel, msg.Text); | |||||
| msg.Nonce = GenerateNonce(); | msg.Nonce = GenerateNonce(); | ||||
| if (_pendingSendsByNonce.TryAdd(msg.Nonce, text)) | if (_pendingSendsByNonce.TryAdd(msg.Nonce, text)) | ||||
| { | { | ||||
| @@ -111,110 +111,99 @@ namespace Discord.Net | |||||
| { | { | ||||
| return Task.Run(async () => | return Task.Run(async () => | ||||
| { | { | ||||
| try | |||||
| while (!cancelToken.IsCancellationRequested) | |||||
| { | { | ||||
| while (!cancelToken.IsCancellationRequested) | |||||
| Message msg; | |||||
| while (_pendingSends.TryDequeue(out msg)) | |||||
| { | { | ||||
| Message msg; | |||||
| while (_pendingSends.TryDequeue(out msg)) | |||||
| DecrementCount(); | |||||
| string text; | |||||
| if (_pendingSendsByNonce.TryRemove(msg.Nonce, out text)) //If it was deleted from queue, this will fail | |||||
| { | { | ||||
| DecrementCount(); | |||||
| string text; | |||||
| if (_pendingSendsByNonce.TryRemove(msg.Nonce, out text)) //If it was deleted from queue, this will fail | |||||
| try | |||||
| { | { | ||||
| try | |||||
| //msg.RawText = text; | |||||
| //msg.Text = Message.ResolveMentions(msg.Channel, text); | |||||
| var request = new SendMessageRequest(msg.Channel.Id) | |||||
| { | { | ||||
| msg.RawText = text; | |||||
| msg.Text = msg.Resolve(text); | |||||
| var request = new SendMessageRequest(msg.Channel.Id) | |||||
| { | |||||
| Content = msg.RawText, | |||||
| Nonce = msg.Nonce.ToString(), | |||||
| IsTTS = msg.IsTTS | |||||
| }; | |||||
| var response = await _rest.Send(request).ConfigureAwait(false); | |||||
| msg.State = MessageState.Normal; | |||||
| msg.Id = response.Id; | |||||
| msg.Update(response); | |||||
| } | |||||
| catch (Exception ex) | |||||
| { | |||||
| msg.State = MessageState.Failed; | |||||
| _logger.Error($"Failed to send message to {msg.Channel.Path}", ex); | |||||
| } | |||||
| Content = text, | |||||
| Nonce = msg.Nonce.ToString(), | |||||
| IsTTS = msg.IsTTS | |||||
| }; | |||||
| var response = await _rest.Send(request).ConfigureAwait(false); | |||||
| msg.Id = response.Id; | |||||
| msg.State = MessageState.Normal; | |||||
| msg.Update(response); | |||||
| } | |||||
| catch (Exception ex) | |||||
| { | |||||
| msg.State = MessageState.Failed; | |||||
| _logger.Error($"Failed to send message to {msg.Channel}", ex); | |||||
| } | } | ||||
| } | } | ||||
| await Task.Delay(Discord.DiscordConfig.MessageQueueInterval).ConfigureAwait(false); | |||||
| } | } | ||||
| await Task.Delay(DiscordConfig.MessageQueueInterval).ConfigureAwait(false); | |||||
| } | } | ||||
| catch (OperationCanceledException) { } | |||||
| }); | }); | ||||
| } | } | ||||
| private Task RunEditQueue(CancellationToken cancelToken) | private Task RunEditQueue(CancellationToken cancelToken) | ||||
| { | { | ||||
| return Task.Run(async () => | return Task.Run(async () => | ||||
| { | { | ||||
| try | |||||
| while (!cancelToken.IsCancellationRequested) | |||||
| { | { | ||||
| while (!cancelToken.IsCancellationRequested) | |||||
| MessageEdit edit; | |||||
| while (_pendingEdits.TryPeek(out edit) && edit.Message.State != MessageState.Queued) | |||||
| { | { | ||||
| MessageEdit edit; | |||||
| while (_pendingEdits.TryPeek(out edit) && edit.Message.State != MessageState.Queued) | |||||
| if (_pendingEdits.TryDequeue(out edit)) | |||||
| { | { | ||||
| if (_pendingEdits.TryDequeue(out edit)) | |||||
| DecrementCount(); | |||||
| if (edit.Message.State == MessageState.Normal) | |||||
| { | { | ||||
| DecrementCount(); | |||||
| if (edit.Message.State == MessageState.Normal) | |||||
| try | |||||
| { | { | ||||
| try | |||||
| var request = new UpdateMessageRequest(edit.Message.Channel.Id, edit.Message.Id) | |||||
| { | { | ||||
| var request = new UpdateMessageRequest(edit.Message.Channel.Id, edit.Message.Id) | |||||
| { | |||||
| Content = edit.NewText | |||||
| }; | |||||
| await _rest.Send(request).ConfigureAwait(false); | |||||
| } | |||||
| catch (Exception ex) { _logger.Error($"Failed to edit message {edit.Message.Path}", ex); } | |||||
| Content = edit.NewText | |||||
| }; | |||||
| await _rest.Send(request).ConfigureAwait(false); | |||||
| } | } | ||||
| catch (Exception ex) { _logger.Error($"Failed to edit message {edit.Message}", ex); } | |||||
| } | } | ||||
| } | } | ||||
| await Task.Delay(Discord.DiscordConfig.MessageQueueInterval).ConfigureAwait(false); | |||||
| } | } | ||||
| await Task.Delay(DiscordConfig.MessageQueueInterval).ConfigureAwait(false); | |||||
| } | } | ||||
| catch (OperationCanceledException) { } | |||||
| }); | }); | ||||
| } | } | ||||
| private Task RunDeleteQueue(CancellationToken cancelToken) | private Task RunDeleteQueue(CancellationToken cancelToken) | ||||
| { | { | ||||
| return Task.Run(async () => | return Task.Run(async () => | ||||
| { | { | ||||
| try | |||||
| while (!cancelToken.IsCancellationRequested) | |||||
| { | { | ||||
| while (!cancelToken.IsCancellationRequested) | |||||
| Message msg; | |||||
| while (_pendingDeletes.TryPeek(out msg) && msg.State != MessageState.Queued) | |||||
| { | { | ||||
| Message msg; | |||||
| while (_pendingDeletes.TryPeek(out msg) && msg.State != MessageState.Queued) | |||||
| if (_pendingDeletes.TryDequeue(out msg)) | |||||
| { | { | ||||
| if (_pendingDeletes.TryDequeue(out msg)) | |||||
| DecrementCount(); | |||||
| if (msg.State == MessageState.Normal) | |||||
| { | { | ||||
| DecrementCount(); | |||||
| if (msg.State == MessageState.Normal) | |||||
| try | |||||
| { | { | ||||
| try | |||||
| { | |||||
| var request = new DeleteMessageRequest(msg.Channel.Id, msg.Id); | |||||
| await _rest.Send(request).ConfigureAwait(false); | |||||
| } | |||||
| catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } //Ignore | |||||
| catch (Exception ex) { _logger.Error($"Failed to delete message {msg.Path}", ex); } | |||||
| var request = new DeleteMessageRequest(msg.Channel.Id, msg.Id); | |||||
| await _rest.Send(request).ConfigureAwait(false); | |||||
| msg.State = MessageState.Deleted; | |||||
| } | } | ||||
| catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } //Ignore | |||||
| catch (Exception ex) { _logger.Error($"Failed to delete message {msg}", ex); } | |||||
| } | } | ||||
| } | } | ||||
| await Task.Delay(Discord.DiscordConfig.MessageQueueInterval).ConfigureAwait(false); | |||||
| } | } | ||||
| await Task.Delay(Discord.DiscordConfig.MessageQueueInterval).ConfigureAwait(false); | |||||
| } | } | ||||
| catch (OperationCanceledException) { } | |||||
| }); | }); | ||||
| } | } | ||||
| @@ -1,602 +1,55 @@ | |||||
| using Discord.API.Client; | |||||
| using Discord.API.Client.Rest; | |||||
| using Discord.Net; | |||||
| using System; | |||||
| using System.Collections.Concurrent; | |||||
| using APIChannel = Discord.API.Client.Channel; | |||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.IO; | |||||
| using System.Linq; | |||||
| using System.Net; | |||||
| using System.Threading.Tasks; | |||||
| using APIChannel = Discord.API.Client.Channel; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| public class Channel : IMentionable | |||||
| public abstract class Channel : IChannel | |||||
| { | { | ||||
| private readonly static Action<Channel, Channel> _cloner = DynamicIL.CreateCopyMethod<Channel>(); | |||||
| private struct Member | |||||
| { | |||||
| public User User { get; } | |||||
| public ChannelPermissions Permissions { get; } | |||||
| public Member(User user, ChannelPermissions permissions) | |||||
| { | |||||
| User = user; | |||||
| Permissions = permissions; | |||||
| } | |||||
| } | |||||
| public class PermissionOverwrite | |||||
| /// <summary> An entry in a public channel's permissions that gives or takes permissions from a specific role or user. </summary> | |||||
| public class PermissionRule | |||||
| { | { | ||||
| /// <summary> The type of object TargetId is referring to. </summary> | |||||
| public PermissionTarget TargetType { get; } | public PermissionTarget TargetType { get; } | ||||
| /// <summary> The Id of an object, whos type is specified by TargetType, that is the target of permissions being added or taken away. </summary> | |||||
| public ulong TargetId { get; } | public ulong TargetId { get; } | ||||
| public ChannelPermissionOverrides Permissions { get; } | |||||
| /// <summary> A collection of permissions that are added or taken away from the target. </summary> | |||||
| public ChannelTriStatePermissions Permissions { get; } | |||||
| internal PermissionOverwrite(PermissionTarget targetType, ulong targetId, uint allow, uint deny) | |||||
| internal PermissionRule(PermissionTarget targetType, ulong targetId, uint allow, uint deny) | |||||
| { | { | ||||
| TargetType = targetType; | TargetType = targetType; | ||||
| TargetId = targetId; | TargetId = targetId; | ||||
| Permissions = new ChannelPermissionOverrides(allow, deny); | |||||
| Permissions = new ChannelTriStatePermissions(allow, deny); | |||||
| } | } | ||||
| } | } | ||||
| private readonly ConcurrentDictionary<ulong, Member> _users; | |||||
| private readonly ConcurrentDictionary<ulong, Message> _messages; | |||||
| private Dictionary<ulong, PermissionOverwrite> _permissionOverwrites; | |||||
| public DiscordClient Client { get; } | |||||
| /// <summary> Gets the unique identifier for this channel. </summary> | /// <summary> Gets the unique identifier for this channel. </summary> | ||||
| public ulong Id { get; } | public ulong Id { get; } | ||||
| /// <summary> Gets the server owning this channel, if this is a public chat. </summary> | |||||
| public Server Server { get; } | |||||
| /// <summary> Gets the target user, if this is a private chat. </summary> | |||||
| public User Recipient { get; } | |||||
| /// <summary> Gets the name of this channel. </summary> | |||||
| public string Name { get; private set; } | |||||
| /// <summary> Gets the topic of this channel. </summary> | |||||
| public string Topic { get; private set; } | |||||
| /// <summary> Gets the position of this channel relative to other channels in this server. </summary> | |||||
| public int Position { get; private set; } | |||||
| /// <summary> Gets the type of this channel). </summary> | |||||
| public ChannelType Type { get; private set; } | |||||
| /// <summary> Gets the path to this object. </summary> | |||||
| internal string Path => $"{Server?.Name ?? "[Private]"}/{Name}"; | |||||
| /// <summary> Gets true if this is a private chat with another user. </summary> | |||||
| public bool IsPrivate => Recipient != null; | |||||
| /// <summary> Gets the string used to mention this channel. </summary> | |||||
| public string Mention => $"<#{Id}>"; | |||||
| /// <summary> Gets a collection of all messages the client has seen posted in this channel. This collection does not guarantee any ordering. </summary> | |||||
| public IEnumerable<Message> Messages => _messages?.Values ?? Enumerable.Empty<Message>(); | |||||
| /// <summary> Gets a collection of all custom permissions used for this channel. </summary> | |||||
| public IEnumerable<PermissionOverwrite> PermissionOverwrites => _permissionOverwrites.Select(x => x.Value); | |||||
| /// <summary> Gets a collection of all users with read access to this channel. </summary> | |||||
| public IEnumerable<User> Users | |||||
| { | |||||
| get | |||||
| { | |||||
| if (Client.Config.UsePermissionsCache) | |||||
| { | |||||
| if (IsPrivate) | |||||
| return new User[] { Client.PrivateUser, Recipient }; | |||||
| else if (Type == ChannelType.Text) | |||||
| return _users.Values.Where(x => x.Permissions.ReadMessages == true).Select(x => x.User); | |||||
| else if (Type == ChannelType.Voice) | |||||
| return _users.Values.Select(x => x.User).Where(x => x.VoiceChannel == this); | |||||
| } | |||||
| else | |||||
| { | |||||
| if (IsPrivate) | |||||
| return new User[] { Client.PrivateUser, Recipient }; | |||||
| else if (Type == ChannelType.Text) | |||||
| { | |||||
| ChannelPermissions perms = new ChannelPermissions(); | |||||
| return Server.Users.Where(x => | |||||
| { | |||||
| UpdatePermissions(x, ref perms); | |||||
| return perms.ReadMessages == true; | |||||
| }); | |||||
| } | |||||
| else if (Type == ChannelType.Voice) | |||||
| return Server.Users.Where(x => x.VoiceChannel == this); | |||||
| } | |||||
| return Enumerable.Empty<User>(); | |||||
| } | |||||
| } | |||||
| internal Channel(DiscordClient client, ulong id, Server server) | |||||
| : this(client, id) | |||||
| { | |||||
| Server = server; | |||||
| if (client.Config.UsePermissionsCache) | |||||
| _users = new ConcurrentDictionary<ulong, Member>(2, (int)(server.UserCount * 1.05)); | |||||
| } | |||||
| internal Channel(DiscordClient client, ulong id, User recipient) | |||||
| : this(client, id) | |||||
| { | |||||
| Recipient = recipient; | |||||
| Type = ChannelType.Text; //Discord doesn't give us a type for private channels | |||||
| } | |||||
| private Channel(DiscordClient client, ulong id) | |||||
| { | |||||
| Client = client; | |||||
| Id = id; | |||||
| _permissionOverwrites = new Dictionary<ulong, PermissionOverwrite>(); | |||||
| if (client.Config.MessageCacheSize > 0) | |||||
| _messages = new ConcurrentDictionary<ulong, Message>(2, (int)(client.Config.MessageCacheSize * 1.05)); | |||||
| } | |||||
| internal void Update(APIChannel model) | |||||
| { | |||||
| if (!IsPrivate && model.Name != null) | |||||
| Name = model.Name; | |||||
| if (model.Type != null) | |||||
| Type = model.Type; | |||||
| if (model.Position != null) | |||||
| Position = model.Position.Value; | |||||
| if (model.Topic != null) | |||||
| Topic = model.Topic; | |||||
| if (model.Recipient != null) | |||||
| { | |||||
| Recipient.Update(model.Recipient); | |||||
| Name = $"@{Recipient}"; | |||||
| } | |||||
| if (model.PermissionOverwrites != null) | |||||
| { | |||||
| _permissionOverwrites = model.PermissionOverwrites | |||||
| .Select(x => new PermissionOverwrite(PermissionTarget.FromString(x.Type), x.Id, x.Allow, x.Deny)) | |||||
| .ToDictionary(x => x.TargetId); | |||||
| UpdatePermissions(); | |||||
| } | |||||
| } | |||||
| /// <summary> Edits this channel, changing only non-null attributes. </summary> | |||||
| public async Task Edit(string name = null, string topic = null, int? position = null) | |||||
| { | |||||
| if (name != null || topic != null) | |||||
| { | |||||
| var request = new UpdateChannelRequest(Id) | |||||
| { | |||||
| Name = name ?? Name, | |||||
| Topic = topic ?? Topic, | |||||
| Position = Position | |||||
| }; | |||||
| await Client.ClientAPI.Send(request).ConfigureAwait(false); | |||||
| } | |||||
| if (position != null) | |||||
| { | |||||
| Channel[] channels = Server.AllChannels.Where(x => x.Type == Type).OrderBy(x => x.Position).ToArray(); | |||||
| int oldPos = Array.IndexOf(channels, this); | |||||
| var newPosChannel = channels.Where(x => x.Position > position).FirstOrDefault(); | |||||
| int newPos = (newPosChannel != null ? Array.IndexOf(channels, newPosChannel) : channels.Length) - 1; | |||||
| if (newPos < 0) | |||||
| newPos = 0; | |||||
| int minPos; | |||||
| public abstract DiscordClient Client { get; } | |||||
| /// <summary> Gets the type of this channel. </summary> | |||||
| public abstract ChannelType Type { get; } | |||||
| public bool IsText => (Type & ChannelType.Text) != 0; | |||||
| public bool IsVoice => (Type & ChannelType.Voice) != 0; | |||||
| public bool IsPrivate => (Type & ChannelType.Private) != 0; | |||||
| public bool IsPublic => (Type & ChannelType.Public) != 0; | |||||
| if (oldPos < newPos) //Moving Down | |||||
| { | |||||
| minPos = oldPos; | |||||
| for (int i = oldPos; i < newPos; i++) | |||||
| channels[i] = channels[i + 1]; | |||||
| channels[newPos] = this; | |||||
| } | |||||
| else //(oldPos > newPos) Moving Up | |||||
| { | |||||
| minPos = newPos; | |||||
| for (int i = oldPos; i > newPos; i--) | |||||
| channels[i] = channels[i - 1]; | |||||
| channels[newPos] = this; | |||||
| } | |||||
| Channel after = minPos > 0 ? channels.Skip(minPos - 1).FirstOrDefault() : null; | |||||
| await Server.ReorderChannels(channels.Skip(minPos), after).ConfigureAwait(false); | |||||
| } | |||||
| } | |||||
| public async Task Delete() | |||||
| { | |||||
| try { await Client.ClientAPI.Send(new DeleteChannelRequest(Id)).ConfigureAwait(false); } | |||||
| catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||||
| } | |||||
| public abstract User CurrentUser { get; } | |||||
| /// <summary> Gets a collection of all users in this channel. </summary> | |||||
| public abstract IEnumerable<User> Users { get; } | |||||
| #region Invites | |||||
| /// <summary> Gets all active (non-expired) invites to this server. </summary> | |||||
| public async Task<IEnumerable<Invite>> GetInvites() | |||||
| => (await Server.GetInvites().ConfigureAwait(false)).Where(x => x.Channel.Id == Id); | |||||
| internal abstract MessageManager MessageManager { get; } | |||||
| internal abstract PermissionManager PermissionManager { get; } | |||||
| /// <summary> Creates a new invite to this channel. </summary> | |||||
| /// <param name="maxAge"> Time (in seconds) until the invite expires. Set to null to never expire. </param> | |||||
| /// <param name="maxUses"> The max amount of times this invite may be used. Set to null to have unlimited uses. </param> | |||||
| /// <param name="tempMembership"> If true, a user accepting this invite will be kicked from the server after closing their client. </param> | |||||
| /// <param name="withXkcd"> If true, creates a human-readable link. Not supported if maxAge is set to null. </param> | |||||
| public async Task<Invite> CreateInvite(int? maxAge = 1800, int? maxUses = null, bool tempMembership = false, bool withXkcd = false) | |||||
| protected Channel(ulong id) | |||||
| { | { | ||||
| if (maxAge < 0) throw new ArgumentOutOfRangeException(nameof(maxAge)); | |||||
| if (maxUses < 0) throw new ArgumentOutOfRangeException(nameof(maxUses)); | |||||
| var request = new CreateInviteRequest(Id) | |||||
| { | |||||
| MaxAge = maxAge ?? 0, | |||||
| MaxUses = maxUses ?? 0, | |||||
| IsTemporary = tempMembership, | |||||
| WithXkcdPass = withXkcd | |||||
| }; | |||||
| var response = await Client.ClientAPI.Send(request).ConfigureAwait(false); | |||||
| var invite = new Invite(Client, response.Code, response.XkcdPass); | |||||
| return invite; | |||||
| } | |||||
| #endregion | |||||
| #region Messages | |||||
| internal Message AddMessage(ulong id, User user, DateTime timestamp) | |||||
| { | |||||
| Message message = new Message(id, this, user); | |||||
| message.State = MessageState.Normal; | |||||
| var cacheLength = Client.Config.MessageCacheSize; | |||||
| if (cacheLength > 0) | |||||
| { | |||||
| var oldestIds = _messages | |||||
| .Where(x => x.Value.Timestamp < timestamp) | |||||
| .Select(x => x.Key).OrderBy(x => x) | |||||
| .Take(_messages.Count - cacheLength); | |||||
| Message removed; | |||||
| foreach (var removeId in oldestIds) | |||||
| _messages.TryRemove(removeId, out removed); | |||||
| return _messages.GetOrAdd(message.Id, message); | |||||
| } | |||||
| return message; | |||||
| } | |||||
| internal Message RemoveMessage(ulong id) | |||||
| { | |||||
| if (Client.Config.MessageCacheSize > 0) | |||||
| { | |||||
| Message msg; | |||||
| if (_messages.TryRemove(id, out msg)) | |||||
| return msg; | |||||
| } | |||||
| return new Message(id, this, null); | |||||
| } | |||||
| public Message GetMessage(ulong id) | |||||
| => GetMessage(id, null); | |||||
| internal Message GetMessage(ulong id, ulong? userId) | |||||
| { | |||||
| if (Client.Config.MessageCacheSize > 0) | |||||
| { | |||||
| Message result; | |||||
| if (_messages.TryGetValue(id, out result)) | |||||
| return result; | |||||
| } | |||||
| return new Message(id, this, userId != null ? GetUserFast(userId.Value) : null); | |||||
| } | |||||
| public async Task<Message[]> DownloadMessages(int limit = 100, ulong? relativeMessageId = null, | |||||
| Relative relativeDir = Relative.Before, bool useCache = true) | |||||
| { | |||||
| if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit)); | |||||
| if (limit == 0 || Type != ChannelType.Text) return new Message[0]; | |||||
| try | |||||
| { | |||||
| var request = new GetMessagesRequest(Id) | |||||
| { | |||||
| Limit = limit, | |||||
| RelativeDir = relativeMessageId.HasValue ? relativeDir == Relative.Before ? "before" : "after" : null, | |||||
| RelativeId = relativeMessageId ?? 0 | |||||
| }; | |||||
| var msgs = await Client.ClientAPI.Send(request).ConfigureAwait(false); | |||||
| return msgs.Select(x => | |||||
| { | |||||
| Message msg = null; | |||||
| if (useCache) | |||||
| { | |||||
| msg = AddMessage(x.Id, GetUserFast(x.Author.Id), x.Timestamp.Value); | |||||
| var user = msg.User; | |||||
| if (user != null) | |||||
| user.UpdateActivity(msg.EditedTimestamp ?? msg.Timestamp); | |||||
| } | |||||
| else | |||||
| msg = new Message(x.Id, this, GetUserFast(x.Author.Id)); | |||||
| msg.Update(x); | |||||
| return msg; | |||||
| }) | |||||
| .ToArray(); | |||||
| } | |||||
| catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.Forbidden) | |||||
| { | |||||
| return new Message[0]; | |||||
| } | |||||
| } | |||||
| /// <summary> Returns all members of this channel with the specified name. </summary> | |||||
| /// <remarks> Name formats supported: Name, @Name and <@Id>. Search is case-insensitive if exactMatch is false.</remarks> | |||||
| public IEnumerable<User> FindUsers(string name, bool exactMatch = false) | |||||
| { | |||||
| if (name == null) throw new ArgumentNullException(nameof(name)); | |||||
| return Users.Find(name, exactMatch: exactMatch); | |||||
| } | |||||
| public Task<Message> SendMessage(string text) => SendMessageInternal(text, false); | |||||
| public Task<Message> SendTTSMessage(string text) => SendMessageInternal(text, true); | |||||
| private Task<Message> SendMessageInternal(string text, bool isTTS) | |||||
| { | |||||
| if (text == null) throw new ArgumentNullException(nameof(text)); | |||||
| if (text == "") throw new ArgumentException("Value cannot be blank", nameof(text)); | |||||
| if (text.Length > DiscordConfig.MaxMessageSize) | |||||
| throw new ArgumentOutOfRangeException(nameof(text), $"Message must be {DiscordConfig.MaxMessageSize} characters or less."); | |||||
| return Task.FromResult(Client.MessageQueue.QueueSend(this, text, isTTS)); | |||||
| } | |||||
| public async Task<Message> SendFile(string filePath) | |||||
| { | |||||
| using (var stream = File.OpenRead(filePath)) | |||||
| return await SendFile(System.IO.Path.GetFileName(filePath), stream).ConfigureAwait(false); | |||||
| } | |||||
| public async Task<Message> SendFile(string filename, Stream stream) | |||||
| { | |||||
| if (filename == null) throw new ArgumentNullException(nameof(filename)); | |||||
| if (stream == null) throw new ArgumentNullException(nameof(stream)); | |||||
| var request = new SendFileRequest(Id) | |||||
| { | |||||
| Filename = filename, | |||||
| Stream = stream | |||||
| }; | |||||
| var model = await Client.ClientAPI.Send(request).ConfigureAwait(false); | |||||
| var msg = AddMessage(model.Id, IsPrivate ? Client.PrivateUser : Server.CurrentUser, model.Timestamp.Value); | |||||
| msg.Update(model); | |||||
| return msg; | |||||
| } | |||||
| public Task SendIsTyping() => Client.ClientAPI.Send(new SendIsTypingRequest(Id)); | |||||
| #endregion | |||||
| #region Permissions | |||||
| internal void UpdatePermissions() | |||||
| { | |||||
| if (!Client.Config.UsePermissionsCache) return; | |||||
| foreach (var pair in _users) | |||||
| { | |||||
| var member = pair.Value; | |||||
| var perms = member.Permissions; | |||||
| if (UpdatePermissions(member.User, ref perms)) | |||||
| _users[pair.Key] = new Member(member.User, perms); | |||||
| } | |||||
| } | |||||
| internal void UpdatePermissions(User user) | |||||
| { | |||||
| if (!Client.Config.UsePermissionsCache) return; | |||||
| Member member; | |||||
| if (_users.TryGetValue(user.Id, out member)) | |||||
| { | |||||
| var perms = member.Permissions; | |||||
| if (UpdatePermissions(member.User, ref perms)) | |||||
| _users[user.Id] = new Member(member.User, perms); | |||||
| } | |||||
| } | |||||
| internal bool UpdatePermissions(User user, ref ChannelPermissions permissions) | |||||
| { | |||||
| uint newPermissions = 0; | |||||
| var server = Server; | |||||
| //Load the mask of all permissions supported by this channel type | |||||
| var mask = ChannelPermissions.All(this).RawValue; | |||||
| if (server != null) | |||||
| { | |||||
| //Start with this user's server permissions | |||||
| newPermissions = server.GetPermissions(user).RawValue; | |||||
| if (IsPrivate || user == Server.Owner) | |||||
| newPermissions = mask; //Owners always have all permissions | |||||
| else | |||||
| { | |||||
| var channelOverwrites = PermissionOverwrites; | |||||
| var roles = user.Roles; | |||||
| foreach (var denyRole in channelOverwrites.Where(x => x.TargetType == PermissionTarget.Role && x.Permissions.DenyValue != 0 && roles.Any(y => y.Id == x.TargetId))) | |||||
| newPermissions &= ~denyRole.Permissions.DenyValue; | |||||
| foreach (var allowRole in channelOverwrites.Where(x => x.TargetType == PermissionTarget.Role && x.Permissions.AllowValue != 0 && roles.Any(y => y.Id == x.TargetId))) | |||||
| newPermissions |= allowRole.Permissions.AllowValue; | |||||
| foreach (var denyUser in channelOverwrites.Where(x => x.TargetType == PermissionTarget.User && x.TargetId == user.Id && x.Permissions.DenyValue != 0)) | |||||
| newPermissions &= ~denyUser.Permissions.DenyValue; | |||||
| foreach (var allowUser in channelOverwrites.Where(x => x.TargetType == PermissionTarget.User && x.TargetId == user.Id && x.Permissions.AllowValue != 0)) | |||||
| newPermissions |= allowUser.Permissions.AllowValue; | |||||
| if (newPermissions.HasBit((byte)PermissionBits.ManageRolesOrPermissions)) | |||||
| newPermissions = mask; //ManageRolesOrPermissions gives all permisions | |||||
| else if (Type == ChannelType.Text && !newPermissions.HasBit((byte)PermissionBits.ReadMessages)) | |||||
| newPermissions = 0; //No read permission on a text channel removes all other permissions | |||||
| else if (Type == ChannelType.Voice && !newPermissions.HasBit((byte)PermissionBits.Connect)) | |||||
| newPermissions = 0; //No connect permissions on a voice channel removes all other permissions | |||||
| else | |||||
| newPermissions &= mask; //Ensure we didnt get any permissions this channel doesnt support (from serverPerms, for example) | |||||
| } | |||||
| } | |||||
| else | |||||
| newPermissions = mask; //Private messages always have all permissions | |||||
| if (newPermissions != permissions.RawValue) | |||||
| { | |||||
| permissions = new ChannelPermissions(newPermissions); | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| internal ChannelPermissions GetPermissions(User user) | |||||
| { | |||||
| if (Client.Config.UsePermissionsCache) | |||||
| { | |||||
| Member member; | |||||
| if (_users.TryGetValue(user.Id, out member)) | |||||
| return member.Permissions; | |||||
| else | |||||
| return ChannelPermissions.None; | |||||
| } | |||||
| else | |||||
| { | |||||
| ChannelPermissions perms = new ChannelPermissions(); | |||||
| UpdatePermissions(user, ref perms); | |||||
| return perms; | |||||
| } | |||||
| } | |||||
| public ChannelPermissionOverrides GetPermissionsRule(User user) | |||||
| { | |||||
| if (user == null) throw new ArgumentNullException(nameof(user)); | |||||
| return PermissionOverwrites | |||||
| .Where(x => x.TargetType == PermissionTarget.User && x.TargetId == user.Id) | |||||
| .Select(x => x.Permissions) | |||||
| .FirstOrDefault(); | |||||
| } | |||||
| public ChannelPermissionOverrides GetPermissionsRule(Role role) | |||||
| { | |||||
| if (role == null) throw new ArgumentNullException(nameof(role)); | |||||
| return PermissionOverwrites | |||||
| .Where(x => x.TargetType == PermissionTarget.Role && x.TargetId == role.Id) | |||||
| .Select(x => x.Permissions) | |||||
| .FirstOrDefault(); | |||||
| } | |||||
| public Task AddPermissionsRule(User user, ChannelPermissions allow, ChannelPermissions deny) | |||||
| { | |||||
| if (user == null) throw new ArgumentNullException(nameof(user)); | |||||
| return AddPermissionsRule(user.Id, PermissionTarget.User, allow.RawValue, deny.RawValue); | |||||
| } | |||||
| public Task AddPermissionsRule(User user, ChannelPermissionOverrides permissions) | |||||
| { | |||||
| if (user == null) throw new ArgumentNullException(nameof(user)); | |||||
| return AddPermissionsRule(user.Id, PermissionTarget.User, permissions.AllowValue, permissions.DenyValue); | |||||
| } | |||||
| public Task AddPermissionsRule(Role role, ChannelPermissions allow, ChannelPermissions deny) | |||||
| { | |||||
| if (role == null) throw new ArgumentNullException(nameof(role)); | |||||
| return AddPermissionsRule(role.Id, PermissionTarget.Role, allow.RawValue, deny.RawValue); | |||||
| } | |||||
| public Task AddPermissionsRule(Role role, ChannelPermissionOverrides permissions) | |||||
| { | |||||
| if (role == null) throw new ArgumentNullException(nameof(role)); | |||||
| return AddPermissionsRule(role.Id, PermissionTarget.Role, permissions.AllowValue, permissions.DenyValue); | |||||
| } | |||||
| private Task AddPermissionsRule(ulong targetId, PermissionTarget targetType, uint allow, uint deny) | |||||
| { | |||||
| var request = new AddChannelPermissionsRequest(Id) | |||||
| { | |||||
| TargetId = targetId, | |||||
| TargetType = targetType.Value, | |||||
| Allow = allow, | |||||
| Deny = deny | |||||
| }; | |||||
| return Client.ClientAPI.Send(request); | |||||
| } | |||||
| public Task RemovePermissionsRule(User user) | |||||
| { | |||||
| if (user == null) throw new ArgumentNullException(nameof(user)); | |||||
| return RemovePermissionsRule(user.Id, PermissionTarget.User); | |||||
| } | |||||
| public Task RemovePermissionsRule(Role role) | |||||
| { | |||||
| if (role == null) throw new ArgumentNullException(nameof(role)); | |||||
| return RemovePermissionsRule(role.Id, PermissionTarget.Role); | |||||
| } | |||||
| private async Task RemovePermissionsRule(ulong userOrRoleId, PermissionTarget targetType) | |||||
| { | |||||
| try | |||||
| { | |||||
| var perms = PermissionOverwrites.Where(x => x.TargetType != targetType || x.TargetId != userOrRoleId).FirstOrDefault(); | |||||
| await Client.ClientAPI.Send(new RemoveChannelPermissionsRequest(Id, userOrRoleId)).ConfigureAwait(false); | |||||
| } | |||||
| catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||||
| } | |||||
| #endregion | |||||
| #region Users | |||||
| internal void AddUser(User user) | |||||
| { | |||||
| if (!Client.Config.UsePermissionsCache) return; | |||||
| var perms = new ChannelPermissions(); | |||||
| UpdatePermissions(user, ref perms); | |||||
| var member = new Member(user, ChannelPermissions.None); | |||||
| _users[user.Id] = new Member(user, ChannelPermissions.None); | |||||
| Id = id; | |||||
| } | } | ||||
| internal void RemoveUser(ulong id) | |||||
| { | |||||
| if (!Client.Config.UsePermissionsCache) return; | |||||
| Member ignored; | |||||
| _users.TryRemove(id, out ignored); | |||||
| } | |||||
| public User GetUser(ulong id) | |||||
| { | |||||
| if (IsPrivate) | |||||
| { | |||||
| if (id == Recipient.Id) | |||||
| return Recipient; | |||||
| else if (id == Client.PrivateUser.Id) | |||||
| return Client.PrivateUser; | |||||
| } | |||||
| else if (!Client.Config.UsePermissionsCache) | |||||
| { | |||||
| var user = Server.GetUser(id); | |||||
| if (user != null) | |||||
| { | |||||
| ChannelPermissions perms = new ChannelPermissions(); | |||||
| UpdatePermissions(user, ref perms); | |||||
| if (perms.ReadMessages) | |||||
| return user; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| Member result; | |||||
| if (_users.TryGetValue(id, out result)) | |||||
| return result.User; | |||||
| } | |||||
| return null; | |||||
| } | |||||
| internal User GetUserFast(ulong id) | |||||
| { | |||||
| //Can return users not in this channel, but that's usually okay | |||||
| if (IsPrivate) | |||||
| { | |||||
| if (id == Recipient.Id) | |||||
| return Recipient; | |||||
| else if (id == Client.PrivateUser.Id) | |||||
| return Client.PrivateUser; | |||||
| } | |||||
| else | |||||
| return Server.GetUser(id); | |||||
| return null; | |||||
| } | |||||
| #endregion | |||||
| internal abstract void Update(APIChannel model); | |||||
| internal Channel Clone() | |||||
| { | |||||
| var result = new Channel(); | |||||
| _cloner(this, result); | |||||
| return result; | |||||
| } | |||||
| private Channel() { } | |||||
| internal abstract User GetUser(ulong id); | |||||
| public override string ToString() => Name ?? Id.ToIdString(); | |||||
| internal abstract Channel Clone(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -3,28 +3,6 @@ | |||||
| public class Color | public class Color | ||||
| { | { | ||||
| public static readonly Color Default = new Color(0); | public static readonly Color Default = new Color(0); | ||||
| public static readonly Color Teal = new Color(0x1ABC9C); | |||||
| public static readonly Color DarkTeal = new Color(0x11806A); | |||||
| public static readonly Color Green = new Color(0x2ECC71); | |||||
| public static readonly Color DarkGreen = new Color(0x1F8B4C); | |||||
| public static readonly Color Blue = new Color(0x3498DB); | |||||
| public static readonly Color DarkBlue = new Color(0x206694); | |||||
| public static readonly Color Purple = new Color(0x9B59B6); | |||||
| public static readonly Color DarkPurple = new Color(0x71368A); | |||||
| public static readonly Color Magenta = new Color(0xE91E63); | |||||
| public static readonly Color DarkMagenta = new Color(0xAD1457); | |||||
| public static readonly Color Gold = new Color(0xF1C40F); | |||||
| public static readonly Color DarkGold = new Color(0xC27C0E); | |||||
| public static readonly Color Orange = new Color(0xE67E22); | |||||
| public static readonly Color DarkOrange = new Color(0xA84300); | |||||
| public static readonly Color Red = new Color(0xE74C3C); | |||||
| public static readonly Color DarkRed = new Color(0x992D22); | |||||
| public static readonly Color LighterGrey = new Color(0x95A5A6); | |||||
| public static readonly Color DarkGrey = new Color(0x607D8B); | |||||
| public static readonly Color LightGrey = new Color(0x979C9F); | |||||
| public static readonly Color DarkerGrey = new Color(0x546E7A); | |||||
| public uint RawValue { get; } | public uint RawValue { get; } | ||||
| @@ -0,0 +1,21 @@ | |||||
| using System.Collections.Generic; | |||||
| namespace Discord | |||||
| { | |||||
| public interface IChannel | |||||
| { | |||||
| /// <summary> Gets the unique identifier for this channel. </summary> | |||||
| ulong Id { get; } | |||||
| DiscordClient Client { get; } | |||||
| /// <summary> Gets the type of this channel. </summary> | |||||
| ChannelType Type { get; } | |||||
| bool IsText { get; } | |||||
| bool IsVoice { get; } | |||||
| bool IsPrivate { get; } | |||||
| bool IsPublic { get; } | |||||
| /// <summary> Gets a collection of all users in this channel. </summary> | |||||
| IEnumerable<User> Users { get; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,6 @@ | |||||
| namespace Discord | |||||
| { | |||||
| public interface IPrivateChannel : IChannel | |||||
| { | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,6 @@ | |||||
| namespace Discord | |||||
| { | |||||
| public interface IPublicChannel : IChannel | |||||
| { | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,17 @@ | |||||
| using System.IO; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord | |||||
| { | |||||
| public interface ITextChannel : IChannel | |||||
| { | |||||
| Message GetMessage(ulong id); | |||||
| Task<Message[]> DownloadMessages(int limit = 100, ulong? relativeMessageId = null, Relative relativeDir = Relative.Before); | |||||
| Task<Message> SendMessage(string text, bool isTTS = false); | |||||
| Task<Message> SendFile(string filePath); | |||||
| Task<Message> SendFile(string filename, Stream stream); | |||||
| Task SendIsTyping(); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,6 @@ | |||||
| namespace Discord | |||||
| { | |||||
| public interface IVoiceChannel : IChannel | |||||
| { | |||||
| } | |||||
| } | |||||
| @@ -1,10 +1,10 @@ | |||||
| using Discord.API.Client; | |||||
| using APIInvite = Discord.API.Client.Invite; | |||||
| using Discord.API.Client; | |||||
| using Discord.API.Client.Rest; | using Discord.API.Client.Rest; | ||||
| using Discord.Net; | using Discord.Net; | ||||
| using System; | using System; | ||||
| using System.Net; | using System.Net; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using APIInvite = Discord.API.Client.Invite; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| @@ -84,9 +84,7 @@ namespace Discord | |||||
| public bool IsTemporary { get; private set; } | public bool IsTemporary { get; private set; } | ||||
| /// <summary> Gets when this invite was created. </summary> | /// <summary> Gets when this invite was created. </summary> | ||||
| public DateTime CreatedAt { get; private set; } | public DateTime CreatedAt { get; private set; } | ||||
| /// <summary> Gets the path to this object. </summary> | |||||
| internal string Path => $"{Server?.Name ?? "[Private]"}/{Code}"; | |||||
| /// <summary> Returns a URL for this invite using XkcdCode if available or Id if not. </summary> | /// <summary> Returns a URL for this invite using XkcdCode if available or Id if not. </summary> | ||||
| public string Url => $"{DiscordConfig.InviteUrl}/{Code}"; | public string Url => $"{DiscordConfig.InviteUrl}/{Code}"; | ||||
| @@ -138,6 +136,6 @@ namespace Discord | |||||
| } | } | ||||
| private Invite() { } //Used for cloning | private Invite() { } //Used for cloning | ||||
| public override string ToString() => XkcdCode ?? Code; | |||||
| public override string ToString() => $"{Server}/{XkcdCode ?? Code}"; | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,154 @@ | |||||
| using Discord.API.Client.Rest; | |||||
| using Discord.Net; | |||||
| using System; | |||||
| using System.Collections; | |||||
| using System.Collections.Concurrent; | |||||
| using System.Collections.Generic; | |||||
| using System.IO; | |||||
| using System.Linq; | |||||
| using System.Net; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord | |||||
| { | |||||
| internal class MessageManager : IEnumerable<Message> | |||||
| { | |||||
| private readonly ITextChannel _channel; | |||||
| private readonly int _size; | |||||
| private readonly ConcurrentDictionary<ulong, Message> _messages; | |||||
| private readonly ConcurrentQueue<ulong> _orderedMessages; | |||||
| public MessageManager(ITextChannel channel, int size = 0) | |||||
| { | |||||
| _channel = channel; | |||||
| _size = size; | |||||
| if (size > 0) | |||||
| { | |||||
| _messages = new ConcurrentDictionary<ulong, Message>(2, size); | |||||
| _orderedMessages = new ConcurrentQueue<ulong>(); | |||||
| } | |||||
| } | |||||
| internal Message Add(ulong id, User user, DateTime timestamp) | |||||
| { | |||||
| Message message = new Message(id, _channel, user); | |||||
| message.State = MessageState.Normal; | |||||
| if (_size > 0) | |||||
| { | |||||
| if (_messages.TryAdd(id, message)) | |||||
| { | |||||
| _orderedMessages.Enqueue(id); | |||||
| ulong msgId; | |||||
| while (_orderedMessages.Count > _size && _orderedMessages.TryDequeue(out msgId)) | |||||
| { | |||||
| Message msg; | |||||
| if (_messages.TryRemove(msgId, out msg)) | |||||
| msg.State = MessageState.Detached; | |||||
| } | |||||
| } | |||||
| } | |||||
| return message; | |||||
| } | |||||
| internal Message Remove(ulong id) | |||||
| { | |||||
| if (_size > 0) | |||||
| { | |||||
| Message msg; | |||||
| if (_messages.TryRemove(id, out msg)) | |||||
| return msg; | |||||
| } | |||||
| return new Message(id, _channel, null) { State = MessageState.Deleted }; | |||||
| } | |||||
| public Message Get(ulong id, ulong? userId = null) | |||||
| { | |||||
| if (_messages != null) | |||||
| { | |||||
| Message result; | |||||
| if (_messages.TryGetValue(id, out result)) | |||||
| return result; | |||||
| } | |||||
| return new Message(id, _channel, userId != null ? (_channel as Channel).GetUser(userId.Value) : null) { State = MessageState.Detached }; | |||||
| } | |||||
| public async Task<Message[]> Download(int limit = 100, ulong? relativeMessageId = null, Relative relativeDir = Relative.Before) | |||||
| { | |||||
| if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit)); | |||||
| if (limit == 0) return new Message[0]; | |||||
| try | |||||
| { | |||||
| var request = new GetMessagesRequest(_channel.Id) | |||||
| { | |||||
| Limit = limit, | |||||
| RelativeDir = relativeMessageId.HasValue ? relativeDir == Relative.Before ? "before" : "after" : null, | |||||
| RelativeId = relativeMessageId ?? 0 | |||||
| }; | |||||
| var msgs = await _channel.Client.ClientAPI.Send(request).ConfigureAwait(false); | |||||
| var server = (_channel as PublicChannel)?.Server; | |||||
| return msgs.Select(x => | |||||
| { | |||||
| Message msg = null; | |||||
| ulong id = x.Author.Id; | |||||
| var user = server?.GetUser(id) ?? (_channel as Channel).GetUser(id); | |||||
| /*if (useCache) | |||||
| { | |||||
| msg = Add(x.Id, user, x.Timestamp.Value); | |||||
| if (user != null) | |||||
| user.UpdateActivity(msg.EditedTimestamp ?? msg.Timestamp); | |||||
| } | |||||
| else*/ | |||||
| msg = new Message(x.Id, _channel, user); | |||||
| msg.Update(x); | |||||
| return msg; | |||||
| }).ToArray(); | |||||
| } | |||||
| catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.Forbidden) | |||||
| { | |||||
| return new Message[0]; | |||||
| } | |||||
| } | |||||
| public Task<Message> Send(string text, bool isTTS) | |||||
| { | |||||
| if (text == "") throw new ArgumentException("Value cannot be blank", nameof(text)); | |||||
| if (text.Length > DiscordConfig.MaxMessageSize) | |||||
| throw new ArgumentOutOfRangeException(nameof(text), $"Message must be {DiscordConfig.MaxMessageSize} characters or less."); | |||||
| return Task.FromResult(_channel.Client.MessageQueue.QueueSend(_channel, text, isTTS)); | |||||
| } | |||||
| public async Task<Message> SendFile(string filePath) | |||||
| { | |||||
| using (var stream = File.OpenRead(filePath)) | |||||
| return await SendFile(Path.GetFileName(filePath), stream).ConfigureAwait(false); | |||||
| } | |||||
| public async Task<Message> SendFile(string filename, Stream stream) | |||||
| { | |||||
| var request = new SendFileRequest(_channel.Id) | |||||
| { | |||||
| Filename = filename, | |||||
| Stream = stream | |||||
| }; | |||||
| var model = await _channel.Client.ClientAPI.Send(request).ConfigureAwait(false); | |||||
| var msg = Add(model.Id, (_channel as Channel).CurrentUser, model.Timestamp.Value); | |||||
| msg.Update(model); | |||||
| return msg; | |||||
| } | |||||
| public IEnumerator<Message> GetEnumerator() | |||||
| { | |||||
| return _orderedMessages | |||||
| .Select(x => | |||||
| { | |||||
| Message msg; | |||||
| if (_messages.TryGetValue(x, out msg)) | |||||
| return msg; | |||||
| return null; | |||||
| }) | |||||
| .Where(x => x != null).GetEnumerator(); | |||||
| } | |||||
| IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,223 @@ | |||||
| using Discord.API.Client.Rest; | |||||
| using Discord.Net; | |||||
| using System; | |||||
| using System.Collections.Concurrent; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Net; | |||||
| using System.Threading.Tasks; | |||||
| using APIChannel = Discord.API.Client.Channel; | |||||
| namespace Discord | |||||
| { | |||||
| internal class PermissionManager | |||||
| { | |||||
| public struct Member | |||||
| { | |||||
| public User User { get; } | |||||
| public ChannelPermissions Permissions { get; } | |||||
| public Member(User user, ChannelPermissions permissions) | |||||
| { | |||||
| User = user; | |||||
| Permissions = permissions; | |||||
| } | |||||
| } | |||||
| private readonly PublicChannel _channel; | |||||
| private readonly ConcurrentDictionary<ulong, Member> _users; | |||||
| private Dictionary<ulong, Channel.PermissionRule> _rules; | |||||
| public IEnumerable<Member> Users => _users.Select(x => x.Value); | |||||
| public IEnumerable<Channel.PermissionRule> Rules => _rules.Values; | |||||
| public PermissionManager(PublicChannel channel, APIChannel model, int initialSize = -1) | |||||
| { | |||||
| _channel = channel; | |||||
| if (initialSize >= 0) | |||||
| _users = new ConcurrentDictionary<ulong, Member>(2, initialSize); | |||||
| Update(model); | |||||
| } | |||||
| public void Update(APIChannel model) | |||||
| { | |||||
| _rules = model.PermissionOverwrites | |||||
| .Select(x => new Channel.PermissionRule(EnumConverters.ToPermissionTarget(x.Type), x.Id, x.Allow, x.Deny)) | |||||
| .ToDictionary(x => x.TargetId); | |||||
| UpdatePermissions(); | |||||
| } | |||||
| public ChannelTriStatePermissions? GetOverwrite(User user) | |||||
| { | |||||
| if (user == null) throw new ArgumentNullException(nameof(user)); | |||||
| Channel.PermissionRule rule; | |||||
| if (_rules.TryGetValue(user.Id, out rule)) | |||||
| return rule.Permissions; | |||||
| return null; | |||||
| } | |||||
| public ChannelTriStatePermissions? GetOverwrite(Role role) | |||||
| { | |||||
| if (role == null) throw new ArgumentNullException(nameof(role)); | |||||
| Channel.PermissionRule rule; | |||||
| if (_rules.TryGetValue(role.Id, out rule)) | |||||
| return rule.Permissions; | |||||
| return null; | |||||
| } | |||||
| public Task AddOrUpdateOverwrite(User user, ChannelTriStatePermissions permissions) | |||||
| { | |||||
| if (user == null) throw new ArgumentNullException(nameof(user)); | |||||
| return AddOrUpdateOverwrite(user.Id, PermissionTarget.User, permissions); | |||||
| } | |||||
| public Task AddOrUpdateOverwrite(Role role, ChannelTriStatePermissions permissions) | |||||
| { | |||||
| if (role == null) throw new ArgumentNullException(nameof(role)); | |||||
| return AddOrUpdateOverwrite(role.Id, PermissionTarget.Role, permissions); | |||||
| } | |||||
| private Task AddOrUpdateOverwrite(ulong id, PermissionTarget type, ChannelTriStatePermissions permissions) | |||||
| { | |||||
| var request = new AddOrUpdateChannelPermissionsRequest(id) | |||||
| { | |||||
| TargetId = id, | |||||
| TargetType = EnumConverters.ToString(type), | |||||
| Allow = permissions.AllowValue, | |||||
| Deny = permissions.DenyValue | |||||
| }; | |||||
| return _channel.Client.ClientAPI.Send(request); | |||||
| } | |||||
| public Task RemoveOverwrite(User user) | |||||
| { | |||||
| if (user == null) throw new ArgumentNullException(nameof(user)); | |||||
| return RemoveOverwrite(user.Id); | |||||
| } | |||||
| public Task RemoveOverwrite(Role role) | |||||
| { | |||||
| if (role == null) throw new ArgumentNullException(nameof(role)); | |||||
| return RemoveOverwrite(role.Id); | |||||
| } | |||||
| private async Task RemoveOverwrite(ulong id) | |||||
| { | |||||
| try { await _channel.Client.ClientAPI.Send(new RemoveChannelPermissionsRequest(_channel.Id, id)).ConfigureAwait(false); } | |||||
| catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||||
| } | |||||
| public ChannelPermissions GetPermissions(User user) | |||||
| { | |||||
| if (user == null) throw new ArgumentNullException(nameof(user)); | |||||
| if (_users != null) | |||||
| { | |||||
| Member member; | |||||
| if (_users.TryGetValue(user.Id, out member)) | |||||
| return member.Permissions; | |||||
| else | |||||
| return ChannelPermissions.None; | |||||
| } | |||||
| else | |||||
| { | |||||
| ChannelPermissions perms = new ChannelPermissions(); | |||||
| ResolvePermissions(user, ref perms); | |||||
| return perms; | |||||
| } | |||||
| } | |||||
| public void UpdatePermissions() | |||||
| { | |||||
| if (_users != null) | |||||
| { | |||||
| foreach (var pair in _users) | |||||
| { | |||||
| var member = pair.Value; | |||||
| var perms = member.Permissions; | |||||
| if (ResolvePermissions(member.User, ref perms)) | |||||
| _users[pair.Key] = new Member(member.User, perms); | |||||
| } | |||||
| } | |||||
| } | |||||
| public void UpdatePermissions(User user) | |||||
| { | |||||
| if (user == null) throw new ArgumentNullException(nameof(user)); | |||||
| if (_users != null) | |||||
| { | |||||
| Member member; | |||||
| if (_users.TryGetValue(user.Id, out member)) | |||||
| { | |||||
| var perms = member.Permissions; | |||||
| if (ResolvePermissions(member.User, ref perms)) | |||||
| _users[user.Id] = new Member(member.User, perms); | |||||
| } | |||||
| } | |||||
| } | |||||
| public bool ResolvePermissions(User user, ref ChannelPermissions permissions) | |||||
| { | |||||
| if (user == null) throw new ArgumentNullException(nameof(user)); | |||||
| uint newPermissions = 0; | |||||
| var server = user.Server; | |||||
| var mask = ChannelPermissions.All(_channel.Type).RawValue; | |||||
| if (_channel.IsPrivate || user.IsOwner) | |||||
| newPermissions = mask; //Private messages and owners always have all permissions | |||||
| else | |||||
| { | |||||
| //Start with this user's server permissions | |||||
| newPermissions = server.GetPermissions(user).RawValue; | |||||
| var rules = _rules; | |||||
| Channel.PermissionRule rule; | |||||
| var roles = user.Roles.ToArray(); | |||||
| if (roles.Length > 0) | |||||
| { | |||||
| for (int i = 0; i < roles.Length; i++) | |||||
| { | |||||
| if (rules.TryGetValue(roles[i].Id, out rule)) | |||||
| newPermissions &= ~rule.Permissions.DenyValue; | |||||
| } | |||||
| for (int i = 0; i < roles.Length; i++) | |||||
| { | |||||
| if (rules.TryGetValue(roles[i].Id, out rule)) | |||||
| newPermissions |= rule.Permissions.AllowValue; | |||||
| } | |||||
| } | |||||
| if (rules.TryGetValue(user.Id, out rule)) | |||||
| newPermissions = (newPermissions & ~rule.Permissions.DenyValue) | rule.Permissions.AllowValue; | |||||
| if (newPermissions.HasBit((byte)PermissionBits.ManageRolesOrPermissions)) | |||||
| newPermissions = mask; //ManageRolesOrPermissions gives all permisions | |||||
| else if (_channel.IsText && !newPermissions.HasBit((byte)PermissionBits.ReadMessages)) | |||||
| newPermissions = 0; //No read permission on a text channel removes all other permissions | |||||
| else if (_channel.IsVoice && !newPermissions.HasBit((byte)PermissionBits.Connect)) | |||||
| newPermissions = 0; //No connect permissions on a voice channel removes all other permissions | |||||
| else | |||||
| newPermissions &= mask; //Ensure we didnt get any permissions this channel doesnt support (from serverPerms, for example) | |||||
| } | |||||
| if (newPermissions != permissions.RawValue) | |||||
| { | |||||
| permissions = new ChannelPermissions(newPermissions); | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| public void AddUser(User user) | |||||
| { | |||||
| if (user == null) throw new ArgumentNullException(nameof(user)); | |||||
| if (_users != null) | |||||
| { | |||||
| var perms = new ChannelPermissions(); | |||||
| ResolvePermissions(user, ref perms); | |||||
| var member = new Member(user, ChannelPermissions.None); | |||||
| _users[user.Id] = new Member(user, ChannelPermissions.None); | |||||
| } | |||||
| } | |||||
| public void RemoveUser(ulong id) | |||||
| { | |||||
| Member ignored; | |||||
| if (_users != null) | |||||
| _users.TryRemove(id, out ignored); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -13,10 +13,14 @@ namespace Discord | |||||
| Normal = 0, | Normal = 0, | ||||
| /// <summary> Message is current queued. </summary> | /// <summary> Message is current queued. </summary> | ||||
| Queued, | Queued, | ||||
| /// <summary> Message was deleted. </summary> | |||||
| Deleted, | |||||
| /// <summary> Message was deleted before it was sent. </summary> | /// <summary> Message was deleted before it was sent. </summary> | ||||
| Aborted, | Aborted, | ||||
| /// <summary> Message failed to be sent. </summary> | /// <summary> Message failed to be sent. </summary> | ||||
| Failed | |||||
| Failed, | |||||
| /// <summary> Message has been removed from cache and will no longer receive updates. </summary> | |||||
| Detached | |||||
| } | } | ||||
| public class Message | public class Message | ||||
| @@ -29,14 +33,14 @@ namespace Discord | |||||
| private static readonly Attachment[] _initialAttachments = new Attachment[0]; | private static readonly Attachment[] _initialAttachments = new Attachment[0]; | ||||
| private static readonly Embed[] _initialEmbeds = new Embed[0]; | private static readonly Embed[] _initialEmbeds = new Embed[0]; | ||||
| internal static string CleanUserMentions(Channel channel, string text, List<User> users = null) | |||||
| internal static string CleanUserMentions(PublicChannel channel, string text, List<User> users = null) | |||||
| { | { | ||||
| return _userRegex.Replace(text, new MatchEvaluator(e => | return _userRegex.Replace(text, new MatchEvaluator(e => | ||||
| { | { | ||||
| ulong id; | ulong id; | ||||
| if (e.Value.Substring(2, e.Value.Length - 3).TryToId(out id)) | if (e.Value.Substring(2, e.Value.Length - 3).TryToId(out id)) | ||||
| { | { | ||||
| var user = channel.GetUserFast(id); | |||||
| var user = channel.GetUser(id); | |||||
| if (user != null) | if (user != null) | ||||
| { | { | ||||
| if (users != null) | if (users != null) | ||||
| @@ -47,7 +51,7 @@ namespace Discord | |||||
| return e.Value; //User not found or parse failed | return e.Value; //User not found or parse failed | ||||
| })); | })); | ||||
| } | } | ||||
| internal static string CleanChannelMentions(Channel channel, string text, List<Channel> channels = null) | |||||
| internal static string CleanChannelMentions(PublicChannel channel, string text, List<PublicChannel> channels = null) | |||||
| { | { | ||||
| var server = channel.Server; | var server = channel.Server; | ||||
| if (server == null) return text; | if (server == null) return text; | ||||
| @@ -81,34 +85,21 @@ namespace Discord | |||||
| })); | })); | ||||
| }*/ | }*/ | ||||
| //TODO: Move this somewhere | |||||
| private static string Resolve(Channel channel, string text) | |||||
| internal static string ResolveMentions(IChannel channel, string text) | |||||
| { | { | ||||
| if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||||
| if (text == null) throw new ArgumentNullException(nameof(text)); | if (text == null) throw new ArgumentNullException(nameof(text)); | ||||
| var client = channel.Client; | |||||
| text = CleanUserMentions(channel, text); | |||||
| text = CleanChannelMentions(channel, text); | |||||
| //text = CleanRoleMentions(Channel, text); | |||||
| var publicChannel = channel as PublicChannel; | |||||
| if (publicChannel != null) | |||||
| { | |||||
| text = CleanUserMentions(publicChannel, text); | |||||
| text = CleanChannelMentions(publicChannel, text); | |||||
| //text = CleanRoleMentions(publicChannel, text); | |||||
| } | |||||
| return text; | return text; | ||||
| } | } | ||||
| /*internal class ImportResolver : DefaultContractResolver | |||||
| { | |||||
| protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) | |||||
| { | |||||
| var property = base.CreateProperty(member, memberSerialization); | |||||
| if (member is PropertyInfo) | |||||
| { | |||||
| if (member.Name == nameof(ChannelId) || !(member as PropertyInfo).CanWrite) | |||||
| return null; | |||||
| property.Writable = true; //Handles private setters | |||||
| } | |||||
| return property; | |||||
| } | |||||
| }*/ | |||||
| public class Attachment : File | public class Attachment : File | ||||
| { | { | ||||
| /// <summary> Unique identifier for this file. </summary> | /// <summary> Unique identifier for this file. </summary> | ||||
| @@ -172,7 +163,7 @@ namespace Discord | |||||
| /// <summary> Returns the unique identifier for this message. </summary> | /// <summary> Returns the unique identifier for this message. </summary> | ||||
| public ulong Id { get; internal set; } | public ulong Id { get; internal set; } | ||||
| /// <summary> Returns the channel this message was sent to. </summary> | /// <summary> Returns the channel this message was sent to. </summary> | ||||
| public Channel Channel { get; } | |||||
| public ITextChannel Channel { get; } | |||||
| /// <summary> Returns the author of this message. </summary> | /// <summary> Returns the author of this message. </summary> | ||||
| public User User { get; } | public User User { get; } | ||||
| @@ -196,20 +187,18 @@ namespace Discord | |||||
| /// <summary> Returns a collection of all users mentioned in this message. </summary> | /// <summary> Returns a collection of all users mentioned in this message. </summary> | ||||
| public IEnumerable<User> MentionedUsers { get; internal set; } | public IEnumerable<User> MentionedUsers { get; internal set; } | ||||
| /// <summary> Returns a collection of all channels mentioned in this message. </summary> | /// <summary> Returns a collection of all channels mentioned in this message. </summary> | ||||
| public IEnumerable<Channel> MentionedChannels { get; internal set; } | |||||
| public IEnumerable<PublicChannel> MentionedChannels { get; internal set; } | |||||
| /// <summary> Returns a collection of all roles mentioned in this message. </summary> | /// <summary> Returns a collection of all roles mentioned in this message. </summary> | ||||
| public IEnumerable<Role> MentionedRoles { get; internal set; } | public IEnumerable<Role> MentionedRoles { get; internal set; } | ||||
| internal int Nonce { get; set; } | internal int Nonce { get; set; } | ||||
| /// <summary> Gets the path to this object. </summary> | |||||
| internal string Path => $"{Server?.Name ?? "[Private]"}/{Id}"; | |||||
| /// <summary> Returns the server containing the channel this message was sent to. </summary> | /// <summary> Returns the server containing the channel this message was sent to. </summary> | ||||
| public Server Server => Channel.Server; | |||||
| public Server Server => (Channel as PublicChannel)?.Server; | |||||
| /// <summary> Returns if this message was sent from the logged-in accounts. </summary> | /// <summary> Returns if this message was sent from the logged-in accounts. </summary> | ||||
| public bool IsAuthor => User != null && User.Id == Client.CurrentUser?.Id; | public bool IsAuthor => User != null && User.Id == Client.CurrentUser?.Id; | ||||
| internal Message(ulong id, Channel channel, User user) | |||||
| internal Message(ulong id, ITextChannel channel, User user) | |||||
| { | { | ||||
| Id = id; | Id = id; | ||||
| Channel = channel; | Channel = channel; | ||||
| @@ -222,7 +211,6 @@ namespace Discord | |||||
| internal void Update(APIMessage model) | internal void Update(APIMessage model) | ||||
| { | { | ||||
| var channel = Channel; | var channel = Channel; | ||||
| var server = channel.Server; | |||||
| if (model.Attachments != null) | if (model.Attachments != null) | ||||
| { | { | ||||
| Attachments = model.Attachments | Attachments = model.Attachments | ||||
| @@ -277,36 +265,34 @@ namespace Discord | |||||
| if (model.Mentions != null) | if (model.Mentions != null) | ||||
| { | { | ||||
| MentionedUsers = model.Mentions | MentionedUsers = model.Mentions | ||||
| .Select(x => Channel.GetUserFast(x.Id)) | |||||
| .Select(x => (Channel as Channel).GetUser(x.Id)) | |||||
| .Where(x => x != null) | .Where(x => x != null) | ||||
| .ToArray(); | .ToArray(); | ||||
| } | } | ||||
| if (model.IsMentioningEveryone != null) | if (model.IsMentioningEveryone != null) | ||||
| { | |||||
| if (model.IsMentioningEveryone.Value && User != null && User.GetPermissions(channel).MentionEveryone) | |||||
| MentionedRoles = new Role[] { Server.EveryoneRole }; | |||||
| else | |||||
| MentionedRoles = new Role[0]; | |||||
| { | |||||
| var server = (channel as PublicChannel).Server; | |||||
| if (model.IsMentioningEveryone.Value && server != null) | |||||
| MentionedRoles = new Role[] { server.EveryoneRole }; | |||||
| else | |||||
| MentionedRoles = Enumerable.Empty<Role>(); | |||||
| } | } | ||||
| if (model.Content != null) | if (model.Content != null) | ||||
| { | { | ||||
| string text = model.Content; | string text = model.Content; | ||||
| RawText = text; | RawText = text; | ||||
| //var mentionedUsers = new List<User>(); | |||||
| var mentionedChannels = new List<Channel>(); | |||||
| //var mentionedRoles = new List<Role>(); | |||||
| text = CleanUserMentions(Channel, text/*, mentionedUsers*/); | |||||
| if (server != null) | |||||
| { | |||||
| text = CleanChannelMentions(Channel, text, mentionedChannels); | |||||
| //text = CleanRoleMentions(_client, User, channel, text, mentionedRoles); | |||||
| } | |||||
| Text = text; | |||||
| //MentionedUsers = mentionedUsers; | |||||
| MentionedChannels = mentionedChannels; | |||||
| //MentionedRoles = mentionedRoles; | |||||
| List<PublicChannel> mentionedChannels = null; | |||||
| if (Channel.IsPublic) | |||||
| mentionedChannels = new List<PublicChannel>(); | |||||
| text = CleanUserMentions(Channel as PublicChannel, text); | |||||
| text = CleanChannelMentions(Channel as PublicChannel, text, mentionedChannels); | |||||
| if (Channel.IsPublic) | |||||
| MentionedChannels = mentionedChannels; | |||||
| Text = text; | |||||
| } | } | ||||
| } | } | ||||
| @@ -341,13 +327,6 @@ namespace Discord | |||||
| return MentionedUsers?.Contains(me) ?? false; | return MentionedUsers?.Contains(me) ?? false; | ||||
| } | } | ||||
| /// <summary>Resolves all mentions in a provided string to those users, channels or roles' names.</summary> | |||||
| public string Resolve(string text) | |||||
| { | |||||
| if (text == null) throw new ArgumentNullException(nameof(text)); | |||||
| return Resolve(Channel, text); | |||||
| } | |||||
| internal Message Clone() | internal Message Clone() | ||||
| { | { | ||||
| var result = new Message(); | var result = new Message(); | ||||
| @@ -356,6 +335,6 @@ namespace Discord | |||||
| } | } | ||||
| private Message() { } //Used for cloning | private Message() { } //Used for cloning | ||||
| public override string ToString() => $"{User?.Name ?? "Unknown User"}: {RawText}"; | |||||
| public override string ToString() => $"{User}: {RawText}"; | |||||
| } | } | ||||
| } | } | ||||
| @@ -97,6 +97,8 @@ namespace Discord | |||||
| RawValue = value; | RawValue = value; | ||||
| } | } | ||||
| public ServerPermissions(uint rawValue) { RawValue = rawValue; } | public ServerPermissions(uint rawValue) { RawValue = rawValue; } | ||||
| public override string ToString() => Convert.ToString(RawValue, 2); | |||||
| } | } | ||||
| public struct ChannelPermissions | public struct ChannelPermissions | ||||
| @@ -105,13 +107,15 @@ namespace Discord | |||||
| public static ChannelPermissions TextOnly { get; } = new ChannelPermissions(Convert.ToUInt32("00000000000000111111110000011001", 2)); | public static ChannelPermissions TextOnly { get; } = new ChannelPermissions(Convert.ToUInt32("00000000000000111111110000011001", 2)); | ||||
| public static ChannelPermissions PrivateOnly { get; } = new ChannelPermissions(Convert.ToUInt32("00000000000000011100110000000000", 2)); | public static ChannelPermissions PrivateOnly { get; } = new ChannelPermissions(Convert.ToUInt32("00000000000000011100110000000000", 2)); | ||||
| public static ChannelPermissions VoiceOnly { get; } = new ChannelPermissions(Convert.ToUInt32("00000011111100000000000000011001", 2)); | public static ChannelPermissions VoiceOnly { get; } = new ChannelPermissions(Convert.ToUInt32("00000011111100000000000000011001", 2)); | ||||
| public static ChannelPermissions All(Channel channel) => All(channel.Type, channel.IsPrivate); | |||||
| public static ChannelPermissions All(ChannelType channelType, bool isPrivate) | |||||
| public static ChannelPermissions All(ChannelType channelType) | |||||
| { | { | ||||
| if (isPrivate) return PrivateOnly; | |||||
| else if (channelType == ChannelType.Text) return TextOnly; | |||||
| else if (channelType == ChannelType.Voice) return VoiceOnly; | |||||
| else return None; | |||||
| switch (channelType) | |||||
| { | |||||
| case ChannelType.Text: return TextOnly; | |||||
| case ChannelType.Voice: return VoiceOnly; | |||||
| case ChannelType.Private: return PrivateOnly; | |||||
| default: return None; | |||||
| } | |||||
| } | } | ||||
| public uint RawValue { get; } | public uint RawValue { get; } | ||||
| @@ -191,11 +195,13 @@ namespace Discord | |||||
| RawValue = value; | RawValue = value; | ||||
| } | } | ||||
| public ChannelPermissions(uint rawValue) { RawValue = rawValue; } | public ChannelPermissions(uint rawValue) { RawValue = rawValue; } | ||||
| public override string ToString() => Convert.ToString(RawValue, 2); | |||||
| } | } | ||||
| public struct ChannelPermissionOverrides | |||||
| public struct ChannelTriStatePermissions | |||||
| { | { | ||||
| public static ChannelPermissionOverrides InheritAll { get; } = new ChannelPermissionOverrides(); | |||||
| public static ChannelTriStatePermissions InheritAll { get; } = new ChannelTriStatePermissions(); | |||||
| public uint AllowValue { get; } | public uint AllowValue { get; } | ||||
| public uint DenyValue { get; } | public uint DenyValue { get; } | ||||
| @@ -236,16 +242,16 @@ namespace Discord | |||||
| /// <summary> If True, a user may use voice activation rather than push-to-talk. </summary> | /// <summary> If True, a user may use voice activation rather than push-to-talk. </summary> | ||||
| public PermValue UseVoiceActivation => PermissionsHelper.GetValue(AllowValue, DenyValue, PermissionBits.UseVoiceActivation); | public PermValue UseVoiceActivation => PermissionsHelper.GetValue(AllowValue, DenyValue, PermissionBits.UseVoiceActivation); | ||||
| public ChannelPermissionOverrides(PermValue? createInstantInvite = null, PermValue? managePermissions = null, | |||||
| public ChannelTriStatePermissions(PermValue? createInstantInvite = null, PermValue? managePermissions = null, | |||||
| PermValue? manageChannel = null, PermValue? readMessages = null, PermValue? sendMessages = null, PermValue? sendTTSMessages = null, | PermValue? manageChannel = null, PermValue? readMessages = null, PermValue? sendMessages = null, PermValue? sendTTSMessages = null, | ||||
| PermValue? manageMessages = null, PermValue? embedLinks = null, PermValue? attachFiles = null, PermValue? readMessageHistory = null, | PermValue? manageMessages = null, PermValue? embedLinks = null, PermValue? attachFiles = null, PermValue? readMessageHistory = null, | ||||
| PermValue? mentionEveryone = null, PermValue? connect = null, PermValue? speak = null, PermValue? muteMembers = null, PermValue? deafenMembers = null, | PermValue? mentionEveryone = null, PermValue? connect = null, PermValue? speak = null, PermValue? muteMembers = null, PermValue? deafenMembers = null, | ||||
| PermValue? moveMembers = null, PermValue? useVoiceActivation = null) | PermValue? moveMembers = null, PermValue? useVoiceActivation = null) | ||||
| : this(new ChannelPermissionOverrides(), createInstantInvite, managePermissions, manageChannel, readMessages, sendMessages, sendTTSMessages, | |||||
| : this(new ChannelTriStatePermissions(), createInstantInvite, managePermissions, manageChannel, readMessages, sendMessages, sendTTSMessages, | |||||
| manageMessages, embedLinks, attachFiles, mentionEveryone, connect, speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation) | manageMessages, embedLinks, attachFiles, mentionEveryone, connect, speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation) | ||||
| { | { | ||||
| } | } | ||||
| public ChannelPermissionOverrides(ChannelPermissionOverrides basePerms, PermValue? createInstantInvite = null, PermValue? managePermissions = null, | |||||
| public ChannelTriStatePermissions(ChannelTriStatePermissions basePerms, PermValue? createInstantInvite = null, PermValue? managePermissions = null, | |||||
| PermValue? manageChannel = null, PermValue? readMessages = null, PermValue? sendMessages = null, PermValue? sendTTSMessages = null, | PermValue? manageChannel = null, PermValue? readMessages = null, PermValue? sendMessages = null, PermValue? sendTTSMessages = null, | ||||
| PermValue? manageMessages = null, PermValue? embedLinks = null, PermValue? attachFiles = null, PermValue? readMessageHistory = null, | PermValue? manageMessages = null, PermValue? embedLinks = null, PermValue? attachFiles = null, PermValue? readMessageHistory = null, | ||||
| PermValue? mentionEveryone = null, PermValue? connect = null, PermValue? speak = null, PermValue? muteMembers = null, PermValue? deafenMembers = null, | PermValue? mentionEveryone = null, PermValue? connect = null, PermValue? speak = null, PermValue? muteMembers = null, PermValue? deafenMembers = null, | ||||
| @@ -274,11 +280,13 @@ namespace Discord | |||||
| AllowValue = allow; | AllowValue = allow; | ||||
| DenyValue = deny; | DenyValue = deny; | ||||
| } | } | ||||
| public ChannelPermissionOverrides(uint allow = 0, uint deny = 0) | |||||
| public ChannelTriStatePermissions(uint allow = 0, uint deny = 0) | |||||
| { | { | ||||
| AllowValue = allow; | AllowValue = allow; | ||||
| DenyValue = deny; | DenyValue = deny; | ||||
| } | } | ||||
| public override string ToString() => $"Allow: {Convert.ToString(AllowValue, 2)}, Deny: {Convert.ToString(DenyValue, 2)}"; | |||||
| } | } | ||||
| internal static class PermissionsHelper | internal static class PermissionsHelper | ||||
| { | { | ||||
| @@ -0,0 +1,66 @@ | |||||
| using APIChannel = Discord.API.Client.Channel; | |||||
| using Discord.API.Client.Rest; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.IO; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord | |||||
| { | |||||
| public class PrivateChannel : Channel, IPrivateChannel, ITextChannel | |||||
| { | |||||
| private readonly static Action<PrivateChannel, PrivateChannel> _cloner = DynamicIL.CreateCopyMethod<PrivateChannel>(); | |||||
| private readonly MessageManager _messages; | |||||
| /// <summary> Gets the target user, if this is a private chat. </summary> | |||||
| public User Recipient { get; } | |||||
| public override DiscordClient Client => Recipient.Client; | |||||
| public override ChannelType Type => ChannelType.Private; | |||||
| public override User CurrentUser => Client.PrivateUser; | |||||
| public override IEnumerable<User> Users => new User[] { Client.PrivateUser, Recipient }; | |||||
| internal override MessageManager MessageManager => _messages; | |||||
| internal override PermissionManager PermissionManager => null; | |||||
| internal PrivateChannel(ulong id, User recipient, APIChannel model) | |||||
| : this(id, recipient) | |||||
| { | |||||
| _messages = new MessageManager(this, Client.Config.MessageCacheSize); | |||||
| Update(model); | |||||
| } | |||||
| private PrivateChannel(ulong id, User recipient) | |||||
| :base(id) | |||||
| { | |||||
| Recipient = recipient; | |||||
| } | |||||
| internal override User GetUser(ulong id) | |||||
| { | |||||
| if (id == Recipient.Id) return Recipient; | |||||
| else if (id == Client.CurrentUser.Id) return Client.PrivateUser; | |||||
| else return null; | |||||
| } | |||||
| public Message GetMessage(ulong id) => _messages.Get(id); | |||||
| public Task<Message[]> DownloadMessages(int limit = 100, ulong? relativeMessageId = null, Relative relativeDir = Relative.Before) | |||||
| => _messages.Download(limit, relativeMessageId, relativeDir); | |||||
| public Task<Message> SendMessage(string text, bool isTTS = false) => _messages.Send(text, isTTS); | |||||
| public Task<Message> SendFile(string filePath) => _messages.SendFile(filePath); | |||||
| public Task<Message> SendFile(string filename, Stream stream) => _messages.SendFile(filename, stream); | |||||
| public Task SendIsTyping() => Client.ClientAPI.Send(new SendIsTypingRequest(Id)); | |||||
| public override string ToString() => $"@{Recipient.Name}"; | |||||
| internal override void Update(APIChannel model) { } | |||||
| internal override Channel Clone() | |||||
| { | |||||
| var result = new PrivateChannel(Id, Recipient); | |||||
| _cloner(this, result); | |||||
| return result; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -83,6 +83,6 @@ namespace Discord | |||||
| } | } | ||||
| private Profile() { } //Used for cloning | private Profile() { } //Used for cloning | ||||
| public override string ToString() => Id.ToIdString(); | |||||
| public override string ToString() => Name; | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,109 @@ | |||||
| using APIChannel = Discord.API.Client.Channel; | |||||
| using Discord.API.Client.Rest; | |||||
| using Discord.Net; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Net; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord | |||||
| { | |||||
| /// <summary> A public Discord channel </summary> | |||||
| public abstract class PublicChannel : Channel, IModel, IMentionable | |||||
| { | |||||
| internal readonly PermissionManager _permissions; | |||||
| /// <summary> Gets the server owning this channel. </summary> | |||||
| public Server Server { get; } | |||||
| /// <summary> Gets or sets the name of this channel. </summary> | |||||
| public string Name { get; set; } | |||||
| /// <summary> Getsor sets the position of this channel relative to other channels of the same type in this server. </summary> | |||||
| public int Position { get; set; } | |||||
| /// <summary> Gets the DiscordClient that created this model. </summary> | |||||
| public override DiscordClient Client => Server.Client; | |||||
| public override User CurrentUser => Server.CurrentUser; | |||||
| /// <summary> Gets the string used to mention this channel. </summary> | |||||
| public string Mention => $"<#{Id}>"; | |||||
| /// <summary> Gets a collection of all custom permissions used for this channel. </summary> | |||||
| public IEnumerable<PermissionRule> PermissionRules => _permissions.Rules; | |||||
| internal PublicChannel(APIChannel model, Server server) | |||||
| : this(model.Id, server) | |||||
| { | |||||
| _permissions = new PermissionManager(this, model, server.Client.Config.UsePermissionsCache ? (int)(server.UserCount * 1.05) : -1); | |||||
| Update(model); | |||||
| } | |||||
| protected PublicChannel(ulong id, Server server) | |||||
| : base(id) | |||||
| { | |||||
| Server = server; | |||||
| } | |||||
| internal override void Update(APIChannel model) | |||||
| { | |||||
| if (model.Name != null) Name = model.Name; | |||||
| if (model.Position != null) Position = model.Position.Value; | |||||
| if (model.PermissionOverwrites != null) | |||||
| _permissions.Update(model); | |||||
| } | |||||
| public async Task Delete() | |||||
| { | |||||
| try { await Client.ClientAPI.Send(new DeleteChannelRequest(Id)).ConfigureAwait(false); } | |||||
| catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||||
| } | |||||
| public abstract Task Save(); | |||||
| internal override User GetUser(ulong id) => Server.GetUser(id); | |||||
| public ChannelTriStatePermissions? GetPermissionsRule(User user) => _permissions.GetOverwrite(user); | |||||
| public ChannelTriStatePermissions? GetPermissionsRule(Role role) => _permissions.GetOverwrite(role); | |||||
| public Task AddOrUpdatePermissionsRule(User user, ChannelTriStatePermissions permissions) => _permissions.AddOrUpdateOverwrite(user, permissions); | |||||
| public Task AddOrUpdatePermissionsRule(Role role, ChannelTriStatePermissions permissions) => _permissions.AddOrUpdateOverwrite(role, permissions); | |||||
| public Task RemovePermissionsRule(User user) => _permissions.RemoveOverwrite(user); | |||||
| public async Task RemovePermissionsRule(Role role) | |||||
| { | |||||
| if (role == null) throw new ArgumentNullException(nameof(role)); | |||||
| try { await Client.ClientAPI.Send(new RemoveChannelPermissionsRequest(Id, role.Id)).ConfigureAwait(false); } | |||||
| catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||||
| } | |||||
| internal ChannelPermissions GetPermissions(User user) => _permissions.GetPermissions(user); | |||||
| internal void UpdatePermissions() => _permissions.UpdatePermissions(); | |||||
| internal void UpdatePermissions(User user) => _permissions.UpdatePermissions(user); | |||||
| internal bool ResolvePermissions(User user, ref ChannelPermissions permissions) => _permissions.ResolvePermissions(user, ref permissions); | |||||
| internal override PermissionManager PermissionManager => null; | |||||
| /// <summary> Creates a new invite to this channel. </summary> | |||||
| /// <param name="maxAge"> Time (in seconds) until the invite expires. Set to null to never expire. </param> | |||||
| /// <param name="maxUses"> The max amount of times this invite may be used. Set to null to have unlimited uses. </param> | |||||
| /// <param name="tempMembership"> If true, a user accepting this invite will be kicked from the server after closing their client. </param> | |||||
| /// <param name="withXkcd"> If true, creates a human-readable link. Not supported if maxAge is set to null. </param> | |||||
| public async Task<Invite> CreateInvite(int? maxAge = 1800, int? maxUses = null, bool tempMembership = false, bool withXkcd = false) | |||||
| { | |||||
| if (maxAge < 0) throw new ArgumentOutOfRangeException(nameof(maxAge)); | |||||
| if (maxUses < 0) throw new ArgumentOutOfRangeException(nameof(maxUses)); | |||||
| var request = new CreateInviteRequest(Id) | |||||
| { | |||||
| MaxAge = maxAge ?? 0, | |||||
| MaxUses = maxUses ?? 0, | |||||
| IsTemporary = tempMembership, | |||||
| WithXkcdPass = withXkcd | |||||
| }; | |||||
| var response = await Client.ClientAPI.Send(request).ConfigureAwait(false); | |||||
| var invite = new Invite(Client, response.Code, response.XkcdPass); | |||||
| return invite; | |||||
| } | |||||
| internal void AddUser(User user) => _permissions.AddUser(user); | |||||
| internal void RemoveUser(ulong id) => _permissions.RemoveUser(id); | |||||
| public override string ToString() => $"{Server}/{Name ?? Id.ToIdString()}"; | |||||
| } | |||||
| } | |||||
| @@ -16,5 +16,7 @@ | |||||
| Port = port; | Port = port; | ||||
| Vip = vip; | Vip = vip; | ||||
| } | } | ||||
| public override string ToString() => Name; | |||||
| } | } | ||||
| } | } | ||||
| @@ -32,9 +32,7 @@ namespace Discord | |||||
| public ServerPermissions Permissions { get; private set; } | public ServerPermissions Permissions { get; private set; } | ||||
| /// <summary> Gets the color of this role. </summary> | /// <summary> Gets the color of this role. </summary> | ||||
| public Color Color { get; private set; } | public Color Color { get; private set; } | ||||
| /// <summary> Gets the path to this object. </summary> | |||||
| internal string Path => $"{Server?.Name ?? "[Private]"}/{Name}"; | |||||
| /// <summary> Gets true if this is the role representing all users in a server. </summary> | /// <summary> Gets true if this is the role representing all users in a server. </summary> | ||||
| public bool IsEveryone => Id == Server.Id; | public bool IsEveryone => Id == Server.Id; | ||||
| /// <summary> Gets a list of all members in this role. </summary> | /// <summary> Gets a list of all members in this role. </summary> | ||||
| @@ -132,6 +130,6 @@ namespace Discord | |||||
| } | } | ||||
| private Role() { } //Used for cloning | private Role() { } //Used for cloning | ||||
| public override string ToString() => Name ?? Id.ToIdString(); | |||||
| public override string ToString() => $"{Server}/{Name ?? Id.ToString()}"; | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,4 +1,5 @@ | |||||
| using Discord.API.Client; | |||||
| using APIChannel = Discord.API.Client.Channel; | |||||
| using Discord.API.Client; | |||||
| using Discord.API.Client.Rest; | using Discord.API.Client.Rest; | ||||
| using Discord.Net; | using Discord.Net; | ||||
| using System; | using System; | ||||
| @@ -48,7 +49,7 @@ namespace Discord | |||||
| private ConcurrentDictionary<ulong, Role> _roles; | private ConcurrentDictionary<ulong, Role> _roles; | ||||
| private ConcurrentDictionary<ulong, Member> _users; | private ConcurrentDictionary<ulong, Member> _users; | ||||
| private ConcurrentDictionary<ulong, Channel> _channels; | |||||
| private ConcurrentDictionary<ulong, PublicChannel> _channels; | |||||
| private ulong _ownerId; | private ulong _ownerId; | ||||
| private ulong? _afkChannelId; | private ulong? _afkChannelId; | ||||
| private int _userCount; | private int _userCount; | ||||
| @@ -59,34 +60,31 @@ namespace Discord | |||||
| public ulong Id { get; } | public ulong Id { get; } | ||||
| /// <summary> Gets the name of this server. </summary> | /// <summary> Gets the name of this server. </summary> | ||||
| public string Name { get; private set; } | |||||
| public string Name { get; set; } | |||||
| /// <summary> Gets the voice region for this server. </summary> | /// <summary> Gets the voice region for this server. </summary> | ||||
| public Region Region { get; private set; } | |||||
| /// <summary> Gets the unique identifier for this user's current avatar. </summary> | |||||
| public string IconId { get; private set; } | |||||
| /// <summary> Gets the unique identifier for this server's custom splash image. </summary> | |||||
| public string SplashId { get; private set; } | |||||
| public Region Region { get; set; } | |||||
| /// <summary> Gets the AFK voice channel for this server. </summary> | |||||
| public VoiceChannel AFKChannel { get; set; } | |||||
| /// <summary> Gets the amount of time (in seconds) a user must be inactive for until they are automatically moved to the AFK voice channel, if one is set. </summary> | /// <summary> Gets the amount of time (in seconds) a user must be inactive for until they are automatically moved to the AFK voice channel, if one is set. </summary> | ||||
| public int AFKTimeout { get; private set; } | |||||
| public int AFKTimeout { get; set; } | |||||
| /// <summary> Gets the date and time you joined this server. </summary> | /// <summary> Gets the date and time you joined this server. </summary> | ||||
| public DateTime JoinedAt { get; private set; } | public DateTime JoinedAt { get; private set; } | ||||
| /// <summary> Gets the default channel for this server. </summary> | |||||
| public Channel DefaultChannel { get; private set; } | |||||
| /// <summary> Gets the the role representing all users in a server. </summary> | /// <summary> Gets the the role representing all users in a server. </summary> | ||||
| public Role EveryoneRole { get; private set; } | public Role EveryoneRole { get; private set; } | ||||
| /// <summary> Gets all extra features added to this server. </summary> | /// <summary> Gets all extra features added to this server. </summary> | ||||
| public IEnumerable<string> Features { get; private set; } | public IEnumerable<string> Features { get; private set; } | ||||
| /// <summary> Gets all custom emojis on this server. </summary> | /// <summary> Gets all custom emojis on this server. </summary> | ||||
| public IEnumerable<Emoji> CustomEmojis { get; private set; } | public IEnumerable<Emoji> CustomEmojis { get; private set; } | ||||
| /// <summary> Gets the path to this object. </summary> | |||||
| internal string Path => Name; | |||||
| /// <summary> Gets the unique identifier for this user's current avatar. </summary> | |||||
| public string IconId { get; private set; } | |||||
| /// <summary> Gets the unique identifier for this server's custom splash image. </summary> | |||||
| public string SplashId { get; private set; } | |||||
| /// <summary> Gets the user that created this server. </summary> | /// <summary> Gets the user that created this server. </summary> | ||||
| public User Owner => GetUser(_ownerId); | public User Owner => GetUser(_ownerId); | ||||
| /// <summary> Returns true if the current user owns this server. </summary> | |||||
| public bool IsOwner => _ownerId == Client.CurrentUser.Id; | |||||
| /// <summary> Gets the AFK voice channel for this server. </summary> | |||||
| public Channel AFKChannel => _afkChannelId != null ? GetChannel(_afkChannelId.Value) : null; | |||||
| /// <summary> Gets the default channel for this server. </summary> | |||||
| public TextChannel DefaultChannel => _channels[Id] as TextChannel; | |||||
| /// <summary> Gets the current user in this server. </summary> | /// <summary> Gets the current user in this server. </summary> | ||||
| public User CurrentUser => GetUser(Client.CurrentUser.Id); | public User CurrentUser => GetUser(Client.CurrentUser.Id); | ||||
| /// <summary> Gets the URL to this server's current icon. </summary> | /// <summary> Gets the URL to this server's current icon. </summary> | ||||
| @@ -95,11 +93,11 @@ namespace Discord | |||||
| public string SplashUrl => GetSplashUrl(Id, SplashId); | public string SplashUrl => GetSplashUrl(Id, SplashId); | ||||
| /// <summary> Gets a collection of all channels in this server. </summary> | /// <summary> Gets a collection of all channels in this server. </summary> | ||||
| public IEnumerable<Channel> AllChannels => _channels.Select(x => x.Value); | |||||
| public IEnumerable<PublicChannel> AllChannels => _channels.Select(x => x.Value); | |||||
| /// <summary> Gets a collection of text channels in this server. </summary> | /// <summary> Gets a collection of text channels in this server. </summary> | ||||
| public IEnumerable<Channel> TextChannels => _channels.Select(x => x.Value).Where(x => x.Type == ChannelType.Text); | |||||
| public IEnumerable<TextChannel> TextChannels => _channels.Where(x => x.Value.IsText).Select(x => x.Value as TextChannel); | |||||
| /// <summary> Gets a collection of voice channels in this server. </summary> | /// <summary> Gets a collection of voice channels in this server. </summary> | ||||
| public IEnumerable<Channel> VoiceChannels => _channels.Select(x => x.Value).Where(x => x.Type == ChannelType.Voice); | |||||
| public IEnumerable<VoiceChannel> VoiceChannels => _channels.Where(x => x.Value.IsVoice).Select(x => x.Value as VoiceChannel); | |||||
| /// <summary> Gets a collection of all members in this server. </summary> | /// <summary> Gets a collection of all members in this server. </summary> | ||||
| public IEnumerable<User> Users => _users.Select(x => x.Value.User); | public IEnumerable<User> Users => _users.Select(x => x.Value.User); | ||||
| /// <summary> Gets a collection of all roles in this server. </summary> | /// <summary> Gets a collection of all roles in this server. </summary> | ||||
| @@ -168,10 +166,9 @@ namespace Discord | |||||
| //Only channels or members should have AddXXX(cachePerms: true), not both | //Only channels or members should have AddXXX(cachePerms: true), not both | ||||
| if (model.Channels != null) | if (model.Channels != null) | ||||
| { | { | ||||
| _channels = new ConcurrentDictionary<ulong, Channel>(2, (int)(model.Channels.Length * 1.05)); | |||||
| _channels = new ConcurrentDictionary<ulong, PublicChannel>(2, (int)(model.Channels.Length * 1.05)); | |||||
| foreach (var subModel in model.Channels) | foreach (var subModel in model.Channels) | ||||
| AddChannel(subModel.Id, false).Update(subModel); | |||||
| DefaultChannel = _channels[Id]; | |||||
| AddChannel(subModel, false); | |||||
| } | } | ||||
| if (model.MemberCount != null) | if (model.MemberCount != null) | ||||
| { | { | ||||
| @@ -257,67 +254,53 @@ namespace Discord | |||||
| #endregion | #endregion | ||||
| #region Channels | #region Channels | ||||
| internal Channel AddChannel(ulong id, bool cachePerms) | |||||
| internal PublicChannel AddChannel(APIChannel model, bool cachePerms) | |||||
| { | { | ||||
| var channel = new Channel(Client, id, this); | |||||
| PublicChannel channel; | |||||
| ChannelType type = EnumConverters.ToChannelType(model.Type); | |||||
| if (type == ChannelType.Voice) | |||||
| channel = new VoiceChannel(model, this); | |||||
| else | |||||
| channel = new TextChannel(model, this); | |||||
| if (cachePerms && Client.Config.UsePermissionsCache) | if (cachePerms && Client.Config.UsePermissionsCache) | ||||
| { | { | ||||
| foreach (var user in Users) | foreach (var user in Users) | ||||
| channel.AddUser(user); | channel.AddUser(user); | ||||
| } | } | ||||
| Client.AddChannel(channel); | Client.AddChannel(channel); | ||||
| return _channels.GetOrAdd(id, x => channel); | |||||
| return _channels.GetOrAdd(model.Id, x => channel); | |||||
| } | } | ||||
| internal Channel RemoveChannel(ulong id) | |||||
| internal PublicChannel RemoveChannel(ulong id) | |||||
| { | { | ||||
| Channel channel; | |||||
| PublicChannel channel; | |||||
| _channels.TryRemove(id, out channel); | _channels.TryRemove(id, out channel); | ||||
| return channel; | return channel; | ||||
| } | } | ||||
| /// <summary> Gets the channel with the provided id and owned by this server, or null if not found. </summary> | /// <summary> Gets the channel with the provided id and owned by this server, or null if not found. </summary> | ||||
| public Channel GetChannel(ulong id) | |||||
| public PublicChannel GetChannel(ulong id) | |||||
| { | { | ||||
| Channel result; | |||||
| PublicChannel result; | |||||
| _channels.TryGetValue(id, out result); | _channels.TryGetValue(id, out result); | ||||
| return result; | return result; | ||||
| } | } | ||||
| /// <summary> Returns all channels with the specified server and name. </summary> | |||||
| /// <remarks> Name formats supported: Name, #Name and <#Id>. Search is case-insensitive if exactMatch is false.</remarks> | |||||
| public IEnumerable<Channel> FindChannels(string name, ChannelType type = null, bool exactMatch = false) | |||||
| { | |||||
| if (name == null) throw new ArgumentNullException(nameof(name)); | |||||
| return _channels.Select(x => x.Value).Find(name, type, exactMatch); | |||||
| } | |||||
| public TextChannel GetTextChannel(ulong id) => GetChannel(id) as TextChannel; | |||||
| public VoiceChannel GetVoiceChannel(ulong id) => GetChannel(id) as VoiceChannel; | |||||
| /// <summary> Creates a new channel. </summary> | /// <summary> Creates a new channel. </summary> | ||||
| public async Task<Channel> CreateChannel(string name, ChannelType type) | |||||
| public async Task<PublicChannel> CreateChannel(string name, ChannelType type) | |||||
| { | { | ||||
| if (name == null) throw new ArgumentNullException(nameof(name)); | if (name == null) throw new ArgumentNullException(nameof(name)); | ||||
| if (type == null) throw new ArgumentNullException(nameof(type)); | |||||
| if (type != ChannelType.Text && type != ChannelType.Voice) throw new ArgumentException("Invalid channel type", nameof(type)); | |||||
| var request = new CreateChannelRequest(Id) { Name = name, Type = type.Value }; | |||||
| var request = new CreateChannelRequest(Id) { Name = name, Type = type }; | |||||
| var response = await Client.ClientAPI.Send(request).ConfigureAwait(false); | var response = await Client.ClientAPI.Send(request).ConfigureAwait(false); | ||||
| var channel = AddChannel(response.Id, true); | |||||
| var channel = AddChannel(response, true); | |||||
| channel.Update(response); | channel.Update(response); | ||||
| return channel; | return channel; | ||||
| } | } | ||||
| /// <summary> Reorders the provided channels and optionally places them after a certain channel. </summary> | |||||
| public Task ReorderChannels(IEnumerable<Channel> channels, Channel after = null) | |||||
| { | |||||
| if (channels == null) throw new ArgumentNullException(nameof(channels)); | |||||
| var request = new ReorderChannelsRequest(Id) | |||||
| { | |||||
| ChannelIds = channels.Select(x => x.Id).ToArray(), | |||||
| StartPos = after != null ? after.Position + 1 : channels.Min(x => x.Position) | |||||
| }; | |||||
| return Client.ClientAPI.Send(request); | |||||
| } | |||||
| #endregion | #endregion | ||||
| #region Invites | #region Invites | ||||
| @@ -359,13 +342,6 @@ namespace Discord | |||||
| _roles.TryGetValue(id, out result); | _roles.TryGetValue(id, out result); | ||||
| return result; | return result; | ||||
| } | } | ||||
| /// <summary> Returns all roles with the specified server and name. </summary> | |||||
| /// <remarks> Search is case-insensitive if exactMatch is false.</remarks> | |||||
| public IEnumerable<Role> FindRoles(string name, bool exactMatch = false) | |||||
| { | |||||
| if (name == null) throw new ArgumentNullException(nameof(name)); | |||||
| return _roles.Select(x => x.Value).Find(name, exactMatch); | |||||
| } | |||||
| /// <summary> Creates a new role. </summary> | /// <summary> Creates a new role. </summary> | ||||
| public async Task<Role> CreateRole(string name, ServerPermissions? permissions = null, Color color = null, bool isHoisted = false) | public async Task<Role> CreateRole(string name, ServerPermissions? permissions = null, Color color = null, bool isHoisted = false) | ||||
| @@ -504,15 +480,7 @@ namespace Discord | |||||
| { | { | ||||
| if (name == null) throw new ArgumentNullException(nameof(name)); | if (name == null) throw new ArgumentNullException(nameof(name)); | ||||
| return _users.Select(x => x.Value.User).Find(name, discriminator: discriminator, exactMatch: false).FirstOrDefault(); | |||||
| } | |||||
| /// <summary> Returns all members of this server with the specified name. </summary> | |||||
| /// <remarks> Name formats supported: Name, @Name and <@Id>. Search is case-insensitive if exactMatch is false.</remarks> | |||||
| public IEnumerable<User> FindUsers(string name, bool exactMatch = false) | |||||
| { | |||||
| if (name == null) throw new ArgumentNullException(nameof(name)); | |||||
| return _users.Select(x => x.Value.User).Find(name, exactMatch: exactMatch); | |||||
| return _users.Select(x => x.Value.User).Where(x => x.Discriminator == discriminator && x.Name == name).SingleOrDefault(); | |||||
| } | } | ||||
| /// <summary> Kicks all users with an inactivity greater or equal to the provided number of days. </summary> | /// <summary> Kicks all users with an inactivity greater or equal to the provided number of days. </summary> | ||||
| @@ -0,0 +1,88 @@ | |||||
| using Discord.API.Client.Rest; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.IO; | |||||
| using System.Linq; | |||||
| using System.Threading.Tasks; | |||||
| using APIChannel = Discord.API.Client.Channel; | |||||
| namespace Discord | |||||
| { | |||||
| public class TextChannel : PublicChannel, IPublicChannel, ITextChannel | |||||
| { | |||||
| private readonly static Action<TextChannel, TextChannel> _cloner = DynamicIL.CreateCopyMethod<TextChannel>(); | |||||
| private readonly MessageManager _messages; | |||||
| /// <summary> Gets or sets the topic of this channel. </summary> | |||||
| public string Topic { get; set; } | |||||
| public override ChannelType Type => ChannelType.Text; | |||||
| /// <summary> Gets a collection of all messages the client has seen posted in this channel. This collection does not guarantee any ordering. </summary> | |||||
| public IEnumerable<Message> Messages => _messages != null ? _messages : Enumerable.Empty<Message>(); | |||||
| /// <summary> Gets a collection of all users with read access to this channel. </summary> | |||||
| public override IEnumerable<User> Users | |||||
| { | |||||
| get | |||||
| { | |||||
| if (Client.Config.UsePermissionsCache) | |||||
| return _permissions.Users.Where(x => x.Permissions.ReadMessages == true).Select(x => x.User); | |||||
| else | |||||
| { | |||||
| ChannelPermissions perms = new ChannelPermissions(); | |||||
| return Server.Users.Where(x => | |||||
| { | |||||
| _permissions.ResolvePermissions(x, ref perms); | |||||
| return perms.ReadMessages == true; | |||||
| }); | |||||
| } | |||||
| } | |||||
| } | |||||
| internal override MessageManager MessageManager => _messages; | |||||
| internal TextChannel(APIChannel model, Server server) | |||||
| : base(model, server) | |||||
| { | |||||
| if (Client.Config.MessageCacheSize > 0) | |||||
| _messages = new MessageManager(this, (int)(Client.Config.MessageCacheSize * 1.05)); | |||||
| } | |||||
| private TextChannel(ulong id, Server server) | |||||
| : base(id, server) | |||||
| { | |||||
| } | |||||
| internal override void Update(APIChannel model) | |||||
| { | |||||
| base.Update(model); | |||||
| if (model.Topic != null) Topic = model.Topic; | |||||
| } | |||||
| /// <summary> Save all changes to this channel. </summary> | |||||
| public override async Task Save() | |||||
| { | |||||
| var request = new UpdateChannelRequest(Id) | |||||
| { | |||||
| Name = Name, | |||||
| Topic = Topic, | |||||
| Position = Position | |||||
| }; | |||||
| await Client.ClientAPI.Send(request).ConfigureAwait(false); | |||||
| } | |||||
| public Message GetMessage(ulong id) => _messages.Get(id); | |||||
| public Task<Message[]> DownloadMessages(int limit = 100, ulong? relativeMessageId = null, Relative relativeDir = Relative.Before) | |||||
| => _messages.Download(limit, relativeMessageId, relativeDir); | |||||
| public Task<Message> SendMessage(string text, bool isTTS = false) => _messages.Send(text, isTTS); | |||||
| public Task<Message> SendFile(string filePath) => _messages.SendFile(filePath); | |||||
| public Task<Message> SendFile(string filename, Stream stream) => _messages.SendFile(filename, stream); | |||||
| public Task SendIsTyping() => Client.ClientAPI.Send(new SendIsTypingRequest(Id)); | |||||
| internal override Channel Clone() | |||||
| { | |||||
| var result = new TextChannel(Id, Server); | |||||
| _cloner(this, result); | |||||
| return result; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -73,12 +73,11 @@ namespace Discord | |||||
| // /// <summary> Gets this user's voice token. </summary> | // /// <summary> Gets this user's voice token. </summary> | ||||
| // public string Token { get; private set; } | // public string Token { get; private set; } | ||||
| /// <summary> Gets the path to this object. </summary> | |||||
| internal string Path => $"{Server?.Name ?? "[Private]"}/{Name}"; | |||||
| /// <summary> Gets the current private channel for this user if one exists. </summary> | /// <summary> Gets the current private channel for this user if one exists. </summary> | ||||
| public Channel PrivateChannel => Client.GetPrivateChannel(Id); | |||||
| public PrivateChannel PrivateChannel => Client.GetPrivateChannel(Id); | |||||
| /// <summary> Returns the string used to mention this user. </summary> | /// <summary> Returns the string used to mention this user. </summary> | ||||
| public string Mention => $"<@{Id}>"; | public string Mention => $"<@{Id}>"; | ||||
| public bool IsOwner => Server == null ? false : this == Server.Owner; | |||||
| /// <summary> Returns true if this user has marked themselves as muted. </summary> | /// <summary> Returns true if this user has marked themselves as muted. </summary> | ||||
| public bool IsSelfMuted => (_voiceState & VoiceState.SelfMuted) != 0; | public bool IsSelfMuted => (_voiceState & VoiceState.SelfMuted) != 0; | ||||
| /// <summary> Returns true if this user has marked themselves as deafened. </summary> | /// <summary> Returns true if this user has marked themselves as deafened. </summary> | ||||
| @@ -92,14 +91,15 @@ namespace Discord | |||||
| /// <summary> Returns the time this user was last seen online in this server. </summary> | /// <summary> Returns the time this user was last seen online in this server. </summary> | ||||
| public DateTime? LastOnlineAt => Status != UserStatus.Offline ? DateTime.UtcNow : _lastOnline; | public DateTime? LastOnlineAt => Status != UserStatus.Offline ? DateTime.UtcNow : _lastOnline; | ||||
| /// <summary> Gets this user's current voice channel. </summary> | /// <summary> Gets this user's current voice channel. </summary> | ||||
| public Channel VoiceChannel => _voiceChannelId != null ? Server.GetChannel(_voiceChannelId.Value) : null; | |||||
| public VoiceChannel VoiceChannel => _voiceChannelId != null ? Server.GetVoiceChannel(_voiceChannelId.Value) : null; | |||||
| /// <summary> Gets the URL to this user's current avatar. </summary> | /// <summary> Gets the URL to this user's current avatar. </summary> | ||||
| public string AvatarUrl => GetAvatarUrl(Id, AvatarId); | public string AvatarUrl => GetAvatarUrl(Id, AvatarId); | ||||
| /// <summary> Gets all roles that have been assigned to this user, including the everyone role. </summary> | /// <summary> Gets all roles that have been assigned to this user, including the everyone role. </summary> | ||||
| public IEnumerable<Role> Roles => _roles.Select(x => x.Value); | public IEnumerable<Role> Roles => _roles.Select(x => x.Value); | ||||
| public ServerPermissions ServerPermissions => Server.GetPermissions(this); | |||||
| /// <summary> Returns a collection of all channels this user has permissions to join on this server. </summary> | /// <summary> Returns a collection of all channels this user has permissions to join on this server. </summary> | ||||
| public IEnumerable<Channel> Channels | |||||
| public IEnumerable<IChannel> Channels | |||||
| { | { | ||||
| get | get | ||||
| { | { | ||||
| @@ -108,8 +108,8 @@ namespace Discord | |||||
| if (Client.Config.UsePermissionsCache) | if (Client.Config.UsePermissionsCache) | ||||
| { | { | ||||
| return Server.AllChannels.Where(x => | return Server.AllChannels.Where(x => | ||||
| (x.Type == ChannelType.Text && x.GetPermissions(this).ReadMessages) || | |||||
| (x.Type == ChannelType.Voice && x.GetPermissions(this).Connect)); | |||||
| (x.IsText && x.GetPermissions(this).ReadMessages) || | |||||
| (x.IsVoice && x.GetPermissions(this).Connect)); | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| @@ -117,7 +117,7 @@ namespace Discord | |||||
| return Server.AllChannels | return Server.AllChannels | ||||
| .Where(x => | .Where(x => | ||||
| { | { | ||||
| x.UpdatePermissions(this, ref perms); | |||||
| x.ResolvePermissions(this, ref perms); | |||||
| return (x.Type == ChannelType.Text && perms.ReadMessages) || | return (x.Type == ChannelType.Text && perms.ReadMessages) || | ||||
| (x.Type == ChannelType.Voice && perms.Connect); | (x.Type == ChannelType.Voice && perms.Connect); | ||||
| }); | }); | ||||
| @@ -131,9 +131,9 @@ namespace Discord | |||||
| { | { | ||||
| var privateChannel = Client.GetPrivateChannel(Id); | var privateChannel = Client.GetPrivateChannel(Id); | ||||
| if (privateChannel != null) | if (privateChannel != null) | ||||
| return new Channel[] { privateChannel }; | |||||
| return new IChannel[] { privateChannel }; | |||||
| else | else | ||||
| return new Channel[0]; | |||||
| return new IChannel[0]; | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -266,47 +266,15 @@ namespace Discord | |||||
| var request = new KickMemberRequest(Server.Id, Id); | var request = new KickMemberRequest(Server.Id, Id); | ||||
| return Client.ClientAPI.Send(request); | return Client.ClientAPI.Send(request); | ||||
| } | } | ||||
| #region Permissions | |||||
| public ServerPermissions ServerPermissions => Server.GetPermissions(this); | |||||
| public ChannelPermissions GetPermissions(Channel channel) | |||||
| public ChannelPermissions GetPermissions(PublicChannel channel) | |||||
| { | { | ||||
| if (channel == null) throw new ArgumentNullException(nameof(channel)); | if (channel == null) throw new ArgumentNullException(nameof(channel)); | ||||
| return channel.GetPermissions(this); | return channel.GetPermissions(this); | ||||
| } | } | ||||
| #endregion | |||||
| #region Channels | |||||
| public Task<Channel> CreatePMChannel() | |||||
| => Client.CreatePMChannel(this); | |||||
| #endregion | |||||
| #region Messages | |||||
| public async Task<Message> SendMessage(string text) | |||||
| { | |||||
| if (text == null) throw new ArgumentNullException(nameof(text)); | |||||
| var channel = await CreatePMChannel().ConfigureAwait(false); | |||||
| return await channel.SendMessage(text).ConfigureAwait(false); | |||||
| } | |||||
| public async Task<Message> SendFile(string filePath) | |||||
| { | |||||
| if (filePath == null) throw new ArgumentNullException(nameof(filePath)); | |||||
| var channel = await CreatePMChannel().ConfigureAwait(false); | |||||
| return await channel.SendFile(filePath).ConfigureAwait(false); | |||||
| } | |||||
| public async Task<Message> SendFile(string filename, Stream stream) | |||||
| { | |||||
| if (filename == null) throw new ArgumentNullException(nameof(filename)); | |||||
| if (stream == null) throw new ArgumentNullException(nameof(stream)); | |||||
| var channel = await CreatePMChannel().ConfigureAwait(false); | |||||
| return await channel.SendFile(filename, stream).ConfigureAwait(false); | |||||
| } | |||||
| #endregion | |||||
| #region Roles | |||||
| public Task<PrivateChannel> CreatePMChannel() => Client.CreatePrivateChannel(this); | |||||
| private void UpdateRoles(IEnumerable<Role> roles) | private void UpdateRoles(IEnumerable<Role> roles) | ||||
| { | { | ||||
| bool updated = false; | bool updated = false; | ||||
| @@ -348,11 +316,10 @@ namespace Discord | |||||
| return _roles.ContainsKey(role.Id); | return _roles.ContainsKey(role.Id); | ||||
| } | } | ||||
| public Task AddRoles(params Role[] roles) | |||||
| => Edit(roles: Roles.Concat(roles)); | |||||
| public Task RemoveRoles(params Role[] roles) | |||||
| => Edit(roles: Roles.Except(roles)); | |||||
| #endregion | |||||
| public Task AddRoles(params Role[] roles) => Edit(roles: Roles.Concat(roles)); | |||||
| public Task AddRoles(IEnumerable<Role> roles) => Edit(roles: Roles.Concat(roles)); | |||||
| public Task RemoveRoles(params Role[] roles) => Edit(roles: Roles.Except(roles)); | |||||
| public Task RemoveRoles(IEnumerable<Role> roles) => Edit(roles: Roles.Except(roles)); | |||||
| internal User Clone() | internal User Clone() | ||||
| { | { | ||||
| @@ -362,6 +329,19 @@ namespace Discord | |||||
| } | } | ||||
| private User() { } //Used for cloning | private User() { } //Used for cloning | ||||
| public override string ToString() => Name != null ? $"{Name}#{Discriminator}" : Id.ToIdString(); | |||||
| } | |||||
| public override string ToString() | |||||
| { | |||||
| if (Name != null) | |||||
| return $"{Server?.Name ?? "[Private]"}/{Name}#{Discriminator}"; | |||||
| else | |||||
| return $"{Server?.Name ?? "[Private]"}/{Id}"; | |||||
| } | |||||
| internal string ToString(IChannel channel) | |||||
| { | |||||
| if (Name != null) | |||||
| return $"{channel}/{Name}#{Discriminator}"; | |||||
| else | |||||
| return $"{channel}/{Id}"; | |||||
| } | |||||
| } | |||||
| } | } | ||||
| @@ -0,0 +1,60 @@ | |||||
| using APIChannel = Discord.API.Client.Channel; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Threading.Tasks; | |||||
| using Discord.API.Client.Rest; | |||||
| namespace Discord | |||||
| { | |||||
| public class VoiceChannel : PublicChannel, IPublicChannel, IVoiceChannel | |||||
| { | |||||
| private readonly static Action<VoiceChannel, VoiceChannel> _cloner = DynamicIL.CreateCopyMethod<VoiceChannel>(); | |||||
| public int Bitrate { get; set; } | |||||
| public override ChannelType Type => ChannelType.Public | ChannelType.Voice; | |||||
| /// <summary> Gets a collection of all users currently in this voice channel. </summary> | |||||
| public override IEnumerable<User> Users | |||||
| { | |||||
| get | |||||
| { | |||||
| if (Client.Config.UsePermissionsCache) | |||||
| return _permissions.Users.Select(x => x.User).Where(x => x.VoiceChannel == this); | |||||
| else | |||||
| return Server.Users.Where(x => x.VoiceChannel == this); | |||||
| } | |||||
| } | |||||
| internal override MessageManager MessageManager => null; | |||||
| internal VoiceChannel(APIChannel model, Server server) | |||||
| : base(model, server) | |||||
| { | |||||
| } | |||||
| private VoiceChannel(ulong id, Server server) | |||||
| : base(id, server) | |||||
| { | |||||
| } | |||||
| /// <summary> Save all changes to this channel. </summary> | |||||
| public override async Task Save() | |||||
| { | |||||
| var request = new UpdateChannelRequest(Id) | |||||
| { | |||||
| Name = Name, | |||||
| Position = Position, | |||||
| Bitrate = Bitrate | |||||
| }; | |||||
| await Client.ClientAPI.Send(request).ConfigureAwait(false); | |||||
| } | |||||
| internal override Channel Clone() | |||||
| { | |||||
| var result = new VoiceChannel(Id, Server); | |||||
| _cloner(this, result); | |||||
| return result; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,9 +1,7 @@ | |||||
| { | { | ||||
| "version": "0.9.0-rc3-3", | |||||
| "version": "1.0.0-alpha1", | |||||
| "description": "An unofficial .Net API wrapper for the Discord client.", | "description": "An unofficial .Net API wrapper for the Discord client.", | ||||
| "authors": [ | |||||
| "RogueException" | |||||
| ], | |||||
| "authors": [ "RogueException" ], | |||||
| "tags": [ | "tags": [ | ||||
| "discord", | "discord", | ||||
| "discordapp" | "discordapp" | ||||