diff --git a/src/Discord.Net.Audio/AudioClient.cs b/src/Discord.Net.Audio/AudioClient.cs index 2f21ce7f8..00aacea12 100644 --- a/src/Discord.Net.Audio/AudioClient.cs +++ b/src/Discord.Net.Audio/AudioClient.cs @@ -7,7 +7,6 @@ using Newtonsoft.Json; using Nito.AsyncEx; using System; using System.Diagnostics; -using System.IO; using System.Threading; using System.Threading.Tasks; @@ -15,35 +14,6 @@ namespace Discord.Audio { 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 AsyncLock _connectionLock; private readonly TaskManager _taskManager; @@ -58,14 +28,13 @@ namespace Discord.Audio public GatewaySocket GatewaySocket { get; } public VoiceSocket VoiceSocket { get; } public JsonSerializer Serializer { get; } - public Stream OutputStream { get; } public CancellationToken CancelToken { get; private set; } public string SessionId => GatewaySocket.SessionId; public ConnectionState State => VoiceSocket.State; public Server Server => VoiceSocket.Server; - public Channel Channel => VoiceSocket.Channel; + public VoiceChannel Channel => VoiceSocket.Channel; public AudioClient(DiscordClient client, Server server, int id) { @@ -121,7 +90,6 @@ namespace Discord.Audio GatewaySocket.ReceivedDispatch += (s, e) => OnReceivedEvent(e); VoiceSocket = new VoiceSocket(_config, Config, client.Serializer, client.Log.CreateLogger($"Voice #{id}")); VoiceSocket.Server = server; - OutputStream = new OutStream(this); } public async Task Connect() @@ -216,7 +184,7 @@ namespace Discord.Audio _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.Type != ChannelType.Voice) @@ -248,7 +216,7 @@ namespace Discord.Audio await Disconnect().ConfigureAwait(false); else { - var channel = Service.Client.GetChannel(data.ChannelId.Value); + var channel = Service.Client.GetChannel(data.ChannelId.Value) as VoiceChannel; if (channel != null) VoiceSocket.Channel = channel; else diff --git a/src/Discord.Net.Audio/AudioExtensions.cs b/src/Discord.Net.Audio/AudioExtensions.cs index 705cc893f..7def445a6 100644 --- a/src/Discord.Net.Audio/AudioExtensions.cs +++ b/src/Discord.Net.Audio/AudioExtensions.cs @@ -18,8 +18,8 @@ namespace Discord.Audio return client; } - public static Task JoinAudio(this Channel channel) => channel.Client.GetService().Join(channel); - public static Task LeaveAudio(this Channel channel) => channel.Client.GetService().Leave(channel); + public static Task JoinAudio(this VoiceChannel channel) => channel.Client.GetService().Join(channel); + public static Task LeaveAudio(this VoiceChannel channel) => channel.Client.GetService().Leave(channel); public static Task LeaveAudio(this Server server) => server.Client.GetService().Leave(server); public static IAudioClient GetAudioClient(this Server server) => server.Client.GetService().GetClient(server); } diff --git a/src/Discord.Net.Audio/AudioService.cs b/src/Discord.Net.Audio/AudioService.cs index 3de3531ae..e44a4a1ce 100644 --- a/src/Discord.Net.Audio/AudioService.cs +++ b/src/Discord.Net.Audio/AudioService.cs @@ -113,7 +113,7 @@ namespace Discord.Audio } } - public async Task Join(Channel channel) + public async Task Join(VoiceChannel 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(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)); diff --git a/src/Discord.Net.Audio/IAudioClient.cs b/src/Discord.Net.Audio/IAudioClient.cs index 71c8783d5..a986fad7d 100644 --- a/src/Discord.Net.Audio/IAudioClient.cs +++ b/src/Discord.Net.Audio/IAudioClient.cs @@ -15,11 +15,9 @@ namespace Discord.Audio /// Gets the current state of this client. ConnectionState State { get; } /// Gets the channel this client is currently a member of. - Channel Channel { get; } + VoiceChannel Channel { get; } /// Gets the server this client is bound to. Server Server { get; } - /// Gets a stream object that wraps the Send() function. - Stream OutputStream { get; } /// Gets a cancellation token that triggers when the client is manually disconnected. CancellationToken CancelToken { get; } @@ -31,7 +29,7 @@ namespace Discord.Audio VoiceSocket VoiceSocket { get; } /// Moves the client to another channel on the same server. - Task Join(Channel channel); + Task Join(VoiceChannel channel); /// Disconnects from the Discord server, canceling any pending requests. Task Disconnect(); diff --git a/src/Discord.Net.Audio/Net/VoiceSocket.cs b/src/Discord.Net.Audio/Net/VoiceSocket.cs index 544a7d710..9ee5c60e0 100644 --- a/src/Discord.Net.Audio/Net/VoiceSocket.cs +++ b/src/Discord.Net.Audio/Net/VoiceSocket.cs @@ -46,7 +46,7 @@ namespace Discord.Net.WebSockets public string Token { 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; internal VoiceBuffer OutputBuffer => _sendBuffer; diff --git a/src/Discord.Net.Audio/VirtualClient.cs b/src/Discord.Net.Audio/VirtualClient.cs index 12d285a0d..9c8100e47 100644 --- a/src/Discord.Net.Audio/VirtualClient.cs +++ b/src/Discord.Net.Audio/VirtualClient.cs @@ -16,8 +16,7 @@ namespace Discord.Audio public string SessionId => _client.Server == Server ? _client.SessionId : null; 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 RestClient ClientAPI => _client.Server == Server ? _client.ClientAPI : null; @@ -31,7 +30,7 @@ namespace Discord.Audio } 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 Clear() => _client.Clear(); diff --git a/src/Discord.Net.Audio/project.json b/src/Discord.Net.Audio/project.json index 664767ba7..c41a619c7 100644 --- a/src/Discord.Net.Audio/project.json +++ b/src/Discord.Net.Audio/project.json @@ -1,5 +1,5 @@ { - "version": "0.9.0-rc3", + "version": "1.0.0-alpha1", "description": "A Discord.Net extension adding voice support.", "authors": [ "RogueException" ], "tags": [ "discord", "discordapp" ], @@ -18,7 +18,7 @@ }, "dependencies": { - "Discord.Net": "0.9.0-rc3-3" + "Discord.Net": "1.0.0-alpha1" }, "frameworks": { "net45": { }, diff --git a/src/Discord.Net.Commands/Command.cs b/src/Discord.Net.Commands/Command.cs index a8addc1b1..ccd62798b 100644 --- a/src/Discord.Net.Commands/Command.cs +++ b/src/Discord.Net.Commands/Command.cs @@ -52,7 +52,7 @@ namespace Discord.Commands _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++) { diff --git a/src/Discord.Net.Commands/CommandBuilder.cs b/src/Discord.Net.Commands/CommandBuilder.cs index 129dc24ab..6b945841c 100644 --- a/src/Discord.Net.Commands/CommandBuilder.cs +++ b/src/Discord.Net.Commands/CommandBuilder.cs @@ -79,7 +79,7 @@ namespace Discord.Commands _checks.Add(check); return this; } - public CommandBuilder AddCheck(Func checkFunc, string errorMsg = null) + public CommandBuilder AddCheck(Func checkFunc, string errorMsg = null) { _checks.Add(new GenericPermissionChecker(checkFunc, errorMsg)); return this; @@ -145,7 +145,7 @@ namespace Discord.Commands { _checks.Add(checker); } - public void AddCheck(Func checkFunc, string errorMsg = null) + public void AddCheck(Func checkFunc, string errorMsg = null) { _checks.Add(new GenericPermissionChecker(checkFunc, errorMsg)); } diff --git a/src/Discord.Net.Commands/CommandEventArgs.cs b/src/Discord.Net.Commands/CommandEventArgs.cs index 818f5fa23..70793f5e1 100644 --- a/src/Discord.Net.Commands/CommandEventArgs.cs +++ b/src/Discord.Net.Commands/CommandEventArgs.cs @@ -10,8 +10,7 @@ namespace Discord.Commands public Command Command { get; } 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) { diff --git a/src/Discord.Net.Commands/CommandMap.cs b/src/Discord.Net.Commands/CommandMap.cs index 98decd833..ad280b335 100644 --- a/src/Discord.Net.Commands/CommandMap.cs +++ b/src/Discord.Net.Commands/CommandMap.cs @@ -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; if (_commands.Count > 0) diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 8e6f42c51..ea530d27b 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -63,7 +63,7 @@ namespace Discord.Commands .Description("Returns information about commands.") .Do(async e => { - Channel replyChannel = Config.HelpMode == HelpMode.Public ? e.Channel : await e.User.CreatePMChannel().ConfigureAwait(false); + ITextChannel replyChannel = Config.HelpMode == HelpMode.Public ? e.Channel : await e.User.CreatePMChannel().ConfigureAwait(false); if (e.Args.Length > 0) //Show command help { var map = _map.GetItem(string.Join(" ", e.Args)); @@ -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(); bool isFirstCategory = true; @@ -219,32 +219,12 @@ namespace Discord.Commands if (output.Length == 0) output.Append("There are no commands you have permission to run."); 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 ` for more information."); - } + output.AppendLine("\n\nRun `help ` for more information."); 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(); @@ -255,9 +235,7 @@ namespace Discord.Commands { 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) isFirstCmd = false; @@ -299,7 +277,7 @@ namespace Discord.Commands 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(); string error; @@ -309,7 +287,7 @@ namespace Discord.Commands ShowCommandHelpInternal(command, user, channel, output); 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(command.Text); diff --git a/src/Discord.Net.Commands/Permissions/GenericPermissionChecker.cs b/src/Discord.Net.Commands/GenericPermissionChecker.cs similarity index 54% rename from src/Discord.Net.Commands/Permissions/GenericPermissionChecker.cs rename to src/Discord.Net.Commands/GenericPermissionChecker.cs index 05e95ac64..10d665811 100644 --- a/src/Discord.Net.Commands/Permissions/GenericPermissionChecker.cs +++ b/src/Discord.Net.Commands/GenericPermissionChecker.cs @@ -4,16 +4,16 @@ namespace Discord.Commands.Permissions { internal class GenericPermissionChecker : IPermissionChecker { - private readonly Func _checkFunc; + private readonly Func _checkFunc; private readonly string _error; - public GenericPermissionChecker(Func checkFunc, string error = null) + public GenericPermissionChecker(Func checkFunc, string error = null) { _checkFunc = checkFunc; _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; return _checkFunc(command, user, channel); diff --git a/src/Discord.Net.Commands/Permissions/IPermissionChecker.cs b/src/Discord.Net.Commands/IPermissionChecker.cs similarity index 54% rename from src/Discord.Net.Commands/Permissions/IPermissionChecker.cs rename to src/Discord.Net.Commands/IPermissionChecker.cs index f400c3420..0f317ffef 100644 --- a/src/Discord.Net.Commands/Permissions/IPermissionChecker.cs +++ b/src/Discord.Net.Commands/IPermissionChecker.cs @@ -2,6 +2,6 @@ { 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); } } diff --git a/src/Discord.Net.Commands/Permissions/Levels/PermissionLevelChecker.cs b/src/Discord.Net.Commands/Permissions/Levels/PermissionLevelChecker.cs deleted file mode 100644 index 0092c4edf..000000000 --- a/src/Discord.Net.Commands/Permissions/Levels/PermissionLevelChecker.cs +++ /dev/null @@ -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(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; - } - } -} diff --git a/src/Discord.Net.Commands/Permissions/Levels/PermissionLevelExtensions.cs b/src/Discord.Net.Commands/Permissions/Levels/PermissionLevelExtensions.cs deleted file mode 100644 index 10f153215..000000000 --- a/src/Discord.Net.Commands/Permissions/Levels/PermissionLevelExtensions.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; - -namespace Discord.Commands.Permissions.Levels -{ - public static class PermissionLevelExtensions - { - public static DiscordClient UsingPermissionLevels(this DiscordClient client, Func 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; - } - } -} diff --git a/src/Discord.Net.Commands/Permissions/Levels/PermissionLevelService.cs b/src/Discord.Net.Commands/Permissions/Levels/PermissionLevelService.cs deleted file mode 100644 index 6bab13b97..000000000 --- a/src/Discord.Net.Commands/Permissions/Levels/PermissionLevelService.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; - -namespace Discord.Commands.Permissions.Levels -{ - public class PermissionLevelService : IService - { - private readonly Func _getPermissionsFunc; - - private DiscordClient _client; - public DiscordClient Client => _client; - - public PermissionLevelService(Func getPermissionsFunc) - { - _getPermissionsFunc = getPermissionsFunc; - } - - public void Install(DiscordClient client) - { - _client = client; - } - public int GetPermissionLevel(User user, Channel channel) => _getPermissionsFunc(user, channel); - } -} diff --git a/src/Discord.Net.Commands/Permissions/Userlist/BlacklistChecker.cs b/src/Discord.Net.Commands/Permissions/Userlist/BlacklistChecker.cs deleted file mode 100644 index 23c48cba9..000000000 --- a/src/Discord.Net.Commands/Permissions/Userlist/BlacklistChecker.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Discord.Commands.Permissions.Userlist -{ - public class BlacklistChecker : IPermissionChecker - { - private readonly BlacklistService _service; - - internal BlacklistChecker(DiscordClient client) - { - _service = client.GetService(true); - } - - public bool CanRun(Command command, User user, Channel channel, out string error) - { - error = null; //Use default error text. - return _service.CanRun(user); - } - } -} diff --git a/src/Discord.Net.Commands/Permissions/Userlist/BlacklistExtensions.cs b/src/Discord.Net.Commands/Permissions/Userlist/BlacklistExtensions.cs deleted file mode 100644 index 1ff51ee58..000000000 --- a/src/Discord.Net.Commands/Permissions/Userlist/BlacklistExtensions.cs +++ /dev/null @@ -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 GetBlacklistedUserIds(this DiscordClient client) - => client.GetService().UserIds; - public static void BlacklistUser(this DiscordClient client, User user) - { - client.GetService().Add(user.Id); - } - public static void BlacklistUser(this DiscordClient client, ulong userId) - { - client.GetService().Add(userId); - } - public static void UnBlacklistUser(this DiscordClient client, User user) - { - client.GetService().Remove(user.Id); - } - public static void UnBlacklistUser(this DiscordClient client, ulong userId) - { - client.GetService().Remove(userId); - } - } -} diff --git a/src/Discord.Net.Commands/Permissions/Userlist/BlacklistService.cs b/src/Discord.Net.Commands/Permissions/Userlist/BlacklistService.cs deleted file mode 100644 index ced4c3fdc..000000000 --- a/src/Discord.Net.Commands/Permissions/Userlist/BlacklistService.cs +++ /dev/null @@ -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); - } -} diff --git a/src/Discord.Net.Commands/Permissions/Userlist/UserlistService.cs b/src/Discord.Net.Commands/Permissions/Userlist/UserlistService.cs deleted file mode 100644 index da8264312..000000000 --- a/src/Discord.Net.Commands/Permissions/Userlist/UserlistService.cs +++ /dev/null @@ -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 _userList; - private DiscordClient _client; - - public DiscordClient Client => _client; - public IEnumerable UserIds => _userList.Select(x => x.Key); - - public UserlistService(params ulong[] initialUserIds) - { - _userList = new ConcurrentDictionary(); - 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; - } - } -} diff --git a/src/Discord.Net.Commands/Permissions/Userlist/WhitelistChecker.cs b/src/Discord.Net.Commands/Permissions/Userlist/WhitelistChecker.cs deleted file mode 100644 index fa441644f..000000000 --- a/src/Discord.Net.Commands/Permissions/Userlist/WhitelistChecker.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Discord.Commands.Permissions.Userlist -{ - public class WhitelistChecker : IPermissionChecker - { - private readonly WhitelistService _service; - - internal WhitelistChecker(DiscordClient client) - { - _service = client.GetService(true); - } - - public bool CanRun(Command command, User user, Channel channel, out string error) - { - error = null; //Use default error text. - return _service.CanRun(user); - } - } -} diff --git a/src/Discord.Net.Commands/Permissions/Userlist/WhitelistExtensions.cs b/src/Discord.Net.Commands/Permissions/Userlist/WhitelistExtensions.cs deleted file mode 100644 index 391668298..000000000 --- a/src/Discord.Net.Commands/Permissions/Userlist/WhitelistExtensions.cs +++ /dev/null @@ -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 GetWhitelistedUserIds(this DiscordClient client) - => client.GetService().UserIds; - public static void WhitelistUser(this DiscordClient client, User user) - { - client.GetService().Add(user.Id); - } - public static void WhitelistUser(this DiscordClient client, ulong userId) - { - client.GetService().Add(userId); - } - public static void UnWhitelistUser(this DiscordClient client, User user) - { - client.GetService().Remove(user.Id); - } - public static void RemoveFromWhitelist(this DiscordClient client, ulong userId) - { - client.GetService().Remove(userId); - } - } -} diff --git a/src/Discord.Net.Commands/Permissions/Userlist/WhitelistService.cs b/src/Discord.Net.Commands/Permissions/Userlist/WhitelistService.cs deleted file mode 100644 index ae25d3fd1..000000000 --- a/src/Discord.Net.Commands/Permissions/Userlist/WhitelistService.cs +++ /dev/null @@ -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); - } -} diff --git a/src/Discord.Net.Commands/Permissions/Visibility/PrivateChecker.cs b/src/Discord.Net.Commands/Permissions/Visibility/PrivateChecker.cs deleted file mode 100644 index dd336042d..000000000 --- a/src/Discord.Net.Commands/Permissions/Visibility/PrivateChecker.cs +++ /dev/null @@ -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; - } - } - } -} diff --git a/src/Discord.Net.Commands/Permissions/Visibility/PrivateExtensions.cs b/src/Discord.Net.Commands/Permissions/Visibility/PrivateExtensions.cs deleted file mode 100644 index cb3579983..000000000 --- a/src/Discord.Net.Commands/Permissions/Visibility/PrivateExtensions.cs +++ /dev/null @@ -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; - } - } -} diff --git a/src/Discord.Net.Commands/Permissions/Visibility/PublicChecker.cs b/src/Discord.Net.Commands/Permissions/Visibility/PublicChecker.cs deleted file mode 100644 index 9e70b647b..000000000 --- a/src/Discord.Net.Commands/Permissions/Visibility/PublicChecker.cs +++ /dev/null @@ -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; - } - } - } -} diff --git a/src/Discord.Net.Commands/Permissions/Visibility/PublicExtensions.cs b/src/Discord.Net.Commands/Permissions/Visibility/PublicExtensions.cs deleted file mode 100644 index 8cd78a4fe..000000000 --- a/src/Discord.Net.Commands/Permissions/Visibility/PublicExtensions.cs +++ /dev/null @@ -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; - } - } -} diff --git a/src/Discord.Net.Commands/project.json b/src/Discord.Net.Commands/project.json index c460619e4..124a29dfe 100644 --- a/src/Discord.Net.Commands/project.json +++ b/src/Discord.Net.Commands/project.json @@ -1,5 +1,5 @@ { - "version": "0.9.0-rc3-1", + "version": "1.0.0-alpha1", "description": "A Discord.Net extension adding basic command support.", "authors": [ "RogueException" ], "tags": [ "discord", "discordapp" ], @@ -16,7 +16,7 @@ }, "dependencies": { - "Discord.Net": "0.9.0-rc3-3" + "Discord.Net": "1.0.0-alpha1" }, "frameworks": { "net45": { }, diff --git a/src/Discord.Net.Modules/ModuleChecker.cs b/src/Discord.Net.Modules/ModuleChecker.cs index 7b54c8a2d..5f9b8e116 100644 --- a/src/Discord.Net.Modules/ModuleChecker.cs +++ b/src/Discord.Net.Modules/ModuleChecker.cs @@ -14,9 +14,11 @@ namespace Discord.Modules _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; return true; diff --git a/src/Discord.Net.Modules/ModuleManager.cs b/src/Discord.Net.Modules/ModuleManager.cs index b00dc244f..71b5b0c08 100644 --- a/src/Discord.Net.Modules/ModuleManager.cs +++ b/src/Discord.Net.Modules/ModuleManager.cs @@ -20,11 +20,6 @@ namespace Discord.Modules public class ModuleManager { - public event EventHandler ServerEnabled = delegate { }; - public event EventHandler ServerDisabled = delegate { }; - public event EventHandler ChannelEnabled = delegate { }; - public event EventHandler ChannelDisabled = delegate { }; - public event EventHandler JoinedServer = delegate { }; public event EventHandler LeftServer = delegate { }; public event EventHandler ServerUpdated = delegate { }; @@ -43,10 +38,8 @@ namespace Discord.Modules public event EventHandler UserJoined = delegate { }; public event EventHandler UserLeft = delegate { }; public event EventHandler UserUpdated = delegate { }; - //public event EventHandler UserPresenceUpdated = delegate { }; - //public event EventHandler UserVoiceStateUpdated = delegate { }; public event EventHandler UserUnbanned = delegate { }; - public event EventHandler UserIsTyping = delegate { }; + public event EventHandler UserIsTyping = delegate { }; public event EventHandler MessageReceived = delegate { }; public event EventHandler MessageSent = delegate { }; @@ -56,7 +49,7 @@ namespace Discord.Modules private readonly bool _useServerWhitelist, _useChannelWhitelist, _allowAll, _allowPrivate; private readonly ConcurrentDictionary _enabledServers; - private readonly ConcurrentDictionary _enabledChannels; + private readonly ConcurrentDictionary _enabledChannels; private readonly ConcurrentDictionary _indirectServers; private readonly AsyncLock _lock; @@ -67,7 +60,7 @@ namespace Discord.Modules public ModuleFilter FilterType { get; } public IEnumerable EnabledServers => _enabledServers.Select(x => x.Value); - public IEnumerable EnabledChannels => _enabledChannels.Select(x => x.Value); + public IEnumerable EnabledChannels => _enabledChannels.Select(x => x.Value); internal ModuleManager(DiscordClient client, IModule instance, string name, ModuleFilter filterType) { @@ -85,12 +78,17 @@ namespace Discord.Modules _allowPrivate = filterType.HasFlag(ModuleFilter.AlwaysAllowPrivate); _enabledServers = new ConcurrentDictionary(); - _enabledChannels = new ConcurrentDictionary(); + _enabledChannels = new ConcurrentDictionary(); _indirectServers = new ConcurrentDictionary(); 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 //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.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.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.UserUnbanned += (s, e) => { if (HasIndirectServer(e.Server)) UserUnbanned(s, e); }; } @@ -155,16 +152,7 @@ namespace Discord.Modules 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) { @@ -172,34 +160,18 @@ namespace Discord.Modules if (!_useServerWhitelist) return false; 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() { if (!_useServerWhitelist) throw new InvalidOperationException("This module is not configured to use a server whitelist."); 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(); - } } - public bool EnableChannel(Channel channel) + public bool EnableChannel(ITextChannel channel) { if (channel == null) throw new ArgumentNullException(nameof(channel)); 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()) return EnableChannelInternal(channel); } - public void EnableChannels(IEnumerable channels) + public void EnableChannels(IEnumerable channels) { if (channels == null) throw new ArgumentNullException(nameof(channels)); if (channels.Contains(null)) throw new ArgumentException("Collection cannot contain null.", nameof(channels)); @@ -219,65 +191,55 @@ namespace Discord.Modules EnableChannelInternal(channel); } } - private bool EnableChannelInternal(Channel channel) + private bool EnableChannelInternal(ITextChannel 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 false; } - public bool DisableChannel(Channel channel) + public bool DisableChannel(IChannel channel) { if (channel == null) throw new ArgumentNullException(nameof(channel)); 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() { if (!_useChannelWhitelist) return; using (_lock.Lock()) { - if (ChannelDisabled != null) - { - foreach (var channel in _enabledChannels) - ChannelDisabled(this, new ChannelEventArgs(channel.Value)); - } - _enabledChannels.Clear(); _indirectServers.Clear(); } @@ -293,20 +255,20 @@ namespace Discord.Modules internal bool HasServer(Server server) => _allowAll || - _useServerWhitelist && _enabledServers.ContainsKey(server.Id); + (_useServerWhitelist && _enabledServers.ContainsKey(server.Id)); internal bool HasIndirectServer(Server server) => _allowAll || (_useServerWhitelist && _enabledServers.ContainsKey(server.Id)) || (_useChannelWhitelist && _indirectServers.ContainsKey(server.Id)); - internal bool HasChannel(Channel channel) + internal bool HasChannel(IChannel channel) { 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 (_useServerWhitelist) + if (_useServerWhitelist && channel.IsPublic) { - var server = channel.Server; + var server = (channel as PublicChannel).Server; if (server == null) return false; if (_enabledServers.ContainsKey(server.Id)) return true; } diff --git a/src/Discord.Net.Modules/project.json b/src/Discord.Net.Modules/project.json index 884339080..e8e897cac 100644 --- a/src/Discord.Net.Modules/project.json +++ b/src/Discord.Net.Modules/project.json @@ -1,5 +1,5 @@ { - "version": "0.9.0-rc3", + "version": "1.0.0-alpha1", "description": "A Discord.Net extension adding basic plugin support.", "authors": [ "RogueException" ], "tags": [ "discord", "discordapp" ], @@ -16,8 +16,8 @@ }, "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": { "net45": { }, diff --git a/src/Discord.Net.Net45/Discord.Net.csproj b/src/Discord.Net.Net45/Discord.Net.csproj index b26180c07..93c4eb664 100644 --- a/src/Discord.Net.Net45/Discord.Net.csproj +++ b/src/Discord.Net.Net45/Discord.Net.csproj @@ -526,9 +526,15 @@ Models\Permissions.cs + + Models\PrivateChannel.cs + Models\Profile.cs + + Models\PublicChannel.cs + Models\Region.cs @@ -538,9 +544,15 @@ Models\Server.cs + + Models\TextChannel.cs + Models\User.cs + + Models\VoiceChannel.cs + Net\HttpException.cs diff --git a/src/Discord.Net/API/Client/Common/Channel.cs b/src/Discord.Net/API/Client/Common/Channel.cs index 90ed8bf38..37eac8e48 100644 --- a/src/Discord.Net/API/Client/Common/Channel.cs +++ b/src/Discord.Net/API/Client/Common/Channel.cs @@ -29,5 +29,7 @@ namespace Discord.API.Client public PermissionOverwrite[] PermissionOverwrites { get; set; } [JsonProperty("recipient")] public UserReference Recipient { get; set; } + [JsonProperty("bitrate")] + public int Bitrate { get; set; } } } diff --git a/src/Discord.Net/API/Client/Rest/AddChannelPermission.cs b/src/Discord.Net/API/Client/Rest/AddChannelPermission.cs index 44d08b4bf..bf725bcaf 100644 --- a/src/Discord.Net/API/Client/Rest/AddChannelPermission.cs +++ b/src/Discord.Net/API/Client/Rest/AddChannelPermission.cs @@ -4,7 +4,7 @@ using Newtonsoft.Json; namespace Discord.API.Client.Rest { [JsonObject(MemberSerialization.OptIn)] - public class AddChannelPermissionsRequest : IRestRequest + public class AddOrUpdateChannelPermissionsRequest : IRestRequest { string IRestRequest.Method => "PUT"; string IRestRequest.Endpoint => $"channels/{ChannelId}/permissions/{TargetId}"; @@ -21,7 +21,7 @@ namespace Discord.API.Client.Rest [JsonProperty("deny")] public uint Deny { get; set; } - public AddChannelPermissionsRequest(ulong channelId) + public AddOrUpdateChannelPermissionsRequest(ulong channelId) { ChannelId = channelId; } diff --git a/src/Discord.Net/API/Client/Rest/CreateChannel.cs b/src/Discord.Net/API/Client/Rest/CreateChannel.cs index 0d3309701..90d9afec0 100644 --- a/src/Discord.Net/API/Client/Rest/CreateChannel.cs +++ b/src/Discord.Net/API/Client/Rest/CreateChannel.cs @@ -14,7 +14,7 @@ namespace Discord.API.Client.Rest [JsonProperty("name")] public string Name { get; set; } [JsonProperty("type")] - public string Type { get; set; } + public ChannelType Type { get; set; } public CreateChannelRequest(ulong guildId) { diff --git a/src/Discord.Net/API/Client/Rest/UpdateChannel.cs b/src/Discord.Net/API/Client/Rest/UpdateChannel.cs index 86a35a605..8a82caefd 100644 --- a/src/Discord.Net/API/Client/Rest/UpdateChannel.cs +++ b/src/Discord.Net/API/Client/Rest/UpdateChannel.cs @@ -17,6 +17,8 @@ namespace Discord.API.Client.Rest public string Topic { get; set; } [JsonProperty("position")] public int Position { get; set; } + [JsonProperty("bitrate")] + public int Bitrate { get; set; } public UpdateChannelRequest(ulong channelId) { diff --git a/src/Discord.Net/DiscordClient.Events.cs b/src/Discord.Net/DiscordClient.Events.cs index 5e9b5bc09..b05725f9b 100644 --- a/src/Discord.Net/DiscordClient.Events.cs +++ b/src/Discord.Net/DiscordClient.Events.cs @@ -25,7 +25,7 @@ namespace Discord public event EventHandler ServerUpdated = delegate { }; public event EventHandler ServerUnavailable = delegate { }; public event EventHandler UserBanned = delegate { }; - public event EventHandler UserIsTyping = delegate { }; + public event EventHandler UserIsTyping = delegate { }; public event EventHandler UserJoined = delegate { }; public event EventHandler UserLeft = delegate { }; public event EventHandler UserUpdated = delegate { }; @@ -36,11 +36,11 @@ namespace Discord /*private void OnLoggedOut(bool wasUnexpected, Exception ex) => OnEvent(LoggedOut, new DisconnectedEventArgs(wasUnexpected, ex));*/ - private void OnChannelCreated(Channel channel) + private void OnChannelCreated(IChannel channel) => OnEvent(ChannelCreated, new ChannelEventArgs(channel)); - private void OnChannelDestroyed(Channel channel) + private void OnChannelDestroyed(IChannel 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)); private void OnMessageAcknowledged(Message msg) @@ -77,8 +77,8 @@ namespace Discord private void OnUserBanned(User 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) => OnEvent(UserJoined, new UserEventArgs(user)); private void OnUserLeft(User user) diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index 1b474ca5d..ac29228dc 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -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.Logging; using Discord.Net; @@ -29,8 +30,8 @@ namespace Discord private readonly TaskManager _taskManager; private readonly ServiceCollection _services; private ConcurrentDictionary _servers; - private ConcurrentDictionary _channels; - private ConcurrentDictionary _privateChannels; //Key = RecipientId + private ConcurrentDictionary _channels; + private ConcurrentDictionary _privateChannels; //Key = RecipientId private Dictionary _regions; private Stopwatch _connectionStopwatch; @@ -71,7 +72,7 @@ namespace Discord /// Gets a collection of all servers this client is a member of. public IEnumerable Servers => _servers.Select(x => x.Value); /// Gets a collection of all private channels this client is a member of. - public IEnumerable PrivateChannels => _privateChannels.Select(x => x.Value); + public IEnumerable PrivateChannels => _privateChannels.Select(x => x.Value); /// Gets a collection of all voice regions currently offered by Discord. public IEnumerable Regions => _regions.Select(x => x.Value); @@ -123,8 +124,8 @@ namespace Discord //Cache //ConcurrentLevel = 2 (only REST and WebSocket can add/remove) _servers = new ConcurrentDictionary(2, 0); - _channels = new ConcurrentDictionary(2, 0); - _privateChannels = new ConcurrentDictionary(2, 0); + _channels = new ConcurrentDictionary(2, 0); + _privateChannels = new ConcurrentDictionary(2, 0); //Serialization Serializer = new JsonSerializer(); @@ -335,45 +336,47 @@ namespace Discord } #region Channels - internal void AddChannel(Channel channel) + internal void AddChannel(IChannel 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 (channel.IsPrivate) - _privateChannels.TryRemove(channel.Recipient.Id, out channel); + { + PrivateChannel removed; + _privateChannels.TryRemove((channel as PrivateChannel).Recipient.Id, out removed); + } else - channel.Server.RemoveChannel(id); + (channel as PublicChannel).Server.RemoveChannel(id); } return channel; } - public Channel GetChannel(ulong id) + public IChannel GetChannel(ulong id) { - Channel channel; + IChannel channel; _channels.TryGetValue(id, out 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); return channel; } - internal Task CreatePMChannel(User user) - => CreatePrivateChannel(user.Id); - public async Task CreatePrivateChannel(ulong userId) + public Task CreatePrivateChannel(User user) => CreatePrivateChannel(user.Id); + public async Task CreatePrivateChannel(ulong userId) { var channel = GetPrivateChannel(userId); if (channel != null) return channel; @@ -381,9 +384,7 @@ namespace Discord var request = new CreatePrivateChannelRequest() { RecipientId = userId }; var response = await ClientAPI.Send(request).ConfigureAwait(false); - channel = AddPrivateChannel(response.Id, userId); - channel.Update(response); - return channel; + return AddPrivateChannel(response); } #endregion @@ -429,8 +430,7 @@ namespace Discord #endregion #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) { Server server; @@ -448,11 +448,6 @@ namespace Discord _servers.TryGetValue(id, out server); return server; } - public IEnumerable FindServers(string name) - { - if (name == null) throw new ArgumentNullException(nameof(name)); - return _servers.Select(x => x.Value).Find(name); - } /// Creates a new server with the provided name and region. public async Task 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) _servers = new ConcurrentDictionary(2, (int)(data.Guilds.Length * 1.05)); - _channels = new ConcurrentDictionary(2, (int)(channelCount * 1.05)); - _privateChannels = new ConcurrentDictionary(2, (int)(data.PrivateChannels.Length * 1.05)); + _channels = new ConcurrentDictionary(2, (int)(channelCount * 1.05)); + _privateChannels = new ConcurrentDictionary(2, (int)(data.PrivateChannels.Length * 1.05)); List largeServers = new List(); SessionId = data.SessionId; @@ -516,11 +511,7 @@ namespace Discord } } 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) GatewaySocket.SendRequestMembers(largeServers, "", 0); else @@ -538,9 +529,9 @@ namespace Discord server.Update(data); if (data.Unavailable != false) - Logger.Info($"GUILD_CREATE: {server.Path}"); + Logger.Info($"GUILD_CREATE: {server}"); else - Logger.Info($"GUILD_AVAILABLE: {server.Path}"); + Logger.Info($"GUILD_AVAILABLE: {server}"); if (data.Unavailable != false) OnJoinedServer(server); @@ -556,7 +547,7 @@ namespace Discord { var before = Config.EnablePreUpdateEvents ? server.Clone() : null; server.Update(data); - Logger.Info($"GUILD_UPDATE: {server.Path}"); + Logger.Info($"GUILD_UPDATE: {server}"); OnServerUpdated(before, server); } else @@ -570,9 +561,9 @@ namespace Discord if (server != null) { if (data.Unavailable != true) - Logger.Info($"GUILD_DELETE: {server.Path}"); + Logger.Info($"GUILD_DELETE: {server}"); else - Logger.Info($"GUILD_UNAVAILABLE: {server.Path}"); + Logger.Info($"GUILD_UNAVAILABLE: {server}"); OnServerUnavailable(server); if (data.Unavailable != true) @@ -588,23 +579,22 @@ namespace Discord { var data = e.Payload.ToObject(Serializer); - Channel channel = null; + IChannel channel = null; if (data.GuildId != null) { var server = GetServer(data.GuildId.Value); if (server != null) - channel = server.AddChannel(data.Id, true); + channel = server.AddChannel(data, true); else + { Logger.Warning("CHANNEL_CREATE referenced an unknown guild."); + break; + } } 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; case "CHANNEL_UPDATE": @@ -613,9 +603,9 @@ namespace Discord var channel = GetChannel(data.Id); 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); } else @@ -628,7 +618,7 @@ namespace Discord var channel = RemoveChannel(data.Id); if (channel != null) { - Logger.Info($"CHANNEL_DELETE: {channel.Path}"); + Logger.Info($"CHANNEL_DELETE: {channel}"); OnChannelDestroyed(channel); } else @@ -646,7 +636,7 @@ namespace Discord var user = server.AddUser(data.User.Id, true, true); user.Update(data); user.UpdateActivity(); - Logger.Info($"GUILD_MEMBER_ADD: {user.Path}"); + Logger.Info($"GUILD_MEMBER_ADD: {user}"); OnUserJoined(user); } else @@ -664,7 +654,7 @@ namespace Discord { var before = Config.EnablePreUpdateEvents ? user.Clone() : null; user.Update(data); - Logger.Info($"GUILD_MEMBER_UPDATE: {user.Path}"); + Logger.Info($"GUILD_MEMBER_UPDATE: {user}"); OnUserUpdated(before, user); } else @@ -683,7 +673,7 @@ namespace Discord var user = server.RemoveUser(data.User.Id); if (user != null) { - Logger.Info($"GUILD_MEMBER_REMOVE: {user.Path}"); + Logger.Info($"GUILD_MEMBER_REMOVE: {user}"); OnUserLeft(user); } else @@ -732,7 +722,7 @@ namespace Discord { var role = server.AddRole(data.Data.Id); role.Update(data.Data, false); - Logger.Info($"GUILD_ROLE_CREATE: {role.Path}"); + Logger.Info($"GUILD_ROLE_CREATE: {role}"); OnRoleCreated(role); } else @@ -750,7 +740,7 @@ namespace Discord { var before = Config.EnablePreUpdateEvents ? role.Clone() : null; role.Update(data.Data, true); - Logger.Info($"GUILD_ROLE_UPDATE: {role.Path}"); + Logger.Info($"GUILD_ROLE_UPDATE: {role}"); OnRoleUpdated(before, role); } else @@ -769,7 +759,7 @@ namespace Discord var role = server.RemoveRole(data.RoleId); if (role != null) { - Logger.Info($"GUILD_ROLE_DELETE: {role.Path}"); + Logger.Info($"GUILD_ROLE_DELETE: {role}"); OnRoleDeleted(role); } else @@ -790,7 +780,7 @@ namespace Discord var user = server.GetUser(data.User.Id); if (user != null) { - Logger.Info($"GUILD_BAN_ADD: {user.Path}"); + Logger.Info($"GUILD_BAN_ADD: {user}"); OnUserBanned(user); } else @@ -808,7 +798,7 @@ namespace Discord { var user = new User(this, data.User.Id, server); user.Update(data.User); - Logger.Info($"GUILD_BAN_REMOVE: {user.Path}"); + Logger.Info($"GUILD_BAN_REMOVE: {user}"); OnUserUnbanned(user); } else @@ -821,40 +811,20 @@ namespace Discord { var data = e.Payload.ToObject(Serializer); - Channel channel = GetChannel(data.ChannelId); + var channel = GetChannel(data.ChannelId) as ITextChannel; if (channel != null) { - var user = channel.GetUserFast(data.Author.Id); + var user = (channel as Channel).GetUser(data.Author.Id); if (user != 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); user.UpdateActivity(); - Logger.Verbose($"MESSAGE_CREATE: {channel.Path} ({user.Name ?? "Unknown"})"); + Logger.Verbose($"MESSAGE_CREATE: {channel} ({user})"); OnMessageReceived(msg); } else @@ -867,13 +837,13 @@ namespace Discord case "MESSAGE_UPDATE": { var data = e.Payload.ToObject(Serializer); - var channel = GetChannel(data.ChannelId); + var channel = GetChannel(data.ChannelId) as ITextChannel; 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; 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); } else @@ -883,11 +853,11 @@ namespace Discord case "MESSAGE_DELETE": { var data = e.Payload.ToObject(Serializer); - var channel = GetChannel(data.ChannelId); + var channel = GetChannel(data.ChannelId) as ITextChannel; 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); } else @@ -921,7 +891,7 @@ namespace Discord if (user != null) { if (Config.LogLevel == LogSeverity.Debug) - Logger.Debug($"PRESENCE_UPDATE: {user.Path}"); + Logger.Debug($"PRESENCE_UPDATE: {user}"); var before = Config.EnablePreUpdateEvents ? user.Clone() : null; user.Update(data); OnUserUpdated(before, user); @@ -933,29 +903,18 @@ namespace Discord case "TYPING_START": { var data = e.Payload.ToObject(Serializer); - var channel = GetChannel(data.ChannelId); + var channel = GetChannel(data.ChannelId) as ITextChannel; 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 (Config.LogLevel == LogSeverity.Debug) - Logger.Debug($"TYPING_START: {channel.Path} ({user.Name})"); + Logger.Debug($"TYPING_START: {user.ToString(channel)}"); OnUserIsTypingUpdated(channel, user); user.UpdateActivity(); } } - else - Logger.Warning("TYPING_START referenced an unknown channel."); } break; @@ -970,7 +929,7 @@ namespace Discord if (user != null) { 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; user.Update(data); //Logger.Verbose($"Voice Updated: {server.Name}/{user.Name}"); diff --git a/src/Discord.Net/EnumConverters.cs b/src/Discord.Net/EnumConverters.cs new file mode 100644 index 000000000..7448055f6 --- /dev/null +++ b/src/Discord.Net/EnumConverters.cs @@ -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)); + } + } + } +} diff --git a/src/Discord.Net/Enums/ChannelType.cs b/src/Discord.Net/Enums/ChannelType.cs index 9ed49a701..77b0614ea 100644 --- a/src/Discord.Net/Enums/ChannelType.cs +++ b/src/Discord.Net/Enums/ChannelType.cs @@ -2,36 +2,12 @@ namespace Discord { - public class ChannelType : StringEnum, IEquatable - { - /// A text-only channel. - public static ChannelType Text { get; } = new ChannelType("text"); - /// A voice-only channel. - 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 } } diff --git a/src/Discord.Net/Enums/PermissionTarget.cs b/src/Discord.Net/Enums/PermissionTarget.cs index 38a70e013..d1381a5ec 100644 --- a/src/Discord.Net/Enums/PermissionTarget.cs +++ b/src/Discord.Net/Enums/PermissionTarget.cs @@ -1,35 +1,8 @@ namespace Discord { - public class PermissionTarget : StringEnum + public enum PermissionTarget : byte { - /// A text-only channel. - public static PermissionTarget Role { get; } = new PermissionTarget("role"); - /// A voice-only channel. - 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 } } diff --git a/src/Discord.Net/Events/ChannelEventArgs.cs b/src/Discord.Net/Events/ChannelEventArgs.cs index 4aecf34f1..ac31a27f5 100644 --- a/src/Discord.Net/Events/ChannelEventArgs.cs +++ b/src/Discord.Net/Events/ChannelEventArgs.cs @@ -4,10 +4,8 @@ namespace Discord { 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; } } } diff --git a/src/Discord.Net/Events/ChannelUpdatedEventArgs.cs b/src/Discord.Net/Events/ChannelUpdatedEventArgs.cs index fa8da98ea..138f2dd8e 100644 --- a/src/Discord.Net/Events/ChannelUpdatedEventArgs.cs +++ b/src/Discord.Net/Events/ChannelUpdatedEventArgs.cs @@ -4,12 +4,10 @@ namespace Discord { 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; After = after; diff --git a/src/Discord.Net/Events/MessageEventArgs.cs b/src/Discord.Net/Events/MessageEventArgs.cs index 7edb23347..76c9455dc 100644 --- a/src/Discord.Net/Events/MessageEventArgs.cs +++ b/src/Discord.Net/Events/MessageEventArgs.cs @@ -7,7 +7,7 @@ namespace Discord public Message Message { get; } public User User => Message.User; - public Channel Channel => Message.Channel; + public ITextChannel Channel => Message.Channel; public Server Server => Message.Server; public MessageEventArgs(Message msg) { Message = msg; } diff --git a/src/Discord.Net/Events/MessageUpdatedEventArgs.cs b/src/Discord.Net/Events/MessageUpdatedEventArgs.cs index 849f234e1..1583dc981 100644 --- a/src/Discord.Net/Events/MessageUpdatedEventArgs.cs +++ b/src/Discord.Net/Events/MessageUpdatedEventArgs.cs @@ -8,7 +8,7 @@ namespace Discord public Message After { get; } public User User => After.User; - public Channel Channel => After.Channel; + public ITextChannel Channel => After.Channel; public Server Server => After.Server; public MessageUpdatedEventArgs(Message before, Message after) diff --git a/src/Discord.Net/Events/ChannelUserEventArgs.cs b/src/Discord.Net/Events/TypingEventArgs.cs similarity index 50% rename from src/Discord.Net/Events/ChannelUserEventArgs.cs rename to src/Discord.Net/Events/TypingEventArgs.cs index 819c7fcfa..73d47f688 100644 --- a/src/Discord.Net/Events/ChannelUserEventArgs.cs +++ b/src/Discord.Net/Events/TypingEventArgs.cs @@ -1,11 +1,11 @@ namespace Discord { - public class ChannelUserEventArgs + public class TypingEventArgs { - public Channel Channel { get; } + public ITextChannel Channel { get; } public User User { get; } - public ChannelUserEventArgs(Channel channel, User user) + public TypingEventArgs(ITextChannel channel, User user) { Channel = channel; User = user; diff --git a/src/Discord.Net/IModel.cs b/src/Discord.Net/IModel.cs new file mode 100644 index 000000000..8a86ceb3d --- /dev/null +++ b/src/Discord.Net/IModel.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; + +namespace Discord +{ + public interface IModel + { + ulong Id { get; } + + Task Save(); + } +} diff --git a/src/Discord.Net/InternalExtensions.cs b/src/Discord.Net/InternalExtensions.cs index be9d90b3f..3dec1c7bf 100644 --- a/src/Discord.Net/InternalExtensions.cs +++ b/src/Discord.Net/InternalExtensions.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Globalization; using System.IO; -using System.Linq; using System.Runtime.CompilerServices; namespace Discord @@ -63,85 +61,6 @@ namespace Discord } } - public static IEnumerable Find(this IEnumerable 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 Find(this IEnumerable 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 Find(this IEnumerable 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 Find(this IEnumerable 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) { if (type == ImageType.None) diff --git a/src/Discord.Net/MessageQueue.cs b/src/Discord.Net/MessageQueue.cs index 99cfd6c3f..c1a0948b0 100644 --- a/src/Discord.Net/MessageQueue.cs +++ b/src/Discord.Net/MessageQueue.cs @@ -57,12 +57,12 @@ namespace Discord.Net _pendingSendsByNonce = new ConcurrentDictionary(); } - 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.RawText = text; - msg.Text = msg.Resolve(text); + msg.Text = Message.ResolveMentions(msg.Channel, msg.Text); msg.Nonce = GenerateNonce(); if (_pendingSendsByNonce.TryAdd(msg.Nonce, text)) { @@ -111,110 +111,99 @@ namespace Discord.Net { 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) { 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) { 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) { } }); } diff --git a/src/Discord.Net/Models/Channel.cs b/src/Discord.Net/Models/Channel.cs index bd2b4a9cc..5894ade45 100644 --- a/src/Discord.Net/Models/Channel.cs +++ b/src/Discord.Net/Models/Channel.cs @@ -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.IO; -using System.Linq; -using System.Net; -using System.Threading.Tasks; -using APIChannel = Discord.API.Client.Channel; namespace Discord { - public class Channel : IMentionable + public abstract class Channel : IChannel { - private readonly static Action _cloner = DynamicIL.CreateCopyMethod(); - - private struct Member - { - public User User { get; } - public ChannelPermissions Permissions { get; } - - public Member(User user, ChannelPermissions permissions) - { - User = user; - Permissions = permissions; - } - } - - public class PermissionOverwrite + /// An entry in a public channel's permissions that gives or takes permissions from a specific role or user. + public class PermissionRule { + /// The type of object TargetId is referring to. public PermissionTarget TargetType { get; } + /// The Id of an object, whos type is specified by TargetType, that is the target of permissions being added or taken away. public ulong TargetId { get; } - public ChannelPermissionOverrides Permissions { get; } + /// A collection of permissions that are added or taken away from the target. + 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; TargetId = targetId; - Permissions = new ChannelPermissionOverrides(allow, deny); + Permissions = new ChannelTriStatePermissions(allow, deny); } } - private readonly ConcurrentDictionary _users; - private readonly ConcurrentDictionary _messages; - private Dictionary _permissionOverwrites; - - public DiscordClient Client { get; } - /// Gets the unique identifier for this channel. public ulong Id { get; } - /// Gets the server owning this channel, if this is a public chat. - public Server Server { get; } - /// Gets the target user, if this is a private chat. - public User Recipient { get; } - - /// Gets the name of this channel. - public string Name { get; private set; } - /// Gets the topic of this channel. - public string Topic { get; private set; } - /// Gets the position of this channel relative to other channels in this server. - public int Position { get; private set; } - /// Gets the type of this channel). - public ChannelType Type { get; private set; } - - /// Gets the path to this object. - internal string Path => $"{Server?.Name ?? "[Private]"}/{Name}"; - /// Gets true if this is a private chat with another user. - public bool IsPrivate => Recipient != null; - /// Gets the string used to mention this channel. - public string Mention => $"<#{Id}>"; - /// Gets a collection of all messages the client has seen posted in this channel. This collection does not guarantee any ordering. - public IEnumerable Messages => _messages?.Values ?? Enumerable.Empty(); - /// Gets a collection of all custom permissions used for this channel. - public IEnumerable PermissionOverwrites => _permissionOverwrites.Select(x => x.Value); - - /// Gets a collection of all users with read access to this channel. - public IEnumerable 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(); - } - } - - internal Channel(DiscordClient client, ulong id, Server server) - : this(client, id) - { - Server = server; - if (client.Config.UsePermissionsCache) - _users = new ConcurrentDictionary(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(); - if (client.Config.MessageCacheSize > 0) - _messages = new ConcurrentDictionary(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(); - } - } - - /// Edits this channel, changing only non-null attributes. - 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; } + /// Gets the type of this channel. + 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; } + /// Gets a collection of all users in this channel. + public abstract IEnumerable Users { get; } - #region Invites - /// Gets all active (non-expired) invites to this server. - public async Task> GetInvites() - => (await Server.GetInvites().ConfigureAwait(false)).Where(x => x.Channel.Id == Id); + internal abstract MessageManager MessageManager { get; } + internal abstract PermissionManager PermissionManager { get; } - /// Creates a new invite to this channel. - /// Time (in seconds) until the invite expires. Set to null to never expire. - /// The max amount of times this invite may be used. Set to null to have unlimited uses. - /// If true, a user accepting this invite will be kicked from the server after closing their client. - /// If true, creates a human-readable link. Not supported if maxAge is set to null. - public async Task 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 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]; - } - } - - /// Returns all members of this channel with the specified name. - /// Name formats supported: Name, @Name and <@Id>. Search is case-insensitive if exactMatch is false. - public IEnumerable FindUsers(string name, bool exactMatch = false) - { - if (name == null) throw new ArgumentNullException(nameof(name)); - return Users.Find(name, exactMatch: exactMatch); - } - - public Task SendMessage(string text) => SendMessageInternal(text, false); - public Task SendTTSMessage(string text) => SendMessageInternal(text, true); - private Task 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 SendFile(string filePath) - { - using (var stream = File.OpenRead(filePath)) - return await SendFile(System.IO.Path.GetFileName(filePath), stream).ConfigureAwait(false); - } - public async Task 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(); } } diff --git a/src/Discord.Net/Models/Color.cs b/src/Discord.Net/Models/Color.cs index 3555c5d8d..325aa2f39 100644 --- a/src/Discord.Net/Models/Color.cs +++ b/src/Discord.Net/Models/Color.cs @@ -3,28 +3,6 @@ public class Color { 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; } diff --git a/src/Discord.Net/Models/IChannel.cs b/src/Discord.Net/Models/IChannel.cs new file mode 100644 index 000000000..f39678abb --- /dev/null +++ b/src/Discord.Net/Models/IChannel.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; + +namespace Discord +{ + public interface IChannel + { + /// Gets the unique identifier for this channel. + ulong Id { get; } + DiscordClient Client { get; } + + /// Gets the type of this channel. + ChannelType Type { get; } + bool IsText { get; } + bool IsVoice { get; } + bool IsPrivate { get; } + bool IsPublic { get; } + + /// Gets a collection of all users in this channel. + IEnumerable Users { get; } + } +} diff --git a/src/Discord.Net/Models/IPrivateChannel.cs b/src/Discord.Net/Models/IPrivateChannel.cs new file mode 100644 index 000000000..02c4c1b9c --- /dev/null +++ b/src/Discord.Net/Models/IPrivateChannel.cs @@ -0,0 +1,6 @@ +namespace Discord +{ + public interface IPrivateChannel : IChannel + { + } +} diff --git a/src/Discord.Net/Models/IPublicChannel.cs b/src/Discord.Net/Models/IPublicChannel.cs new file mode 100644 index 000000000..32b8c610e --- /dev/null +++ b/src/Discord.Net/Models/IPublicChannel.cs @@ -0,0 +1,6 @@ +namespace Discord +{ + public interface IPublicChannel : IChannel + { + } +} diff --git a/src/Discord.Net/Models/ITextChannel.cs b/src/Discord.Net/Models/ITextChannel.cs new file mode 100644 index 000000000..af665ab0f --- /dev/null +++ b/src/Discord.Net/Models/ITextChannel.cs @@ -0,0 +1,17 @@ +using System.IO; +using System.Threading.Tasks; + +namespace Discord +{ + public interface ITextChannel : IChannel + { + Message GetMessage(ulong id); + Task DownloadMessages(int limit = 100, ulong? relativeMessageId = null, Relative relativeDir = Relative.Before); + + Task SendMessage(string text, bool isTTS = false); + Task SendFile(string filePath); + Task SendFile(string filename, Stream stream); + + Task SendIsTyping(); + } +} diff --git a/src/Discord.Net/Models/IVoiceChannel.cs b/src/Discord.Net/Models/IVoiceChannel.cs new file mode 100644 index 000000000..7c3f2c194 --- /dev/null +++ b/src/Discord.Net/Models/IVoiceChannel.cs @@ -0,0 +1,6 @@ +namespace Discord +{ + public interface IVoiceChannel : IChannel + { + } +} diff --git a/src/Discord.Net/Models/Invite.cs b/src/Discord.Net/Models/Invite.cs index a6a0407b1..f8ee235ba 100644 --- a/src/Discord.Net/Models/Invite.cs +++ b/src/Discord.Net/Models/Invite.cs @@ -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.Net; using System; using System.Net; using System.Threading.Tasks; -using APIInvite = Discord.API.Client.Invite; namespace Discord { @@ -84,9 +84,7 @@ namespace Discord public bool IsTemporary { get; private set; } /// Gets when this invite was created. public DateTime CreatedAt { get; private set; } - - /// Gets the path to this object. - internal string Path => $"{Server?.Name ?? "[Private]"}/{Code}"; + /// Returns a URL for this invite using XkcdCode if available or Id if not. public string Url => $"{DiscordConfig.InviteUrl}/{Code}"; @@ -138,6 +136,6 @@ namespace Discord } private Invite() { } //Used for cloning - public override string ToString() => XkcdCode ?? Code; + public override string ToString() => $"{Server}/{XkcdCode ?? Code}"; } } diff --git a/src/Discord.Net/Models/Managers/MessageManager.cs b/src/Discord.Net/Models/Managers/MessageManager.cs new file mode 100644 index 000000000..88a95dc48 --- /dev/null +++ b/src/Discord.Net/Models/Managers/MessageManager.cs @@ -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 + { + private readonly ITextChannel _channel; + private readonly int _size; + private readonly ConcurrentDictionary _messages; + private readonly ConcurrentQueue _orderedMessages; + + public MessageManager(ITextChannel channel, int size = 0) + { + _channel = channel; + _size = size; + if (size > 0) + { + _messages = new ConcurrentDictionary(2, size); + _orderedMessages = new ConcurrentQueue(); + } + } + + 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 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 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 SendFile(string filePath) + { + using (var stream = File.OpenRead(filePath)) + return await SendFile(Path.GetFileName(filePath), stream).ConfigureAwait(false); + } + public async Task 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 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(); + } +} diff --git a/src/Discord.Net/Models/Managers/PermissionManager.cs b/src/Discord.Net/Models/Managers/PermissionManager.cs new file mode 100644 index 000000000..4b8eb5c8c --- /dev/null +++ b/src/Discord.Net/Models/Managers/PermissionManager.cs @@ -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 _users; + private Dictionary _rules; + + public IEnumerable Users => _users.Select(x => x.Value); + public IEnumerable Rules => _rules.Values; + + public PermissionManager(PublicChannel channel, APIChannel model, int initialSize = -1) + { + _channel = channel; + if (initialSize >= 0) + _users = new ConcurrentDictionary(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); + } + } +} diff --git a/src/Discord.Net/Models/Message.cs b/src/Discord.Net/Models/Message.cs index a3e0ed699..6a45b2ec1 100644 --- a/src/Discord.Net/Models/Message.cs +++ b/src/Discord.Net/Models/Message.cs @@ -13,10 +13,14 @@ namespace Discord Normal = 0, /// Message is current queued. Queued, + /// Message was deleted. + Deleted, /// Message was deleted before it was sent. Aborted, /// Message failed to be sent. - Failed + Failed, + /// Message has been removed from cache and will no longer receive updates. + Detached } public class Message @@ -29,14 +33,14 @@ namespace Discord private static readonly Attachment[] _initialAttachments = new Attachment[0]; private static readonly Embed[] _initialEmbeds = new Embed[0]; - internal static string CleanUserMentions(Channel channel, string text, List users = null) + internal static string CleanUserMentions(PublicChannel channel, string text, List users = null) { return _userRegex.Replace(text, new MatchEvaluator(e => { ulong 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 (users != null) @@ -47,7 +51,7 @@ namespace Discord return e.Value; //User not found or parse failed })); } - internal static string CleanChannelMentions(Channel channel, string text, List channels = null) + internal static string CleanChannelMentions(PublicChannel channel, string text, List channels = null) { var server = channel.Server; 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)); - 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; } - /*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 { /// Unique identifier for this file. @@ -172,7 +163,7 @@ namespace Discord /// Returns the unique identifier for this message. public ulong Id { get; internal set; } /// Returns the channel this message was sent to. - public Channel Channel { get; } + public ITextChannel Channel { get; } /// Returns the author of this message. public User User { get; } @@ -196,20 +187,18 @@ namespace Discord /// Returns a collection of all users mentioned in this message. public IEnumerable MentionedUsers { get; internal set; } /// Returns a collection of all channels mentioned in this message. - public IEnumerable MentionedChannels { get; internal set; } + public IEnumerable MentionedChannels { get; internal set; } /// Returns a collection of all roles mentioned in this message. public IEnumerable MentionedRoles { get; internal set; } internal int Nonce { get; set; } - - /// Gets the path to this object. - internal string Path => $"{Server?.Name ?? "[Private]"}/{Id}"; + /// Returns the server containing the channel this message was sent to. - public Server Server => Channel.Server; + public Server Server => (Channel as PublicChannel)?.Server; /// Returns if this message was sent from the logged-in accounts. 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; Channel = channel; @@ -222,7 +211,6 @@ namespace Discord internal void Update(APIMessage model) { var channel = Channel; - var server = channel.Server; if (model.Attachments != null) { Attachments = model.Attachments @@ -277,36 +265,34 @@ namespace Discord if (model.Mentions != null) { MentionedUsers = model.Mentions - .Select(x => Channel.GetUserFast(x.Id)) + .Select(x => (Channel as Channel).GetUser(x.Id)) .Where(x => x != null) .ToArray(); } 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(); } if (model.Content != null) { string text = model.Content; RawText = text; - //var mentionedUsers = new List(); - var mentionedChannels = new List(); - //var mentionedRoles = new List(); - 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 mentionedChannels = null; + if (Channel.IsPublic) + mentionedChannels = new List(); + + 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; } - /// Resolves all mentions in a provided string to those users, channels or roles' names. - public string Resolve(string text) - { - if (text == null) throw new ArgumentNullException(nameof(text)); - return Resolve(Channel, text); - } - internal Message Clone() { var result = new Message(); @@ -356,6 +335,6 @@ namespace Discord } private Message() { } //Used for cloning - public override string ToString() => $"{User?.Name ?? "Unknown User"}: {RawText}"; + public override string ToString() => $"{User}: {RawText}"; } } diff --git a/src/Discord.Net/Models/Permissions.cs b/src/Discord.Net/Models/Permissions.cs index 609524dfe..badccc116 100644 --- a/src/Discord.Net/Models/Permissions.cs +++ b/src/Discord.Net/Models/Permissions.cs @@ -97,6 +97,8 @@ namespace Discord RawValue = value; } public ServerPermissions(uint rawValue) { RawValue = rawValue; } + + public override string ToString() => Convert.ToString(RawValue, 2); } public struct ChannelPermissions @@ -105,13 +107,15 @@ namespace Discord 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 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; } @@ -191,11 +195,13 @@ namespace Discord RawValue = value; } 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 DenyValue { get; } @@ -236,16 +242,16 @@ namespace Discord /// If True, a user may use voice activation rather than push-to-talk. 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? 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? 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) { } - 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? 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, @@ -274,11 +280,13 @@ namespace Discord AllowValue = allow; DenyValue = deny; } - public ChannelPermissionOverrides(uint allow = 0, uint deny = 0) + public ChannelTriStatePermissions(uint allow = 0, uint deny = 0) { AllowValue = allow; DenyValue = deny; } + + public override string ToString() => $"Allow: {Convert.ToString(AllowValue, 2)}, Deny: {Convert.ToString(DenyValue, 2)}"; } internal static class PermissionsHelper { diff --git a/src/Discord.Net/Models/PrivateChannel.cs b/src/Discord.Net/Models/PrivateChannel.cs new file mode 100644 index 000000000..b3f3f1faf --- /dev/null +++ b/src/Discord.Net/Models/PrivateChannel.cs @@ -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 _cloner = DynamicIL.CreateCopyMethod(); + + private readonly MessageManager _messages; + + /// Gets the target user, if this is a private chat. + 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 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 DownloadMessages(int limit = 100, ulong? relativeMessageId = null, Relative relativeDir = Relative.Before) + => _messages.Download(limit, relativeMessageId, relativeDir); + + public Task SendMessage(string text, bool isTTS = false) => _messages.Send(text, isTTS); + public Task SendFile(string filePath) => _messages.SendFile(filePath); + public Task 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; + } + } +} diff --git a/src/Discord.Net/Models/Profile.cs b/src/Discord.Net/Models/Profile.cs index 89cdde640..4e499325a 100644 --- a/src/Discord.Net/Models/Profile.cs +++ b/src/Discord.Net/Models/Profile.cs @@ -83,6 +83,6 @@ namespace Discord } private Profile() { } //Used for cloning - public override string ToString() => Id.ToIdString(); + public override string ToString() => Name; } } diff --git a/src/Discord.Net/Models/PublicChannel.cs b/src/Discord.Net/Models/PublicChannel.cs new file mode 100644 index 000000000..112a250ce --- /dev/null +++ b/src/Discord.Net/Models/PublicChannel.cs @@ -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 +{ + /// A public Discord channel + public abstract class PublicChannel : Channel, IModel, IMentionable + { + internal readonly PermissionManager _permissions; + + /// Gets the server owning this channel. + public Server Server { get; } + + /// Gets or sets the name of this channel. + public string Name { get; set; } + /// Getsor sets the position of this channel relative to other channels of the same type in this server. + public int Position { get; set; } + + /// Gets the DiscordClient that created this model. + public override DiscordClient Client => Server.Client; + public override User CurrentUser => Server.CurrentUser; + /// Gets the string used to mention this channel. + public string Mention => $"<#{Id}>"; + /// Gets a collection of all custom permissions used for this channel. + public IEnumerable 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; + + /// Creates a new invite to this channel. + /// Time (in seconds) until the invite expires. Set to null to never expire. + /// The max amount of times this invite may be used. Set to null to have unlimited uses. + /// If true, a user accepting this invite will be kicked from the server after closing their client. + /// If true, creates a human-readable link. Not supported if maxAge is set to null. + public async Task 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()}"; + } +} diff --git a/src/Discord.Net/Models/Region.cs b/src/Discord.Net/Models/Region.cs index 5b0354048..cf144a223 100644 --- a/src/Discord.Net/Models/Region.cs +++ b/src/Discord.Net/Models/Region.cs @@ -16,5 +16,7 @@ Port = port; Vip = vip; } + + public override string ToString() => Name; } } diff --git a/src/Discord.Net/Models/Role.cs b/src/Discord.Net/Models/Role.cs index d3e9f9094..6fb989fec 100644 --- a/src/Discord.Net/Models/Role.cs +++ b/src/Discord.Net/Models/Role.cs @@ -32,9 +32,7 @@ namespace Discord public ServerPermissions Permissions { get; private set; } /// Gets the color of this role. public Color Color { get; private set; } - - /// Gets the path to this object. - internal string Path => $"{Server?.Name ?? "[Private]"}/{Name}"; + /// Gets true if this is the role representing all users in a server. public bool IsEveryone => Id == Server.Id; /// Gets a list of all members in this role. @@ -132,6 +130,6 @@ namespace Discord } private Role() { } //Used for cloning - public override string ToString() => Name ?? Id.ToIdString(); + public override string ToString() => $"{Server}/{Name ?? Id.ToString()}"; } } diff --git a/src/Discord.Net/Models/Server.cs b/src/Discord.Net/Models/Server.cs index b7a9933dc..85e74b4ac 100644 --- a/src/Discord.Net/Models/Server.cs +++ b/src/Discord.Net/Models/Server.cs @@ -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.Net; using System; @@ -48,7 +49,7 @@ namespace Discord private ConcurrentDictionary _roles; private ConcurrentDictionary _users; - private ConcurrentDictionary _channels; + private ConcurrentDictionary _channels; private ulong _ownerId; private ulong? _afkChannelId; private int _userCount; @@ -59,34 +60,31 @@ namespace Discord public ulong Id { get; } /// Gets the name of this server. - public string Name { get; private set; } + public string Name { get; set; } /// Gets the voice region for this server. - public Region Region { get; private set; } - /// Gets the unique identifier for this user's current avatar. - public string IconId { get; private set; } - /// Gets the unique identifier for this server's custom splash image. - public string SplashId { get; private set; } + public Region Region { get; set; } + /// Gets the AFK voice channel for this server. + public VoiceChannel AFKChannel { get; set; } /// 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. - public int AFKTimeout { get; private set; } + public int AFKTimeout { get; set; } + /// Gets the date and time you joined this server. public DateTime JoinedAt { get; private set; } - /// Gets the default channel for this server. - public Channel DefaultChannel { get; private set; } /// Gets the the role representing all users in a server. public Role EveryoneRole { get; private set; } /// Gets all extra features added to this server. public IEnumerable Features { get; private set; } /// Gets all custom emojis on this server. public IEnumerable CustomEmojis { get; private set; } - - /// Gets the path to this object. - internal string Path => Name; + /// Gets the unique identifier for this user's current avatar. + public string IconId { get; private set; } + /// Gets the unique identifier for this server's custom splash image. + public string SplashId { get; private set; } + /// Gets the user that created this server. public User Owner => GetUser(_ownerId); - /// Returns true if the current user owns this server. - public bool IsOwner => _ownerId == Client.CurrentUser.Id; - /// Gets the AFK voice channel for this server. - public Channel AFKChannel => _afkChannelId != null ? GetChannel(_afkChannelId.Value) : null; + /// Gets the default channel for this server. + public TextChannel DefaultChannel => _channels[Id] as TextChannel; /// Gets the current user in this server. public User CurrentUser => GetUser(Client.CurrentUser.Id); /// Gets the URL to this server's current icon. @@ -95,11 +93,11 @@ namespace Discord public string SplashUrl => GetSplashUrl(Id, SplashId); /// Gets a collection of all channels in this server. - public IEnumerable AllChannels => _channels.Select(x => x.Value); + public IEnumerable AllChannels => _channels.Select(x => x.Value); /// Gets a collection of text channels in this server. - public IEnumerable TextChannels => _channels.Select(x => x.Value).Where(x => x.Type == ChannelType.Text); + public IEnumerable TextChannels => _channels.Where(x => x.Value.IsText).Select(x => x.Value as TextChannel); /// Gets a collection of voice channels in this server. - public IEnumerable VoiceChannels => _channels.Select(x => x.Value).Where(x => x.Type == ChannelType.Voice); + public IEnumerable VoiceChannels => _channels.Where(x => x.Value.IsVoice).Select(x => x.Value as VoiceChannel); /// Gets a collection of all members in this server. public IEnumerable Users => _users.Select(x => x.Value.User); /// Gets a collection of all roles in this server. @@ -168,10 +166,9 @@ namespace Discord //Only channels or members should have AddXXX(cachePerms: true), not both if (model.Channels != null) { - _channels = new ConcurrentDictionary(2, (int)(model.Channels.Length * 1.05)); + _channels = new ConcurrentDictionary(2, (int)(model.Channels.Length * 1.05)); foreach (var subModel in model.Channels) - AddChannel(subModel.Id, false).Update(subModel); - DefaultChannel = _channels[Id]; + AddChannel(subModel, false); } if (model.MemberCount != null) { @@ -257,67 +254,53 @@ namespace Discord #endregion #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) { foreach (var user in Users) channel.AddUser(user); } 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); return channel; } /// Gets the channel with the provided id and owned by this server, or null if not found. - public Channel GetChannel(ulong id) + public PublicChannel GetChannel(ulong id) { - Channel result; + PublicChannel result; _channels.TryGetValue(id, out result); return result; } - - /// Returns all channels with the specified server and name. - /// Name formats supported: Name, #Name and <#Id>. Search is case-insensitive if exactMatch is false. - public IEnumerable 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; /// Creates a new channel. - public async Task CreateChannel(string name, ChannelType type) + public async Task CreateChannel(string name, ChannelType type) { 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 channel = AddChannel(response.Id, true); + var channel = AddChannel(response, true); channel.Update(response); return channel; } - - /// Reorders the provided channels and optionally places them after a certain channel. - public Task ReorderChannels(IEnumerable 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 #region Invites @@ -359,13 +342,6 @@ namespace Discord _roles.TryGetValue(id, out result); return result; } - /// Returns all roles with the specified server and name. - /// Search is case-insensitive if exactMatch is false. - public IEnumerable FindRoles(string name, bool exactMatch = false) - { - if (name == null) throw new ArgumentNullException(nameof(name)); - return _roles.Select(x => x.Value).Find(name, exactMatch); - } /// Creates a new role. public async Task 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)); - return _users.Select(x => x.Value.User).Find(name, discriminator: discriminator, exactMatch: false).FirstOrDefault(); - } - /// Returns all members of this server with the specified name. - /// Name formats supported: Name, @Name and <@Id>. Search is case-insensitive if exactMatch is false. - public IEnumerable 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(); } /// Kicks all users with an inactivity greater or equal to the provided number of days. diff --git a/src/Discord.Net/Models/TextChannel.cs b/src/Discord.Net/Models/TextChannel.cs new file mode 100644 index 000000000..8ee97aad1 --- /dev/null +++ b/src/Discord.Net/Models/TextChannel.cs @@ -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 _cloner = DynamicIL.CreateCopyMethod(); + + private readonly MessageManager _messages; + + /// Gets or sets the topic of this channel. + public string Topic { get; set; } + + public override ChannelType Type => ChannelType.Text; + /// Gets a collection of all messages the client has seen posted in this channel. This collection does not guarantee any ordering. + public IEnumerable Messages => _messages != null ? _messages : Enumerable.Empty(); + /// Gets a collection of all users with read access to this channel. + public override IEnumerable 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; + } + /// Save all changes to this channel. + 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 DownloadMessages(int limit = 100, ulong? relativeMessageId = null, Relative relativeDir = Relative.Before) + => _messages.Download(limit, relativeMessageId, relativeDir); + + public Task SendMessage(string text, bool isTTS = false) => _messages.Send(text, isTTS); + public Task SendFile(string filePath) => _messages.SendFile(filePath); + public Task 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; + } + } +} diff --git a/src/Discord.Net/Models/User.cs b/src/Discord.Net/Models/User.cs index 8c45e5dcc..ac2bbe17b 100644 --- a/src/Discord.Net/Models/User.cs +++ b/src/Discord.Net/Models/User.cs @@ -73,12 +73,11 @@ namespace Discord // /// Gets this user's voice token. // public string Token { get; private set; } - /// Gets the path to this object. - internal string Path => $"{Server?.Name ?? "[Private]"}/{Name}"; /// Gets the current private channel for this user if one exists. - public Channel PrivateChannel => Client.GetPrivateChannel(Id); + public PrivateChannel PrivateChannel => Client.GetPrivateChannel(Id); /// Returns the string used to mention this user. public string Mention => $"<@{Id}>"; + public bool IsOwner => Server == null ? false : this == Server.Owner; /// Returns true if this user has marked themselves as muted. public bool IsSelfMuted => (_voiceState & VoiceState.SelfMuted) != 0; /// Returns true if this user has marked themselves as deafened. @@ -92,14 +91,15 @@ namespace Discord /// Returns the time this user was last seen online in this server. public DateTime? LastOnlineAt => Status != UserStatus.Offline ? DateTime.UtcNow : _lastOnline; /// Gets this user's current voice channel. - public Channel VoiceChannel => _voiceChannelId != null ? Server.GetChannel(_voiceChannelId.Value) : null; + public VoiceChannel VoiceChannel => _voiceChannelId != null ? Server.GetVoiceChannel(_voiceChannelId.Value) : null; /// Gets the URL to this user's current avatar. public string AvatarUrl => GetAvatarUrl(Id, AvatarId); /// Gets all roles that have been assigned to this user, including the everyone role. public IEnumerable Roles => _roles.Select(x => x.Value); + public ServerPermissions ServerPermissions => Server.GetPermissions(this); /// Returns a collection of all channels this user has permissions to join on this server. - public IEnumerable Channels + public IEnumerable Channels { get { @@ -108,8 +108,8 @@ namespace Discord if (Client.Config.UsePermissionsCache) { 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 { @@ -117,7 +117,7 @@ namespace Discord return Server.AllChannels .Where(x => { - x.UpdatePermissions(this, ref perms); + x.ResolvePermissions(this, ref perms); return (x.Type == ChannelType.Text && perms.ReadMessages) || (x.Type == ChannelType.Voice && perms.Connect); }); @@ -131,9 +131,9 @@ namespace Discord { var privateChannel = Client.GetPrivateChannel(Id); if (privateChannel != null) - return new Channel[] { privateChannel }; + return new IChannel[] { privateChannel }; else - return new Channel[0]; + return new IChannel[0]; } } } @@ -266,47 +266,15 @@ namespace Discord var request = new KickMemberRequest(Server.Id, Id); 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)); return channel.GetPermissions(this); } - #endregion - - #region Channels - public Task CreatePMChannel() - => Client.CreatePMChannel(this); - #endregion - - #region Messages - public async Task 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 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 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 CreatePMChannel() => Client.CreatePrivateChannel(this); + private void UpdateRoles(IEnumerable roles) { bool updated = false; @@ -348,11 +316,10 @@ namespace Discord 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 roles) => Edit(roles: Roles.Concat(roles)); + public Task RemoveRoles(params Role[] roles) => Edit(roles: Roles.Except(roles)); + public Task RemoveRoles(IEnumerable roles) => Edit(roles: Roles.Except(roles)); internal User Clone() { @@ -362,6 +329,19 @@ namespace Discord } 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}"; + } + } } \ No newline at end of file diff --git a/src/Discord.Net/Models/VoiceChannel.cs b/src/Discord.Net/Models/VoiceChannel.cs new file mode 100644 index 000000000..5b40a6aad --- /dev/null +++ b/src/Discord.Net/Models/VoiceChannel.cs @@ -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 _cloner = DynamicIL.CreateCopyMethod(); + + public int Bitrate { get; set; } + + public override ChannelType Type => ChannelType.Public | ChannelType.Voice; + /// Gets a collection of all users currently in this voice channel. + public override IEnumerable 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) + { + } + + + /// Save all changes to this channel. + 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; + } + } +} diff --git a/src/Discord.Net/project.json b/src/Discord.Net/project.json index 565bc2e86..80a6fb51d 100644 --- a/src/Discord.Net/project.json +++ b/src/Discord.Net/project.json @@ -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.", - "authors": [ - "RogueException" - ], + "authors": [ "RogueException" ], "tags": [ "discord", "discordapp"