diff --git a/src/Discord.Net.Net45/Discord.Net.csproj b/src/Discord.Net.Net45/Discord.Net.csproj index d92fa3858..1d54ee4b0 100644 --- a/src/Discord.Net.Net45/Discord.Net.csproj +++ b/src/Discord.Net.Net45/Discord.Net.csproj @@ -154,17 +154,35 @@ DiscordAPIClientConfig.cs - - DiscordClient.API.cs + + DiscordClient.Bans.cs - - DiscordClient.Cache.cs + + DiscordClient.Channels.cs DiscordClient.cs - - DiscordClient.Events.cs + + DiscordClient.Invites.cs + + + DiscordClient.Members.cs + + + DiscordClient.Messages.cs + + + DiscordClient.Permissions.cs + + + DiscordClient.Roles.cs + + + DiscordClient.Servers.cs + + + DiscordClient.Users.cs DiscordClientConfig.cs diff --git a/src/Discord.Net/Collections/AsyncCollection.cs b/src/Discord.Net/Collections/AsyncCollection.cs index 052988535..9d4a2d79b 100644 --- a/src/Discord.Net/Collections/AsyncCollection.cs +++ b/src/Discord.Net/Collections/AsyncCollection.cs @@ -127,8 +127,8 @@ namespace Discord.Collections } } - protected abstract void OnCreated(TValue item); - protected abstract void OnRemoved(TValue item); + protected virtual void OnCreated(TValue item) { } + protected virtual void OnRemoved(TValue item) { } public IEnumerator GetEnumerator() { diff --git a/src/Discord.Net/Collections/Channels.cs b/src/Discord.Net/Collections/Channels.cs index 4b9a88cff..a2fb81b24 100644 --- a/src/Discord.Net/Collections/Channels.cs +++ b/src/Discord.Net/Collections/Channels.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; namespace Discord.Collections { @@ -46,29 +44,13 @@ namespace Discord.Collections } } - internal Channel this[string id] => Get(id); - - internal IEnumerable Find(string serverId, string name, string type = null) + internal Channel this[string id] { - if (serverId == null) throw new ArgumentNullException(nameof(serverId)); - - IEnumerable result; - if (name.StartsWith("#")) - { - string name2 = name.Substring(1); - result = this.Where(x => x.ServerId == serverId && - string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) || string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); - } - else + get { - result = this.Where(x => x.ServerId == serverId && - string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); + if (id == null) throw new ArgumentNullException(nameof(id)); + return Get(id); } - - if (type != null) - result = result.Where(x => x.Type == type); - - return result; } } } diff --git a/src/Discord.Net/Collections/Members.cs b/src/Discord.Net/Collections/Members.cs index c8c4deae1..363a5d37c 100644 --- a/src/Discord.Net/Collections/Members.cs +++ b/src/Discord.Net/Collections/Members.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; namespace Discord.Collections { @@ -45,52 +43,8 @@ namespace Discord.Collections { if (serverId == null) throw new ArgumentNullException(nameof(serverId)); if (userId == null) throw new ArgumentNullException(nameof(userId)); - return Get(GetKey(userId, serverId)); } } - - internal IEnumerable Find(Server server, string name) - { - if (server == null) throw new ArgumentNullException(nameof(server)); - if (name == null) throw new ArgumentNullException(nameof(name)); - - if (name.StartsWith("@")) - { - string name2 = name.Substring(1); - return server.Members.Where(x => - { - var user = x.User; - if (user == null) - return false; - return string.Equals(user.Name, name, StringComparison.OrdinalIgnoreCase) || string.Equals(user.Name, name2, StringComparison.OrdinalIgnoreCase); - }); - } - else - { - return server.Members.Where(x => - { - var user = x.User; - if (user == null) - return false; - return string.Equals(x.User.Name, name, StringComparison.OrdinalIgnoreCase); - }); - } - } - - internal Member Find(string username, string discriminator) - { - if (username == null) throw new ArgumentNullException(nameof(username)); - if (discriminator == null) throw new ArgumentNullException(nameof(discriminator)); - - if (username.StartsWith("@")) - username = username.Substring(1); - - return this.Where(x => - string.Equals(x.Name, username, StringComparison.OrdinalIgnoreCase) && - x.Discriminator == discriminator - ) - .FirstOrDefault(); - } } } diff --git a/src/Discord.Net/Collections/Messages.cs b/src/Discord.Net/Collections/Messages.cs index 0943c8a9d..16b2d32a7 100644 --- a/src/Discord.Net/Collections/Messages.cs +++ b/src/Discord.Net/Collections/Messages.cs @@ -1,11 +1,11 @@ -namespace Discord.Collections +using System; + +namespace Discord.Collections { public sealed class Messages : AsyncCollection { internal Messages(DiscordClient client, object writerLock) - : base(client, writerLock) - { - } + : base(client, writerLock) { } internal Message GetOrAdd(string id, string channelId, string userId) => GetOrAdd(id, () => new Message(_client, id, channelId, userId)); internal new Message TryRemove(string id) => base.TryRemove(id); @@ -26,6 +26,13 @@ user.RemoveRef(); } - internal Message this[string id] => Get(id); + internal Message this[string id] + { + get + { + if (id == null) throw new ArgumentNullException(nameof(id)); + return Get(id); + } + } } } diff --git a/src/Discord.Net/Collections/Roles.cs b/src/Discord.Net/Collections/Roles.cs index 502c9b8d7..fd83a7c1e 100644 --- a/src/Discord.Net/Collections/Roles.cs +++ b/src/Discord.Net/Collections/Roles.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; namespace Discord.Collections { @@ -23,23 +21,12 @@ namespace Discord.Collections item.Server.RemoveRole(item.Id); } - internal Role this[string id] => Get(id); - - internal IEnumerable Find(string serverId, string name) + internal Role this[string id] { - if (serverId == null) throw new ArgumentNullException(nameof(serverId)); - if (name == null) throw new ArgumentNullException(nameof(name)); - - if (name.StartsWith("@")) - { - string name2 = name.Substring(1); - return this.Where(x => x.ServerId == serverId && - string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) || string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); - } - else + get { - return this.Where(x => x.ServerId == serverId && - string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); + if (id == null) throw new ArgumentNullException(nameof(id)); + return Get(id); } } } diff --git a/src/Discord.Net/Collections/Servers.cs b/src/Discord.Net/Collections/Servers.cs index d1f6b9245..ddd44071a 100644 --- a/src/Discord.Net/Collections/Servers.cs +++ b/src/Discord.Net/Collections/Servers.cs @@ -11,8 +11,7 @@ namespace Discord.Collections internal Server GetOrAdd(string id) => base.GetOrAdd(id, () => new Server(_client, id)); internal new Server TryRemove(string id) => base.TryRemove(id); - - protected override void OnCreated(Server item) { } + protected override void OnRemoved(Server item) { var channels = _client.Channels; @@ -29,12 +28,5 @@ namespace Discord.Collections } internal Server this[string id] => Get(id); - - internal IEnumerable Find(string name) - { - if (name == null) throw new ArgumentNullException(nameof(name)); - - return this.Where(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); - } } } diff --git a/src/Discord.Net/Collections/Users.cs b/src/Discord.Net/Collections/Users.cs index 11f301aaf..90317b65b 100644 --- a/src/Discord.Net/Collections/Users.cs +++ b/src/Discord.Net/Collections/Users.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; namespace Discord.Collections { @@ -15,22 +13,12 @@ namespace Discord.Collections protected override void OnCreated(User item) { } protected override void OnRemoved(User item) { } - internal User this[string id] => Get(id); - - internal IEnumerable Find(string name) + internal User this[string id] { - if (name == null) throw new ArgumentNullException(nameof(name)); - - if (name.StartsWith("@")) - { - string name2 = name.Substring(1); - return this.Where(x => - string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) || string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); - } - else + get { - return this.Where(x => - string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); + if (id == null) throw new ArgumentNullException(nameof(id)); + return Get(id); } } } diff --git a/src/Discord.Net/DiscordClient.API.cs b/src/Discord.Net/DiscordClient.API.cs deleted file mode 100644 index e99561ebd..000000000 --- a/src/Discord.Net/DiscordClient.API.cs +++ /dev/null @@ -1,756 +0,0 @@ -using Discord.API; -using Discord.Net; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Threading.Tasks; - -namespace Discord -{ - public partial class DiscordClient - { - public const int MaxMessageSize = 2000; - - //Bans - /// Bans a user from the provided server. - public Task Ban(Member member) - => Ban(member?.ServerId, member?.UserId); - /// Bans a user from the provided server. - public Task Ban(Server server, User user) - => Ban(server?.Id, user?.Id); - /// Bans a user from the provided server. - public Task Ban(Server server, string userId) - => Ban(server?.Id, userId); - /// Bans a user from the provided server. - public Task Ban(string server, User user) - => Ban(server, user?.Id); - /// Bans a user from the provided server. - public Task Ban(string serverId, string userId) - { - CheckReady(); - if (serverId == null) throw new ArgumentNullException(nameof(serverId)); - if (userId == null) throw new ArgumentNullException(nameof(userId)); - - return _api.Ban(serverId, userId); - } - - /// Unbans a user from the provided server. - public Task Unban(Member member) - => Unban(member?.ServerId, member?.UserId); - /// Unbans a user from the provided server. - public Task Unban(Server server, User user) - => Unban(server?.Id, user?.Id); - /// Unbans a user from the provided server. - public Task Unban(Server server, string userId) - => Unban(server?.Id, userId); - /// Unbans a user from the provided server. - public Task Unban(string server, User user) - => Unban(server, user?.Id); - /// Unbans a user from the provided server. - public async Task Unban(string serverId, string userId) - { - CheckReady(); - if (serverId == null) throw new ArgumentNullException(nameof(serverId)); - if (userId == null) throw new ArgumentNullException(nameof(userId)); - - try { await _api.Unban(serverId, userId).ConfigureAwait(false); } - catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } - } - - //Channels - /// Creates a new channel with the provided name and type (see ChannelTypes). - public Task CreateChannel(Server server, string name, string type = ChannelTypes.Text) - => CreateChannel(server?.Id, name, type); - /// Creates a new channel with the provided name and type (see ChannelTypes). - public async Task CreateChannel(string serverId, string name, string type = ChannelTypes.Text) - { - CheckReady(); - if (serverId == null) throw new ArgumentNullException(nameof(serverId)); - if (name == null) throw new ArgumentNullException(nameof(name)); - if (type == null) throw new ArgumentNullException(nameof(type)); - - var response = await _api.CreateChannel(serverId, name, type).ConfigureAwait(false); - var channel = _channels.GetOrAdd(response.Id, response.GuildId, response.Recipient?.Id); - channel.Update(response); - return channel; - } - - /// Returns the private channel with the provided user, creating one if it does not currently exist. - public Task CreatePMChannel(string userId) => CreatePMChannel(_users[userId], userId); - /// Returns the private channel with the provided user, creating one if it does not currently exist. - public Task CreatePMChannel(User user) => CreatePMChannel(user, user?.Id); - /// Returns the private channel with the provided user, creating one if it does not currently exist. - public Task CreatePMChannel(Member member) => CreatePMChannel(member.User, member.UserId); - private async Task CreatePMChannel(User user, string userId) - { - CheckReady(); - if (userId == null) throw new ArgumentNullException(nameof(userId)); - - Channel channel = null; - if (user != null) - channel = user.PrivateChannel; - if (channel == null) - { - var response = await _api.CreatePMChannel(CurrentUserId, userId).ConfigureAwait(false); - user = _users.GetOrAdd(response.Recipient?.Id); - user.Update(response.Recipient); - channel = _channels.GetOrAdd(response.Id, response.GuildId, response.Recipient?.Id); - channel.Update(response); - } - return channel; - } - - /// Edits the provided channel, changing only non-null attributes. - public Task EditChannel(string channelId, string name = null, string topic = null, int? position = null) - => EditChannel(_channels[channelId], name: name, topic: topic, position: position); - /// Edits the provided channel, changing only non-null attributes. - public async Task EditChannel(Channel channel, string name = null, string topic = null, int? position = null) - { - CheckReady(); - if (channel == null) throw new ArgumentNullException(nameof(channel)); - - await _api.EditChannel(channel.Id, name: name, topic: topic); - - if (position != null) - { - int oldPos = channel.Position; - int newPos = position.Value; - int minPos; - Channel[] channels = channel.Server.Channels.OrderBy(x => x.Position).ToArray(); - - if (oldPos < newPos) //Moving Down - { - minPos = oldPos; - for (int i = oldPos; i < newPos; i++) - channels[i] = channels[i + 1]; - channels[newPos] = channel; - } - else //(oldPos > newPos) Moving Up - { - minPos = newPos; - for (int i = oldPos; i > newPos; i--) - channels[i] = channels[i - 1]; - channels[newPos] = channel; - } - await _api.ReorderChannels(channel.ServerId, channels.Skip(minPos).Select(x => x.Id), minPos); - } - } - - public Task ReorderChannels(Server server, IEnumerable channels, int startPos = 0) - => ReorderChannels(server.Id, channels, startPos); - public Task ReorderChannels(string serverId, IEnumerable channels, int startPos = 0) - { - if (serverId == null) throw new ArgumentNullException(nameof(serverId)); - if (channels == null) throw new ArgumentNullException(nameof(channels)); - if (startPos < 0) throw new ArgumentOutOfRangeException(nameof(startPos), "startPos must be a positive integer."); - - var channelIds = CollectionHelper.FlattenChannels(channels); - return _api.ReorderChannels(serverId, channelIds, startPos); - } - - /// Destroys the provided channel. - public Task DestroyChannel(Channel channel) - => DestroyChannel(channel?.Id); - /// Destroys the provided channel. - public async Task DestroyChannel(string channelId) - { - CheckReady(); - if (channelId == null) throw new ArgumentNullException(nameof(channelId)); - - try { await _api.DestroyChannel(channelId).ConfigureAwait(false); } - catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } - return _channels.TryRemove(channelId); - } - - //Invites - /// Creates a new invite to the default channel of the provided server. - /// Time (in seconds) until the invite expires. Set to 0 to never expire. - /// 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 0. - /// The max amount of times this invite may be used. Set to 0 to have unlimited uses. - public Task CreateInvite(Server server, int maxAge = 1800, int maxUses = 0, bool tempMembership = false, bool hasXkcd = false) - => CreateInvite(server?.DefaultChannelId, maxAge, maxUses, tempMembership, hasXkcd); - /// Creates a new invite to the provided channel. - /// Time (in seconds) until the invite expires. Set to 0 to never expire. - /// 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 0. - /// The max amount of times this invite may be used. Set to 0 to have unlimited uses. - public Task CreateInvite(Channel channel, int maxAge = 1800, int maxUses = 0, bool tempMembership = false, bool hasXkcd = false) - => CreateInvite(channel?.Id, maxAge, maxUses, tempMembership, hasXkcd); - /// Creates a new invite to the provided channel. - /// Time (in seconds) until the invite expires. Set to 0 to never expire. - /// 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 0. - /// The max amount of times this invite may be used. Set to 0 to have unlimited uses. - public async Task CreateInvite(string serverOrChannelId, int maxAge = 1800, int maxUses = 0, bool tempMembership = false, bool hasXkcd = false) - { - CheckReady(); - if (serverOrChannelId == null) throw new ArgumentNullException(nameof(serverOrChannelId)); - if (maxAge <= 0) throw new ArgumentOutOfRangeException(nameof(maxAge)); - if (maxUses <= 0) throw new ArgumentOutOfRangeException(nameof(maxUses)); - - var response = await _api.CreateInvite(serverOrChannelId, maxAge, maxUses, tempMembership, hasXkcd).ConfigureAwait(false); - var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id); - invite.Update(response); - return invite; - } - - /// Deletes the provided invite. - public async Task DestroyInvite(string inviteId) - { - CheckReady(); - if (inviteId == null) throw new ArgumentNullException(nameof(inviteId)); - - try - { - //Check if this is a human-readable link and get its ID - var response = await _api.GetInvite(inviteId).ConfigureAwait(false); - await _api.DeleteInvite(response.Code).ConfigureAwait(false); - } - catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } - } - - /// Gets more info about the provided invite code. - /// Supported formats: inviteCode, xkcdCode, https://discord.gg/inviteCode, https://discord.gg/xkcdCode - public async Task GetInvite(string inviteIdOrXkcd) - { - CheckReady(); - if (inviteIdOrXkcd == null) throw new ArgumentNullException(nameof(inviteIdOrXkcd)); - - var response = await _api.GetInvite(inviteIdOrXkcd).ConfigureAwait(false); - var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id); - invite.Update(response); - return invite; - } - - /// Accepts the provided invite. - public Task AcceptInvite(Invite invite) - { - CheckReady(); - if (invite == null) throw new ArgumentNullException(nameof(invite)); - - return _api.AcceptInvite(invite.Id); - } - /// Accepts the provided invite. - public async Task AcceptInvite(string inviteId) - { - CheckReady(); - if (inviteId == null) throw new ArgumentNullException(nameof(inviteId)); - - //Remove trailing slash and any non-code url parts - if (inviteId.Length > 0 && inviteId[inviteId.Length - 1] == '/') - inviteId = inviteId.Substring(0, inviteId.Length - 1); - int index = inviteId.LastIndexOf('/'); - if (index >= 0) - inviteId = inviteId.Substring(index + 1); - - //Check if this is a human-readable link and get its ID - var invite = await GetInvite(inviteId).ConfigureAwait(false); - await _api.AcceptInvite(invite.Id).ConfigureAwait(false); - } - - //Members - public Task EditMember(Member member, bool? mute = null, bool? deaf = null, IEnumerable roles = null) - => EditMember(member?.ServerId, member?.UserId, mute, deaf, roles); - public Task EditMember(Server server, User user, bool? mute = null, bool? deaf = null, IEnumerable roles = null) - => EditMember(server?.Id, user?.Id, mute, deaf, roles); - public Task EditMember(Server server, string userId, bool? mute = null, bool? deaf = null, IEnumerable roles = null) - => EditMember(server?.Id, userId, mute, deaf, roles); - public Task EditMember(string serverId, User user, bool? mute = null, bool? deaf = null, IEnumerable roles = null) - => EditMember(serverId, user?.Id, mute, deaf, roles); - public Task EditMember(string serverId, string userId, bool? mute = null, bool? deaf = null, IEnumerable roles = null) - { - CheckReady(); - if (serverId == null) throw new NullReferenceException(nameof(serverId)); - if (userId == null) throw new NullReferenceException(nameof(userId)); - - var newRoles = CollectionHelper.FlattenRoles(roles); - return _api.EditMember(serverId, userId, mute: mute, deaf: deaf, roles: newRoles); - } - - //Messages - /// Sends a message to the provided channel. To include a mention, see the Mention static helper class. - public Task SendMessage(Channel channel, string text) - => SendMessage(channel, text, MentionHelper.GetUserIds(text), false); - /// Sends a message to the provided channel. To include a mention, see the Mention static helper class. - public Task SendMessage(string channelId, string text) - => SendMessage(_channels[channelId], text, MentionHelper.GetUserIds(text), false); - private async Task SendMessage(Channel channel, string text, IEnumerable mentionedUsers = null, bool isTextToSpeech = false) - { - CheckReady(); - if (channel == null) throw new ArgumentNullException(nameof(channel)); - if (text == null) throw new ArgumentNullException(nameof(text)); - var mentionedUserIds = CollectionHelper.FlattenUsers(mentionedUsers); - - int blockCount = (int)Math.Ceiling(text.Length / (double)MaxMessageSize); - Message[] result = new Message[blockCount]; - for (int i = 0; i < blockCount; i++) - { - int index = i * MaxMessageSize; - string blockText = text.Substring(index, Math.Min(2000, text.Length - index)); - var nonce = GenerateNonce(); - if (Config.UseMessageQueue) - { - var msg = _messages.GetOrAdd("nonce_" + nonce, channel.Id, CurrentUserId); - var currentUser = msg.User; - msg.Update(new MessageInfo - { - Content = blockText, - Timestamp = DateTime.UtcNow, - Author = new UserReference { Avatar = currentUser.AvatarId, Discriminator = currentUser.Discriminator, Id = CurrentUserId, Username = currentUser.Name }, - ChannelId = channel.Id, - IsTextToSpeech = isTextToSpeech - }); - msg.IsQueued = true; - msg.Nonce = nonce; - result[i] = msg; - _pendingMessages.Enqueue(msg); - } - else - { - var model = await _api.SendMessage(channel.Id, blockText, mentionedUserIds, nonce, isTextToSpeech).ConfigureAwait(false); - var msg = _messages.GetOrAdd(model.Id, channel.Id, model.Author.Id); - msg.Update(model); - RaiseMessageSent(msg); - result[i] = msg; - } - await Task.Delay(1000).ConfigureAwait(false); - } - return result; - } - - /// Sends a private message to the provided user. - public Task SendPrivateMessage(Member member, string text) - => SendPrivateMessage(member?.UserId, text); - /// Sends a private message to the provided user. - public Task SendPrivateMessage(User user, string text) - => SendPrivateMessage(user?.Id, text); - /// Sends a private message to the provided user. - public async Task SendPrivateMessage(string userId, string text) - { - var channel = await CreatePMChannel(userId).ConfigureAwait(false); - return await SendMessage(channel, text, new string[0]).ConfigureAwait(false); - } - - /// Sends a file to the provided channel. - public Task SendFile(Channel channel, string filePath) - => SendFile(channel?.Id, filePath); - /// Sends a file to the provided channel. - public Task SendFile(string channelId, string filePath) - { - CheckReady(); - if (channelId == null) throw new ArgumentNullException(nameof(channelId)); - if (filePath == null) throw new ArgumentNullException(nameof(filePath)); - - return _api.SendFile(channelId, filePath); - } - - /// Edits the provided message, changing only non-null attributes. - /// While not required, it is recommended to include a mention reference in the text (see Mention.User). - public Task EditMessage(Message message, string text = null, IEnumerable mentionedUsers = null) - => EditMessage(message?.ChannelId, message?.Id, text, mentionedUsers); - /// Edits the provided message, changing only non-null attributes. - /// While not required, it is recommended to include a mention reference in the text (see Mention.User). - public Task EditMessage(Channel channel, string messageId, string text = null, IEnumerable mentionedUsers = null) - => EditMessage(channel?.Id, messageId, text, mentionedUsers); - /// Edits the provided message, changing only non-null attributes. - /// While not required, it is recommended to include a mention reference in the text (see Mention.User). - public async Task EditMessage(string channelId, string messageId, string text = null, IEnumerable mentionedUsers = null) - { - CheckReady(); - if (channelId == null) throw new ArgumentNullException(nameof(channelId)); - if (messageId == null) throw new ArgumentNullException(nameof(messageId)); - var mentionedUserIds = CollectionHelper.FlattenUsers(mentionedUsers); - - if (text != null && text.Length > MaxMessageSize) - text = text.Substring(0, MaxMessageSize); - - var model = await _api.EditMessage(messageId, channelId, text, mentionedUserIds).ConfigureAwait(false); - var msg = _messages[messageId]; - if (msg != null) - msg.Update(model); - } - - /// Deletes the provided message. - public Task DeleteMessage(Message msg) - => DeleteMessage(msg?.ChannelId, msg?.Id); - /// Deletes the provided message. - public async Task DeleteMessage(string channelId, string msgId) - { - CheckReady(); - if (channelId == null) throw new ArgumentNullException(nameof(channelId)); - if (msgId == null) throw new ArgumentNullException(nameof(msgId)); - - try - { - await _api.DeleteMessage(msgId, channelId).ConfigureAwait(false); - _messages.TryRemove(msgId); - } - catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } - } - public async Task DeleteMessages(IEnumerable msgs) - { - CheckReady(); - if (msgs == null) throw new ArgumentNullException(nameof(msgs)); - - foreach (var msg in msgs) - { - try - { - await _api.DeleteMessage(msg.Id, msg.ChannelId).ConfigureAwait(false); - } - catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } - } - } - public async Task DeleteMessages(string channelId, IEnumerable msgIds) - { - CheckReady(); - if (msgIds == null) throw new ArgumentNullException(nameof(msgIds)); - - foreach (var msgId in msgIds) - { - try - { - await _api.DeleteMessage(msgId, channelId).ConfigureAwait(false); - } - catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } - } - } - - /// Downloads last count messages from the server, starting at beforeMessageId if it's provided. - public Task DownloadMessages(Channel channel, int count, string beforeMessageId = null, bool cache = true) - => DownloadMessages(channel.Id, count, beforeMessageId, cache); - /// Downloads last count messages from the server, starting at beforeMessageId if it's provided. - public async Task DownloadMessages(string channelId, int count, string beforeMessageId = null, bool cache = true) - { - CheckReady(); - if (channelId == null) throw new NullReferenceException(nameof(channelId)); - if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); - if (count == 0) return new Message[0]; - - Channel channel = _channels[channelId]; - if (channel != null && channel.Type == ChannelTypes.Text) - { - try - { - var msgs = await _api.GetMessages(channel.Id, count).ConfigureAwait(false); - return msgs.Select(x => - { - Message msg; - if (cache) - msg = _messages.GetOrAdd(x.Id, x.ChannelId, x.Author.Id); - else - msg = _messages[x.Id] ?? new Message(this, x.Id, x.ChannelId, x.Author.Id); - if (msg != null) - { - msg.Update(x); - if (Config.TrackActivity) - { - /*if (channel.IsPrivate) - { - var user = msg.User; - if (user != null) - user.UpdateActivity(msg.EditedTimestamp ?? msg.Timestamp); - } - else*/ - if (!channel.IsPrivate) - { - var member = msg.Member; - if (member != null) - member.UpdateActivity(msg.EditedTimestamp ?? msg.Timestamp); - } - } - } - return msg; - }) - .ToArray(); - } - catch (HttpException) { } //Bad Permissions? - } - return null; - } - - //Permissions - public Task SetChannelUserPermissions(Channel channel, Member member, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) - => SetChannelPermissions(channel, member?.UserId, PermissionTarget.Member, allow, deny); - public Task SetChannelUserPermissions(string channelId, Member member, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) - => SetChannelPermissions(_channels[channelId], member?.UserId, PermissionTarget.Member, allow, deny); - public Task SetChannelUserPermissions(Channel channel, User user, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) - => SetChannelPermissions(channel, user?.Id, PermissionTarget.Member, allow, deny); - public Task SetChannelUserPermissions(string channelId, User user, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) - => SetChannelPermissions(_channels[channelId], user?.Id, PermissionTarget.Member, allow, deny); - public Task SetChannelUserPermissions(Channel channel, string userId, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) - => SetChannelPermissions(channel, userId, PermissionTarget.Member, allow, deny); - public Task SetChannelUserPermissions(string channelId, string userId, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) - => SetChannelPermissions(_channels[channelId], userId, PermissionTarget.Member, allow, deny); - - public Task SetChannelRolePermissions(Channel channel, Role role, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) - => SetChannelPermissions(channel, role?.Id, PermissionTarget.Role, allow, deny); - public Task SetChannelRolePermissions(string channelId, Role role, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) - => SetChannelPermissions(_channels[channelId], role?.Id, PermissionTarget.Role, allow, deny); - public Task SetChannelRolePermissions(Channel channel, string userId, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) - => SetChannelPermissions(channel, userId, PermissionTarget.Role, allow, deny); - public Task SetChannelRolePermissions(string channelId, string userId, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) - => SetChannelPermissions(_channels[channelId], userId, PermissionTarget.Role, allow, deny); - - private async Task SetChannelPermissions(Channel channel, string targetId, string targetType, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) - { - CheckReady(); - if (channel == null) throw new NullReferenceException(nameof(channel)); - if (targetId == null) throw new NullReferenceException(nameof(targetId)); - if (targetType == null) throw new NullReferenceException(nameof(targetType)); - - uint allowValue = allow?.RawValue ?? 0; - uint denyValue = deny?.RawValue ?? 0; - bool changed = false; - - var perms = channel.PermissionOverwrites.Where(x => x.TargetType != targetType || x.TargetId != targetId).FirstOrDefault(); - if (allowValue != 0 || denyValue != 0) - { - await _api.SetChannelPermissions(channel.Id, targetId, targetType, allowValue, denyValue); - if (perms != null) - { - perms.Allow.SetRawValueInternal(allowValue); - perms.Deny.SetRawValueInternal(denyValue); - } - else - { - var oldPerms = channel._permissionOverwrites; - var newPerms = new Channel.PermissionOverwrite[oldPerms.Length + 1]; - Array.Copy(oldPerms, newPerms, oldPerms.Length); - newPerms[oldPerms.Length] = new Channel.PermissionOverwrite(targetType, targetId, allowValue, denyValue); - channel._permissionOverwrites = newPerms; - } - changed = true; - } - else - { - try - { - await _api.DeleteChannelPermissions(channel.Id, targetId); - if (perms != null) - { - channel._permissionOverwrites = channel.PermissionOverwrites.Where(x => x.TargetType != targetType || x.TargetId != targetId).ToArray(); - changed = true; - } - } - catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } - } - - if (changed) - { - if (targetType == PermissionTarget.Role) - channel.InvalidatePermissionsCache(); - else if (targetType == PermissionTarget.Member) - channel.InvalidatePermissionsCache(targetId); - } - } - - public Task RemoveChannelUserPermissions(Channel channel, Member member) - => RemoveChannelPermissions(channel, member?.UserId, PermissionTarget.Member); - public Task RemoveChannelUserPermissions(string channelId, Member member) - => RemoveChannelPermissions(_channels[channelId], member?.UserId, PermissionTarget.Member); - public Task RemoveChannelUserPermissions(Channel channel, User user) - => RemoveChannelPermissions(channel, user?.Id, PermissionTarget.Member); - public Task RemoveChannelUserPermissions(string channelId, User user) - => RemoveChannelPermissions(_channels[channelId], user?.Id, PermissionTarget.Member); - public Task RemoveChannelUserPermissions(Channel channel, string userId) - => RemoveChannelPermissions(channel, userId, PermissionTarget.Member); - public Task RemoveChannelUserPermissions(string channelId, string userId) - => RemoveChannelPermissions(_channels[channelId], userId, PermissionTarget.Member); - - public Task RemoveChannelRolePermissions(Channel channel, Role role) - => RemoveChannelPermissions(channel, role?.Id, PermissionTarget.Role); - public Task RemoveChannelRolePermissions(string channelId, Role role) - => RemoveChannelPermissions(_channels[channelId], role?.Id, PermissionTarget.Role); - public Task RemoveChannelRolePermissions(Channel channel, string roleId) - => RemoveChannelPermissions(channel, roleId, PermissionTarget.Role); - public Task RemoveChannelRolePermissions(string channelId, string roleId) - => RemoveChannelPermissions(_channels[channelId], roleId, PermissionTarget.Role); - - private async Task RemoveChannelPermissions(Channel channel, string userOrRoleId, string idType) - { - CheckReady(); - if (channel == null) throw new NullReferenceException(nameof(channel)); - if (userOrRoleId == null) throw new NullReferenceException(nameof(userOrRoleId)); - if (idType == null) throw new NullReferenceException(nameof(idType)); - - try - { - var perms = channel.PermissionOverwrites.Where(x => x.TargetType != idType || x.TargetId != userOrRoleId).FirstOrDefault(); - await _api.DeleteChannelPermissions(channel.Id, userOrRoleId).ConfigureAwait(false); - if (perms != null) - { - channel.PermissionOverwrites.Where(x => x.TargetType != idType || x.TargetId != userOrRoleId).ToArray(); - - if (idType == PermissionTarget.Role) - channel.InvalidatePermissionsCache(); - else if (idType == PermissionTarget.Member) - channel.InvalidatePermissionsCache(userOrRoleId); - } - } - catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } - } - - //Profile - public Task EditProfile(string currentPassword = "", - string username = null, string email = null, string password = null, - ImageType avatarType = ImageType.Png, byte[] avatar = null) - { - if (currentPassword == null) throw new ArgumentNullException(nameof(currentPassword)); - - return _api.EditUser(currentPassword: currentPassword, username: username ?? _currentUser?.Name, email: email ?? _currentUser?.Email, password: password, - avatarType: avatarType, avatar: avatar); - } - public Task SetStatus(string status) - { - if (status != UserStatus.Online && status != UserStatus.Idle) - throw new ArgumentException($"Invalid status, must be {UserStatus.Online} or {UserStatus.Idle}"); - _status = status; - return SendStatus(); - } - public Task SetGame(int? gameId) - { - _gameId = gameId; - return SendStatus(); - } - private Task SendStatus() - { - _dataSocket.SendStatus(_status == UserStatus.Idle ? EpochTime.GetMilliseconds() - (10 * 60 * 1000) : (ulong?)null, _gameId); - return TaskHelper.CompletedTask; - } - - //Roles - /// Note: due to current API limitations, the created role cannot be returned. - public Task CreateRole(Server server, string name) - => CreateRole(server?.Id, name); - /// Note: due to current API limitations, the created role cannot be returned. - public async Task CreateRole(string serverId, string name) - { - CheckReady(); - if (serverId == null) throw new NullReferenceException(nameof(serverId)); - - var response = await _api.CreateRole(serverId).ConfigureAwait(false); - var role = _roles.GetOrAdd(response.Id, serverId); - role.Update(response); - - await EditRole(role, name: name); - - return role; - } - - public Task EditRole(Role role, string name = null, PackedServerPermissions permissions = null, PackedColor color = null, bool? hoist = null, int? position = null) - => EditRole(role.ServerId, role.Id, name: name, permissions: permissions, color: color, hoist: hoist, position: position); - public async Task EditRole(string serverId, string roleId, string name = null, PackedServerPermissions permissions = null, PackedColor color = null, bool? hoist = null, int? position = null) - { - CheckReady(); - if (serverId == null) throw new NullReferenceException(nameof(serverId)); - if (roleId == null) throw new NullReferenceException(nameof(roleId)); - - var response = await _api.EditRole(serverId, roleId, name: name, - permissions: permissions?.RawValue, color: color?.RawValue, hoist: hoist); - - var role = _roles[response.Id]; - if (role != null) - role.Update(response); - - if (position != null) - { - int oldPos = role.Position; - int newPos = position.Value; - int minPos; - Role[] roles = role.Server.Roles.OrderBy(x => x.Position).ToArray(); - - if (oldPos < newPos) //Moving Down - { - minPos = oldPos; - for (int i = oldPos; i < newPos; i++) - roles[i] = roles[i + 1]; - roles[newPos] = role; - } - else //(oldPos > newPos) Moving Up - { - minPos = newPos; - for (int i = oldPos; i > newPos; i--) - roles[i] = roles[i - 1]; - roles[newPos] = role; - } - await _api.ReorderRoles(role.ServerId, roles.Skip(minPos).Select(x => x.Id), minPos); - } - } - - public Task DeleteRole(Role role) - => DeleteRole(role?.ServerId, role?.Id); - public Task DeleteRole(string serverId, string roleId) - { - CheckReady(); - if (serverId == null) throw new NullReferenceException(nameof(serverId)); - if (roleId == null) throw new NullReferenceException(nameof(roleId)); - - return _api.DeleteRole(serverId, roleId); - } - - public Task ReorderRoles(Server server, IEnumerable roles, int startPos = 0) - => ReorderChannels(server.Id, roles, startPos); - public Task ReorderRoles(string serverId, IEnumerable roles, int startPos = 0) - { - if (serverId == null) throw new ArgumentNullException(nameof(serverId)); - if (roles == null) throw new ArgumentNullException(nameof(roles)); - if (startPos < 0) throw new ArgumentOutOfRangeException(nameof(startPos), "startPos must be a positive integer."); - - var roleIds = roles.Select(x => - { - if (x is string) - return x as string; - else if (x is Role) - return (x as Role).Id; - else - throw new ArgumentException("Channels must be a collection of string or Role.", nameof(roles)); - }); - - return _api.ReorderRoles(serverId, roleIds, startPos); - } - - //Servers - /// Creates a new server with the provided name and region (see Regions). - public async Task CreateServer(string name, string region) - { - CheckReady(); - if (name == null) throw new ArgumentNullException(nameof(name)); - if (region == null) throw new ArgumentNullException(nameof(region)); - - var response = await _api.CreateServer(name, region).ConfigureAwait(false); - var server = _servers.GetOrAdd(response.Id); - server.Update(response); - return server; - } - - /// Edits the provided server, changing only non-null attributes. - public Task EditServer(string serverId, string name = null, string region = null, ImageType iconType = ImageType.Png, byte[] icon = null) - => EditServer(_servers[serverId], name: name, region: region, iconType: iconType, icon: icon); - /// Edits the provided server, changing only non-null attributes. - public async Task EditServer(Server server, string name = null, string region = null, ImageType iconType = ImageType.Png, byte[] icon = null) - { - CheckReady(); - if (server == null) throw new ArgumentNullException(nameof(server)); - - var response = await _api.EditServer(server.Id, name: name ?? server.Name, region: region, iconType: iconType, icon: icon); - server.Update(response); - } - - /// Leaves the provided server, destroying it if you are the owner. - public Task LeaveServer(Server server) - => LeaveServer(server?.Id); - /// Leaves the provided server, destroying it if you are the owner. - public async Task LeaveServer(string serverId) - { - CheckReady(); - if (serverId == null) throw new ArgumentNullException(nameof(serverId)); - - try { await _api.LeaveServer(serverId).ConfigureAwait(false); } - catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } - return _servers.TryRemove(serverId); - } - } -} \ No newline at end of file diff --git a/src/Discord.Net/DiscordClient.Bans.cs b/src/Discord.Net/DiscordClient.Bans.cs new file mode 100644 index 000000000..2c0d98ad0 --- /dev/null +++ b/src/Discord.Net/DiscordClient.Bans.cs @@ -0,0 +1,68 @@ +using Discord.Net; +using System; +using System.Net; +using System.Threading.Tasks; + +namespace Discord +{ + public partial class DiscordClient + { + public event EventHandler BanAdded; + private void RaiseBanAdded(string userId, Server server) + { + if (BanAdded != null) + RaiseEvent(nameof(BanAdded), () => BanAdded(this, new BanEventArgs(_users[userId], userId, server))); + } + public event EventHandler BanRemoved; + private void RaiseBanRemoved(string userId, Server server) + { + if (BanRemoved != null) + RaiseEvent(nameof(BanRemoved), () => BanRemoved(this, new BanEventArgs(_users[userId], userId, server))); + } + + /// Bans a user from the provided server. + public Task Ban(Member member) + => Ban(member?.ServerId, member?.UserId); + /// Bans a user from the provided server. + public Task Ban(Server server, User user) + => Ban(server?.Id, user?.Id); + /// Bans a user from the provided server. + public Task Ban(Server server, string userId) + => Ban(server?.Id, userId); + /// Bans a user from the provided server. + public Task Ban(string server, User user) + => Ban(server, user?.Id); + /// Bans a user from the provided server. + public Task Ban(string serverId, string userId) + { + CheckReady(); + if (serverId == null) throw new ArgumentNullException(nameof(serverId)); + if (userId == null) throw new ArgumentNullException(nameof(userId)); + + return _api.Ban(serverId, userId); + } + + /// Unbans a user from the provided server. + public Task Unban(Member member) + => Unban(member?.ServerId, member?.UserId); + /// Unbans a user from the provided server. + public Task Unban(Server server, User user) + => Unban(server?.Id, user?.Id); + /// Unbans a user from the provided server. + public Task Unban(Server server, string userId) + => Unban(server?.Id, userId); + /// Unbans a user from the provided server. + public Task Unban(string server, User user) + => Unban(server, user?.Id); + /// Unbans a user from the provided server. + public async Task Unban(string serverId, string userId) + { + CheckReady(); + if (serverId == null) throw new ArgumentNullException(nameof(serverId)); + if (userId == null) throw new ArgumentNullException(nameof(userId)); + + try { await _api.Unban(serverId, userId).ConfigureAwait(false); } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } + } + } +} \ No newline at end of file diff --git a/src/Discord.Net/DiscordClient.Cache.cs b/src/Discord.Net/DiscordClient.Cache.cs deleted file mode 100644 index ea72e881f..000000000 --- a/src/Discord.Net/DiscordClient.Cache.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System.Collections.Generic; - -namespace Discord -{ - public partial class DiscordClient - { - /// Returns the channel with the specified id, or null if none was found. - public Channel GetChannel(string id) => _channels[id]; - /// Returns all channels with the specified server and name. - /// Name formats supported: Name and #Name. Search is case-insensitive. - public IEnumerable FindChannels(Server server, string name, string type = null) => _channels.Find(server?.Id, name, type); - /// Returns all channels with the specified server and name. - /// Name formats supported: Name and #Name. Search is case-insensitive. - public IEnumerable FindChannels(string serverId, string name, string type = null) => _channels.Find(serverId, name, type); - - /// Returns the user with the specified id, along with their server-specific data, or null if none was found. - public Member GetMember(string serverId, User user) => _members[user?.Id, serverId]; - /// Returns the user with the specified id, along with their server-specific data, or null if none was found. - public Member GetMember(Server server, User user) => _members[user?.Id, server?.Id]; - /// Returns the user with the specified id, along with their server-specific data, or null if none was found. - public Member GetMember(Server server, string userId) => _members[userId, server?.Id]; - /// Returns the user with the specified id, along with their server-specific data, or null if none was found. - public Member GetMember(string serverId, string userId) => _members[userId, serverId]; - /// Returns all users in with the specified server and name, along with their server-specific data. - /// Name formats supported: Name and @Name. Search is case-insensitive. - public IEnumerable FindMembers(Server server, string name) => _members.Find(server, name); - /// Returns all users in with the specified server and name, along with their server-specific data. - /// Name formats supported: Name and @Name. Search is case-insensitive. - public IEnumerable FindMembers(string serverId, string name) => _members.Find(_servers[serverId], name); - - /// Returns the message with the specified id, or null if none was found. - public Message GetMessage(string id) => _messages[id]; - - /// Returns the role with the specified id, or null if none was found. - public Role GetRole(string id) => _roles[id]; - /// Returns all roles with the specified server and name. - /// Name formats supported: Name and @Name. Search is case-insensitive. - public IEnumerable FindRoles(Server server, string name) => _roles.Find(server?.Id, name); - /// Returns all roles with the specified server and name. - /// Name formats supported: Name and @Name. Search is case-insensitive. - public IEnumerable FindRoles(string serverId, string name) => _roles.Find(serverId, name); - - /// Returns the server with the specified id, or null if none was found. - public Server GetServer(string id) => _servers[id]; - /// Returns all servers with the specified name. - /// Search is case-insensitive. - public IEnumerable FindServers(string name) => _servers.Find(name); - - /// Returns the user with the specified id, or null if none was found. - public User GetUser(string id) => _users[id]; - /// Returns the user with the specified name and discriminator, or null if none was found. - /// Name formats supported: Name and @Name. Search is case-insensitive. - public User GetUser(string name, string discriminator) => _members[name, discriminator]?.User; - /// Returns all users with the specified name across all servers. - /// Name formats supported: Name and @Name. Search is case-insensitive. - public IEnumerable FindUsers(string name) => _users.Find(name); - - } -} \ No newline at end of file diff --git a/src/Discord.Net/DiscordClient.Channels.cs b/src/Discord.Net/DiscordClient.Channels.cs new file mode 100644 index 000000000..fa1902d7f --- /dev/null +++ b/src/Discord.Net/DiscordClient.Channels.cs @@ -0,0 +1,182 @@ +using Discord.Collections; +using Discord.Net; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; + +namespace Discord +{ + public sealed class ChannelEventArgs : EventArgs + { + public Channel Channel { get; } + public string ChannelId => Channel.Id; + public Server Server => Channel.Server; + public string ServerId => Channel.ServerId; + + internal ChannelEventArgs(Channel channel) { Channel = channel; } + } + + public partial class DiscordClient + { + /// Returns a collection of all channels this client is a member of. + public Channels Channels => _channels; + private readonly Channels _channels; + + public event EventHandler ChannelCreated; + private void RaiseChannelCreated(Channel channel) + { + if (ChannelCreated != null) + RaiseEvent(nameof(ChannelCreated), () => ChannelCreated(this, new ChannelEventArgs(channel))); + } + public event EventHandler ChannelDestroyed; + private void RaiseChannelDestroyed(Channel channel) + { + if (ChannelDestroyed != null) + RaiseEvent(nameof(ChannelDestroyed), () => ChannelDestroyed(this, new ChannelEventArgs(channel))); + } + public event EventHandler ChannelUpdated; + private void RaiseChannelUpdated(Channel channel) + { + if (ChannelUpdated != null) + RaiseEvent(nameof(ChannelUpdated), () => ChannelUpdated(this, new ChannelEventArgs(channel))); + } + + + /// Returns the channel with the specified id, or null if none was found. + public Channel GetChannel(string id) => _channels[id]; + /// Returns all channels with the specified server and name. + /// Name formats supported: Name and #Name. Search is case-insensitive. + public IEnumerable FindChannels(Server server, string name, string type = null) => FindChannels(server?.Id, name, type); + /// Returns all channels with the specified server and name. + /// Name formats supported: Name and #Name. Search is case-insensitive. + public IEnumerable FindChannels(string serverId, string name, string type = null) + { + if (serverId == null) throw new ArgumentNullException(nameof(serverId)); + + IEnumerable result; + if (name.StartsWith("#")) + { + string name2 = name.Substring(1); + result = _channels.Where(x => x.ServerId == serverId && + string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) || + string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); + } + else + { + result = _channels.Where(x => x.ServerId == serverId && + string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); + } + + if (type != null) + result = result.Where(x => x.Type == type); + + return result; + } + + /// Creates a new channel with the provided name and type (see ChannelTypes). + public Task CreateChannel(Server server, string name, string type = ChannelTypes.Text) + => CreateChannel(server?.Id, name, type); + /// Creates a new channel with the provided name and type (see ChannelTypes). + public async Task CreateChannel(string serverId, string name, string type = ChannelTypes.Text) + { + CheckReady(); + if (serverId == null) throw new ArgumentNullException(nameof(serverId)); + if (name == null) throw new ArgumentNullException(nameof(name)); + if (type == null) throw new ArgumentNullException(nameof(type)); + + var response = await _api.CreateChannel(serverId, name, type).ConfigureAwait(false); + var channel = _channels.GetOrAdd(response.Id, response.GuildId, response.Recipient?.Id); + channel.Update(response); + return channel; + } + + /// Returns the private channel with the provided user, creating one if it does not currently exist. + public Task CreatePMChannel(string userId) => CreatePMChannel(_users[userId], userId); + /// Returns the private channel with the provided user, creating one if it does not currently exist. + public Task CreatePMChannel(User user) => CreatePMChannel(user, user?.Id); + /// Returns the private channel with the provided user, creating one if it does not currently exist. + public Task CreatePMChannel(Member member) => CreatePMChannel(member.User, member.UserId); + private async Task CreatePMChannel(User user, string userId) + { + CheckReady(); + if (userId == null) throw new ArgumentNullException(nameof(userId)); + + Channel channel = null; + if (user != null) + channel = user.PrivateChannel; + if (channel == null) + { + var response = await _api.CreatePMChannel(CurrentUserId, userId).ConfigureAwait(false); + user = _users.GetOrAdd(response.Recipient?.Id); + user.Update(response.Recipient); + channel = _channels.GetOrAdd(response.Id, response.GuildId, response.Recipient?.Id); + channel.Update(response); + } + return channel; + } + + /// Edits the provided channel, changing only non-null attributes. + public Task EditChannel(string channelId, string name = null, string topic = null, int? position = null) + => EditChannel(_channels[channelId], name: name, topic: topic, position: position); + /// Edits the provided channel, changing only non-null attributes. + public async Task EditChannel(Channel channel, string name = null, string topic = null, int? position = null) + { + CheckReady(); + if (channel == null) throw new ArgumentNullException(nameof(channel)); + + await _api.EditChannel(channel.Id, name: name, topic: topic); + + if (position != null) + { + int oldPos = channel.Position; + int newPos = position.Value; + int minPos; + Channel[] channels = channel.Server.Channels.OrderBy(x => x.Position).ToArray(); + + if (oldPos < newPos) //Moving Down + { + minPos = oldPos; + for (int i = oldPos; i < newPos; i++) + channels[i] = channels[i + 1]; + channels[newPos] = channel; + } + else //(oldPos > newPos) Moving Up + { + minPos = newPos; + for (int i = oldPos; i > newPos; i--) + channels[i] = channels[i - 1]; + channels[newPos] = channel; + } + await _api.ReorderChannels(channel.ServerId, channels.Skip(minPos).Select(x => x.Id), minPos); + } + } + + public Task ReorderChannels(Server server, IEnumerable channels, int startPos = 0) + => ReorderChannels(server.Id, channels, startPos); + public Task ReorderChannels(string serverId, IEnumerable channels, int startPos = 0) + { + if (serverId == null) throw new ArgumentNullException(nameof(serverId)); + if (channels == null) throw new ArgumentNullException(nameof(channels)); + if (startPos < 0) throw new ArgumentOutOfRangeException(nameof(startPos), "startPos must be a positive integer."); + + var channelIds = CollectionHelper.FlattenChannels(channels); + return _api.ReorderChannels(serverId, channelIds, startPos); + } + + /// Destroys the provided channel. + public Task DestroyChannel(Channel channel) + => DestroyChannel(channel?.Id); + /// Destroys the provided channel. + public async Task DestroyChannel(string channelId) + { + CheckReady(); + if (channelId == null) throw new ArgumentNullException(nameof(channelId)); + + try { await _api.DestroyChannel(channelId).ConfigureAwait(false); } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } + return _channels.TryRemove(channelId); + } + } +} \ No newline at end of file diff --git a/src/Discord.Net/DiscordClient.Events.cs b/src/Discord.Net/DiscordClient.Events.cs deleted file mode 100644 index 9b7f8e712..000000000 --- a/src/Discord.Net/DiscordClient.Events.cs +++ /dev/null @@ -1,278 +0,0 @@ -using System; - -namespace Discord -{ - public sealed class ServerEventArgs : EventArgs - { - public Server Server { get; } - public string ServerId => Server.Id; - - internal ServerEventArgs(Server server) { Server = server; } - } - public sealed class ChannelEventArgs : EventArgs - { - public Channel Channel { get; } - public string ChannelId => Channel.Id; - public Server Server => Channel.Server; - public string ServerId => Channel.ServerId; - - internal ChannelEventArgs(Channel channel) { Channel = channel; } - } - public sealed class UserEventArgs : EventArgs - { - public User User { get; } - public string UserId => User.Id; - - internal UserEventArgs(User user) { User = user; } - } - public sealed class MessageEventArgs : EventArgs - { - public Message Message { get; } - public string MessageId => Message.Id; - public Member Member => Message.Member; - public Channel Channel => Message.Channel; - public string ChannelId => Message.ChannelId; - public Server Server => Message.Server; - public string ServerId => Message.ServerId; - public User User => Member.User; - public string UserId => Message.UserId; - - internal MessageEventArgs(Message msg) { Message = msg; } - } - public sealed class RoleEventArgs : EventArgs - { - public Role Role { get; } - public string RoleId => Role.Id; - public Server Server => Role.Server; - public string ServerId => Role.ServerId; - - internal RoleEventArgs(Role role) { Role = role; } - } - public sealed class BanEventArgs : EventArgs - { - public User User { get; } - public string UserId { get; } - public Server Server { get; } - public string ServerId => Server.Id; - - internal BanEventArgs(User user, string userId, Server server) - { - User = user; - UserId = userId; - Server = server; - } - } - public sealed class MemberEventArgs : EventArgs - { - public Member Member { get; } - public User User => Member.User; - public string UserId => Member.UserId; - public Server Server => Member.Server; - public string ServerId => Member.ServerId; - - internal MemberEventArgs(Member member) { Member = member; } - } - public sealed class UserTypingEventArgs : EventArgs - { - public Channel Channel { get; } - public string ChannelId => Channel.Id; - public Server Server => Channel.Server; - public string ServerId => Channel.ServerId; - public User User { get; } - public string UserId => User.Id; - - internal UserTypingEventArgs(User user, Channel channel) - { - User = user; - Channel = channel; - } - } - public sealed class UserIsSpeakingEventArgs : EventArgs - { - public Channel Channel => Member.VoiceChannel; - public string ChannelId => Member.VoiceChannelId; - public Server Server => Member.Server; - public string ServerId => Member.ServerId; - public User User => Member.User; - public string UserId => Member.UserId; - public Member Member { get; } - public bool IsSpeaking { get; } - - internal UserIsSpeakingEventArgs(Member member, bool isSpeaking) - { - Member = member; - IsSpeaking = isSpeaking; - } - } - - public partial class DiscordClient - { - //Server - public event EventHandler ServerCreated; - private void RaiseServerCreated(Server server) - { - if (ServerCreated != null) - RaiseEvent(nameof(ServerCreated), () => ServerCreated(this, new ServerEventArgs(server))); - } - public event EventHandler ServerDestroyed; - private void RaiseServerDestroyed(Server server) - { - if (ServerDestroyed != null) - RaiseEvent(nameof(ServerDestroyed), () => ServerDestroyed(this, new ServerEventArgs(server))); - } - public event EventHandler ServerUpdated; - private void RaiseServerUpdated(Server server) - { - if (ServerUpdated != null) - RaiseEvent(nameof(ServerUpdated), () => ServerUpdated(this, new ServerEventArgs(server))); - } - public event EventHandler ServerUnavailable; - private void RaiseServerUnavailable(Server server) - { - if (ServerUnavailable != null) - RaiseEvent(nameof(ServerUnavailable), () => ServerUnavailable(this, new ServerEventArgs(server))); - } - public event EventHandler ServerAvailable; - private void RaiseServerAvailable(Server server) - { - if (ServerAvailable != null) - RaiseEvent(nameof(ServerAvailable), () => ServerAvailable(this, new ServerEventArgs(server))); - } - - //Channel - public event EventHandler ChannelCreated; - private void RaiseChannelCreated(Channel channel) - { - if (ChannelCreated != null) - RaiseEvent(nameof(ChannelCreated), () => ChannelCreated(this, new ChannelEventArgs(channel))); - } - public event EventHandler ChannelDestroyed; - private void RaiseChannelDestroyed(Channel channel) - { - if (ChannelDestroyed != null) - RaiseEvent(nameof(ChannelDestroyed), () => ChannelDestroyed(this, new ChannelEventArgs(channel))); - } - public event EventHandler ChannelUpdated; - private void RaiseChannelUpdated(Channel channel) - { - if (ChannelUpdated != null) - RaiseEvent(nameof(ChannelUpdated), () => ChannelUpdated(this, new ChannelEventArgs(channel))); - } - - //Message - public event EventHandler MessageCreated; - private void RaiseMessageCreated(Message msg) - { - if (MessageCreated != null) - RaiseEvent(nameof(MessageCreated), () => MessageCreated(this, new MessageEventArgs(msg))); - } - public event EventHandler MessageDeleted; - private void RaiseMessageDeleted(Message msg) - { - if (MessageDeleted != null) - RaiseEvent(nameof(MessageDeleted), () => MessageDeleted(this, new MessageEventArgs(msg))); - } - public event EventHandler MessageUpdated; - private void RaiseMessageUpdated(Message msg) - { - if (MessageUpdated != null) - RaiseEvent(nameof(MessageUpdated), () => MessageUpdated(this, new MessageEventArgs(msg))); - } - public event EventHandler MessageReadRemotely; - private void RaiseMessageReadRemotely(Message msg) - { - if (MessageReadRemotely != null) - RaiseEvent(nameof(MessageReadRemotely), () => MessageReadRemotely(this, new MessageEventArgs(msg))); - } - public event EventHandler MessageSent; - private void RaiseMessageSent(Message msg) - { - if (MessageSent != null) - RaiseEvent(nameof(MessageSent), () => MessageSent(this, new MessageEventArgs(msg))); - } - - //Role - public event EventHandler RoleCreated; - private void RaiseRoleCreated(Role role) - { - if (RoleCreated != null) - RaiseEvent(nameof(RoleCreated), () => RoleCreated(this, new RoleEventArgs(role))); - } - public event EventHandler RoleUpdated; - private void RaiseRoleDeleted(Role role) - { - if (RoleDeleted != null) - RaiseEvent(nameof(RoleDeleted), () => RoleDeleted(this, new RoleEventArgs(role))); - } - public event EventHandler RoleDeleted; - private void RaiseRoleUpdated(Role role) - { - if (RoleUpdated != null) - RaiseEvent(nameof(RoleUpdated), () => RoleUpdated(this, new RoleEventArgs(role))); - } - - //Ban - public event EventHandler BanAdded; - private void RaiseBanAdded(string userId, Server server) - { - if (BanAdded != null) - RaiseEvent(nameof(BanAdded), () => BanAdded(this, new BanEventArgs(_users[userId], userId, server))); - } - public event EventHandler BanRemoved; - private void RaiseBanRemoved(string userId, Server server) - { - if (BanRemoved != null) - RaiseEvent(nameof(BanRemoved), () => BanRemoved(this, new BanEventArgs(_users[userId], userId, server))); - } - - //User - public event EventHandler UserAdded; - private void RaiseUserAdded(Member member) - { - if (UserAdded != null) - RaiseEvent(nameof(UserAdded), () => UserAdded(this, new MemberEventArgs(member))); - } - public event EventHandler UserRemoved; - private void RaiseUserRemoved(Member member) - { - if (UserRemoved != null) - RaiseEvent(nameof(UserRemoved), () => UserRemoved(this, new MemberEventArgs(member))); - } - public event EventHandler UserUpdated; - private void RaiseUserUpdated(User user) - { - if (UserUpdated != null) - RaiseEvent(nameof(UserUpdated), () => UserUpdated(this, new UserEventArgs(user))); - } - public event EventHandler MemberUpdated; - private void RaiseMemberUpdated(Member member) - { - if (MemberUpdated != null) - RaiseEvent(nameof(MemberUpdated), () => MemberUpdated(this, new MemberEventArgs(member))); - } - public event EventHandler UserPresenceUpdated; - private void RaiseUserPresenceUpdated(Member member) - { - if (UserPresenceUpdated != null) - RaiseEvent(nameof(UserPresenceUpdated), () => UserPresenceUpdated(this, new MemberEventArgs(member))); - } - public event EventHandler UserVoiceStateUpdated; - private void RaiseUserVoiceStateUpdated(Member member) - { - if (UserVoiceStateUpdated != null) - RaiseEvent(nameof(UserVoiceStateUpdated), () => UserVoiceStateUpdated(this, new MemberEventArgs(member))); - } - public event EventHandler UserIsTyping; - private void RaiseUserIsTyping(User user, Channel channel) - { - if (UserIsTyping != null) - RaiseEvent(nameof(UserIsTyping), () => UserIsTyping(this, new UserTypingEventArgs(user, channel))); - } - public event EventHandler UserIsSpeaking; - private void RaiseUserIsSpeaking(Member member, bool isSpeaking) - { - if (UserIsSpeaking != null) - RaiseEvent(nameof(UserIsSpeaking), () => UserIsSpeaking(this, new UserIsSpeakingEventArgs(member, isSpeaking))); - } - } -} \ No newline at end of file diff --git a/src/Discord.Net/DiscordClient.Invites.cs b/src/Discord.Net/DiscordClient.Invites.cs new file mode 100644 index 000000000..115d9b7ce --- /dev/null +++ b/src/Discord.Net/DiscordClient.Invites.cs @@ -0,0 +1,96 @@ +using Discord.Net; +using System; +using System.Net; +using System.Threading.Tasks; + +namespace Discord +{ + public partial class DiscordClient + { + /// Creates a new invite to the default channel of the provided server. + /// Time (in seconds) until the invite expires. Set to 0 to never expire. + /// 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 0. + /// The max amount of times this invite may be used. Set to 0 to have unlimited uses. + public Task CreateInvite(Server server, int maxAge = 1800, int maxUses = 0, bool tempMembership = false, bool hasXkcd = false) + => CreateInvite(server?.DefaultChannelId, maxAge, maxUses, tempMembership, hasXkcd); + /// Creates a new invite to the provided channel. + /// Time (in seconds) until the invite expires. Set to 0 to never expire. + /// 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 0. + /// The max amount of times this invite may be used. Set to 0 to have unlimited uses. + public Task CreateInvite(Channel channel, int maxAge = 1800, int maxUses = 0, bool tempMembership = false, bool hasXkcd = false) + => CreateInvite(channel?.Id, maxAge, maxUses, tempMembership, hasXkcd); + /// Creates a new invite to the provided channel. + /// Time (in seconds) until the invite expires. Set to 0 to never expire. + /// 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 0. + /// The max amount of times this invite may be used. Set to 0 to have unlimited uses. + public async Task CreateInvite(string serverOrChannelId, int maxAge = 1800, int maxUses = 0, bool tempMembership = false, bool hasXkcd = false) + { + CheckReady(); + if (serverOrChannelId == null) throw new ArgumentNullException(nameof(serverOrChannelId)); + if (maxAge <= 0) throw new ArgumentOutOfRangeException(nameof(maxAge)); + if (maxUses <= 0) throw new ArgumentOutOfRangeException(nameof(maxUses)); + + var response = await _api.CreateInvite(serverOrChannelId, maxAge, maxUses, tempMembership, hasXkcd).ConfigureAwait(false); + var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id); + invite.Update(response); + return invite; + } + + /// Deletes the provided invite. + public async Task DestroyInvite(string inviteId) + { + CheckReady(); + if (inviteId == null) throw new ArgumentNullException(nameof(inviteId)); + + try + { + //Check if this is a human-readable link and get its ID + var response = await _api.GetInvite(inviteId).ConfigureAwait(false); + await _api.DeleteInvite(response.Code).ConfigureAwait(false); + } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } + } + + /// Gets more info about the provided invite code. + /// Supported formats: inviteCode, xkcdCode, https://discord.gg/inviteCode, https://discord.gg/xkcdCode + public async Task GetInvite(string inviteIdOrXkcd) + { + CheckReady(); + if (inviteIdOrXkcd == null) throw new ArgumentNullException(nameof(inviteIdOrXkcd)); + + var response = await _api.GetInvite(inviteIdOrXkcd).ConfigureAwait(false); + var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id); + invite.Update(response); + return invite; + } + + /// Accepts the provided invite. + public Task AcceptInvite(Invite invite) + { + CheckReady(); + if (invite == null) throw new ArgumentNullException(nameof(invite)); + + return _api.AcceptInvite(invite.Id); + } + /// Accepts the provided invite. + public async Task AcceptInvite(string inviteId) + { + CheckReady(); + if (inviteId == null) throw new ArgumentNullException(nameof(inviteId)); + + //Remove trailing slash and any non-code url parts + if (inviteId.Length > 0 && inviteId[inviteId.Length - 1] == '/') + inviteId = inviteId.Substring(0, inviteId.Length - 1); + int index = inviteId.LastIndexOf('/'); + if (index >= 0) + inviteId = inviteId.Substring(index + 1); + + //Check if this is a human-readable link and get its ID + var invite = await GetInvite(inviteId).ConfigureAwait(false); + await _api.AcceptInvite(invite.Id).ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/src/Discord.Net/DiscordClient.Members.cs b/src/Discord.Net/DiscordClient.Members.cs new file mode 100644 index 000000000..562deb744 --- /dev/null +++ b/src/Discord.Net/DiscordClient.Members.cs @@ -0,0 +1,135 @@ +using Discord.Collections; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Discord +{ + public sealed class MemberTypingEventArgs : EventArgs + { + public Channel Channel { get; } + public string ChannelId => Channel.Id; + public Server Server => Channel.Server; + public string ServerId => Channel.ServerId; + public Member Member { get; } + public string UserId => User.Id; + public User User => Member.User; + + internal MemberTypingEventArgs(Member member, Channel channel) + { + Member = member; + Channel = channel; + } + } + + public sealed class MemberIsSpeakingEventArgs : EventArgs + { + public Channel Channel => Member.VoiceChannel; + public string ChannelId => Member.VoiceChannelId; + public Server Server => Member.Server; + public string ServerId => Member.ServerId; + public User User => Member.User; + public string UserId => Member.UserId; + public Member Member { get; } + public bool IsSpeaking { get; } + + internal MemberIsSpeakingEventArgs(Member member, bool isSpeaking) + { + Member = member; + IsSpeaking = isSpeaking; + } + } + + public partial class DiscordClient + { + public event EventHandler UserIsTyping; + private void RaiseUserIsTyping(Member member, Channel channel) + { + if (UserIsTyping != null) + RaiseEvent(nameof(UserIsTyping), () => UserIsTyping(this, new MemberTypingEventArgs(member, channel))); + } + public event EventHandler UserIsSpeaking; + private void RaiseUserIsSpeaking(Member member, bool isSpeaking) + { + if (UserIsSpeaking != null) + RaiseEvent(nameof(UserIsSpeaking), () => UserIsSpeaking(this, new MemberIsSpeakingEventArgs(member, isSpeaking))); + } + + /// Returns a collection of all user-server pairs this client can currently see. + public Members Members => _members; + private readonly Members _members; + + /// Returns the user with the specified id, along with their server-specific data, or null if none was found. + public Member GetMember(Server server, User user) => _members[user?.Id, server?.Id]; + /// Returns the user with the specified id, along with their server-specific data, or null if none was found. + public Member GetMember(Server server, string userId) => _members[userId, server?.Id]; + /// Returns the user with the specified id, along with their server-specific data, or null if none was found. + public Member GetMember(string serverId, User user) => _members[user?.Id, serverId]; + /// Returns the user with the specified id, along with their server-specific data, or null if none was found. + public Member GetMember(string serverId, string userId) => _members[userId, serverId]; + /// Returns the user with the specified name and discriminator, along withtheir server-specific data, or null if they couldn't be found. + /// Name formats supported: Name and @Name. Search is case-insensitive. + public Member GetMember(Server server, string username, string discriminator) + => GetMember(server?.Id, username, discriminator); + /// Returns the user with the specified name and discriminator, along withtheir server-specific data, or null if they couldn't be found. + /// Name formats supported: Name and @Name. Search is case-insensitive. + public Member GetMember(string serverId, string username, string discriminator) + { + User user = GetUser(username, discriminator); + return _members[user?.Id, serverId]; + } + + /// Returns all users in with the specified server and name, along with their server-specific data. + /// Name formats supported: Name and @Name. Search is case-insensitive. + public IEnumerable FindMembers(string serverId, string name) => FindMembers(_servers[serverId], name); + /// Returns all users in with the specified server and name, along with their server-specific data. + /// Name formats supported: Name and @Name. Search is case-insensitive. + public IEnumerable FindMembers(Server server, string name) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + if (name == null) throw new ArgumentNullException(nameof(name)); + + if (name.StartsWith("@")) + { + string name2 = name.Substring(1); + return server.Members.Where(x => + { + var user = x.User; + if (user == null) + return false; + return string.Equals(user.Name, name, StringComparison.OrdinalIgnoreCase) || + string.Equals(user.Name, name2, StringComparison.OrdinalIgnoreCase); + }); + } + else + { + return server.Members.Where(x => + { + var user = x.User; + if (user == null) + return false; + return string.Equals(x.User.Name, name, StringComparison.OrdinalIgnoreCase); + }); + } + } + + public Task EditMember(Member member, bool? mute = null, bool? deaf = null, IEnumerable roles = null) + => EditMember(member?.ServerId, member?.UserId, mute, deaf, roles); + public Task EditMember(Server server, User user, bool? mute = null, bool? deaf = null, IEnumerable roles = null) + => EditMember(server?.Id, user?.Id, mute, deaf, roles); + public Task EditMember(Server server, string userId, bool? mute = null, bool? deaf = null, IEnumerable roles = null) + => EditMember(server?.Id, userId, mute, deaf, roles); + public Task EditMember(string serverId, User user, bool? mute = null, bool? deaf = null, IEnumerable roles = null) + => EditMember(serverId, user?.Id, mute, deaf, roles); + public Task EditMember(string serverId, string userId, bool? mute = null, bool? deaf = null, IEnumerable roles = null) + { + CheckReady(); + if (serverId == null) throw new NullReferenceException(nameof(serverId)); + if (userId == null) throw new NullReferenceException(nameof(userId)); + + var newRoles = CollectionHelper.FlattenRoles(roles); + return _api.EditMember(serverId, userId, mute: mute, deaf: deaf, roles: newRoles); + } + } +} \ No newline at end of file diff --git a/src/Discord.Net/DiscordClient.Messages.cs b/src/Discord.Net/DiscordClient.Messages.cs new file mode 100644 index 000000000..697233c85 --- /dev/null +++ b/src/Discord.Net/DiscordClient.Messages.cs @@ -0,0 +1,296 @@ +using Discord.API; +using Discord.Collections; +using Discord.Net; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; + +namespace Discord +{ + public partial class DiscordClient + { + public const int MaxMessageSize = 2000; + + /// Returns a collection of all messages this client has seen since logging in and currently has in cache. + public Messages Messages => _messages; + private readonly Messages _messages; + + public event EventHandler MessageCreated; + private void RaiseMessageCreated(Message msg) + { + if (MessageCreated != null) + RaiseEvent(nameof(MessageCreated), () => MessageCreated(this, new MessageEventArgs(msg))); + } + public event EventHandler MessageDeleted; + private void RaiseMessageDeleted(Message msg) + { + if (MessageDeleted != null) + RaiseEvent(nameof(MessageDeleted), () => MessageDeleted(this, new MessageEventArgs(msg))); + } + public event EventHandler MessageUpdated; + private void RaiseMessageUpdated(Message msg) + { + if (MessageUpdated != null) + RaiseEvent(nameof(MessageUpdated), () => MessageUpdated(this, new MessageEventArgs(msg))); + } + public event EventHandler MessageReadRemotely; + private void RaiseMessageReadRemotely(Message msg) + { + if (MessageReadRemotely != null) + RaiseEvent(nameof(MessageReadRemotely), () => MessageReadRemotely(this, new MessageEventArgs(msg))); + } + public event EventHandler MessageSent; + private void RaiseMessageSent(Message msg) + { + if (MessageSent != null) + RaiseEvent(nameof(MessageSent), () => MessageSent(this, new MessageEventArgs(msg))); + } + + /// Returns the message with the specified id, or null if none was found. + public Message GetMessage(string id) => _messages[id]; + + /// Sends a message to the provided channel. To include a mention, see the Mention static helper class. + public Task SendMessage(Channel channel, string text) + => SendMessage(channel, text, MentionHelper.GetUserIds(text), false); + /// Sends a message to the provided channel. To include a mention, see the Mention static helper class. + public Task SendMessage(string channelId, string text) + => SendMessage(_channels[channelId], text, MentionHelper.GetUserIds(text), false); + private async Task SendMessage(Channel channel, string text, IEnumerable mentionedUsers = null, bool isTextToSpeech = false) + { + CheckReady(); + if (channel == null) throw new ArgumentNullException(nameof(channel)); + if (text == null) throw new ArgumentNullException(nameof(text)); + var mentionedUserIds = CollectionHelper.FlattenUsers(mentionedUsers); + + int blockCount = (int)Math.Ceiling(text.Length / (double)MaxMessageSize); + Message[] result = new Message[blockCount]; + for (int i = 0; i < blockCount; i++) + { + int index = i * MaxMessageSize; + string blockText = text.Substring(index, Math.Min(2000, text.Length - index)); + var nonce = GenerateNonce(); + if (Config.UseMessageQueue) + { + var msg = _messages.GetOrAdd("nonce_" + nonce, channel.Id, CurrentUserId); + var currentUser = msg.User; + msg.Update(new MessageInfo + { + Content = blockText, + Timestamp = DateTime.UtcNow, + Author = new UserReference { Avatar = currentUser.AvatarId, Discriminator = currentUser.Discriminator, Id = CurrentUserId, Username = currentUser.Name }, + ChannelId = channel.Id, + IsTextToSpeech = isTextToSpeech + }); + msg.IsQueued = true; + msg.Nonce = nonce; + result[i] = msg; + _pendingMessages.Enqueue(msg); + } + else + { + var model = await _api.SendMessage(channel.Id, blockText, mentionedUserIds, nonce, isTextToSpeech).ConfigureAwait(false); + var msg = _messages.GetOrAdd(model.Id, channel.Id, model.Author.Id); + msg.Update(model); + RaiseMessageSent(msg); + result[i] = msg; + } + await Task.Delay(1000).ConfigureAwait(false); + } + return result; + } + + /// Sends a private message to the provided user. + public Task SendPrivateMessage(Member member, string text) + => SendPrivateMessage(member?.UserId, text); + /// Sends a private message to the provided user. + public Task SendPrivateMessage(User user, string text) + => SendPrivateMessage(user?.Id, text); + /// Sends a private message to the provided user. + public async Task SendPrivateMessage(string userId, string text) + { + var channel = await CreatePMChannel(userId).ConfigureAwait(false); + return await SendMessage(channel, text, new string[0]).ConfigureAwait(false); + } + + /// Sends a file to the provided channel. + public Task SendFile(Channel channel, string filePath) + => SendFile(channel?.Id, filePath); + /// Sends a file to the provided channel. + public Task SendFile(string channelId, string filePath) + { + CheckReady(); + if (channelId == null) throw new ArgumentNullException(nameof(channelId)); + if (filePath == null) throw new ArgumentNullException(nameof(filePath)); + + return _api.SendFile(channelId, filePath); + } + + /// Edits the provided message, changing only non-null attributes. + /// While not required, it is recommended to include a mention reference in the text (see Mention.User). + public Task EditMessage(Message message, string text = null, IEnumerable mentionedUsers = null) + => EditMessage(message?.ChannelId, message?.Id, text, mentionedUsers); + /// Edits the provided message, changing only non-null attributes. + /// While not required, it is recommended to include a mention reference in the text (see Mention.User). + public Task EditMessage(Channel channel, string messageId, string text = null, IEnumerable mentionedUsers = null) + => EditMessage(channel?.Id, messageId, text, mentionedUsers); + /// Edits the provided message, changing only non-null attributes. + /// While not required, it is recommended to include a mention reference in the text (see Mention.User). + public async Task EditMessage(string channelId, string messageId, string text = null, IEnumerable mentionedUsers = null) + { + CheckReady(); + if (channelId == null) throw new ArgumentNullException(nameof(channelId)); + if (messageId == null) throw new ArgumentNullException(nameof(messageId)); + var mentionedUserIds = CollectionHelper.FlattenUsers(mentionedUsers); + + if (text != null && text.Length > MaxMessageSize) + text = text.Substring(0, MaxMessageSize); + + var model = await _api.EditMessage(messageId, channelId, text, mentionedUserIds).ConfigureAwait(false); + var msg = _messages[messageId]; + if (msg != null) + msg.Update(model); + } + + /// Deletes the provided message. + public Task DeleteMessage(Message msg) + => DeleteMessage(msg?.ChannelId, msg?.Id); + /// Deletes the provided message. + public async Task DeleteMessage(string channelId, string msgId) + { + CheckReady(); + if (channelId == null) throw new ArgumentNullException(nameof(channelId)); + if (msgId == null) throw new ArgumentNullException(nameof(msgId)); + + try + { + await _api.DeleteMessage(msgId, channelId).ConfigureAwait(false); + _messages.TryRemove(msgId); + } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } + } + public async Task DeleteMessages(IEnumerable msgs) + { + CheckReady(); + if (msgs == null) throw new ArgumentNullException(nameof(msgs)); + + foreach (var msg in msgs) + { + try + { + await _api.DeleteMessage(msg.Id, msg.ChannelId).ConfigureAwait(false); + } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } + } + } + public async Task DeleteMessages(string channelId, IEnumerable msgIds) + { + CheckReady(); + if (msgIds == null) throw new ArgumentNullException(nameof(msgIds)); + + foreach (var msgId in msgIds) + { + try + { + await _api.DeleteMessage(msgId, channelId).ConfigureAwait(false); + } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } + } + } + + /// Downloads last count messages from the server, starting at beforeMessageId if it's provided. + public Task DownloadMessages(Channel channel, int count, string beforeMessageId = null, bool cache = true) + => DownloadMessages(channel.Id, count, beforeMessageId, cache); + /// Downloads last count messages from the server, starting at beforeMessageId if it's provided. + public async Task DownloadMessages(string channelId, int count, string beforeMessageId = null, bool cache = true) + { + CheckReady(); + if (channelId == null) throw new NullReferenceException(nameof(channelId)); + if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); + if (count == 0) return new Message[0]; + + Channel channel = _channels[channelId]; + if (channel != null && channel.Type == ChannelTypes.Text) + { + try + { + var msgs = await _api.GetMessages(channel.Id, count).ConfigureAwait(false); + return msgs.Select(x => + { + Message msg; + if (cache) + msg = _messages.GetOrAdd(x.Id, x.ChannelId, x.Author.Id); + else + msg = _messages[x.Id] ?? new Message(this, x.Id, x.ChannelId, x.Author.Id); + if (msg != null) + { + msg.Update(x); + if (Config.TrackActivity) + { + /*if (channel.IsPrivate) + { + var user = msg.User; + if (user != null) + user.UpdateActivity(msg.EditedTimestamp ?? msg.Timestamp); + } + else*/ + if (!channel.IsPrivate) + { + var member = msg.Member; + if (member != null) + member.UpdateActivity(msg.EditedTimestamp ?? msg.Timestamp); + } + } + } + return msg; + }) + .ToArray(); + } + catch (HttpException) { } //Bad Permissions? + } + return null; + } + + private Task MessageQueueLoop() + { + var cancelToken = CancelToken; + int interval = Config.MessageQueueInterval; + + return Task.Run(async () => + { + Message msg; + while (!cancelToken.IsCancellationRequested) + { + while (_pendingMessages.TryDequeue(out msg)) + { + bool hasFailed = false; + SendMessageResponse response = null; + try + { + response = await _api.SendMessage(msg.ChannelId, msg.RawText, msg.MentionIds, msg.Nonce, msg.IsTTS).ConfigureAwait(false); + } + catch (WebException) { break; } + catch (HttpException) { hasFailed = true; } + + if (!hasFailed) + { + _messages.Remap(msg.Id, response.Id); + msg.Id = response.Id; + msg.Update(response); + } + msg.IsQueued = false; + msg.HasFailed = hasFailed; + RaiseMessageSent(msg); + } + await Task.Delay(interval).ConfigureAwait(false); + } + }); + } + private string GenerateNonce() + { + lock (_rand) + return _rand.Next().ToString(); + } + } +} \ No newline at end of file diff --git a/src/Discord.Net/DiscordClient.Permissions.cs b/src/Discord.Net/DiscordClient.Permissions.cs new file mode 100644 index 000000000..e6b46b8ac --- /dev/null +++ b/src/Discord.Net/DiscordClient.Permissions.cs @@ -0,0 +1,132 @@ +using Discord.Net; +using System; +using System.Linq; +using System.Net; +using System.Threading.Tasks; + +namespace Discord +{ + public partial class DiscordClient + { + public Task SetChannelUserPermissions(Channel channel, Member member, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) + => SetChannelPermissions(channel, member?.UserId, PermissionTarget.Member, allow, deny); + public Task SetChannelUserPermissions(string channelId, Member member, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) + => SetChannelPermissions(_channels[channelId], member?.UserId, PermissionTarget.Member, allow, deny); + public Task SetChannelUserPermissions(Channel channel, User user, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) + => SetChannelPermissions(channel, user?.Id, PermissionTarget.Member, allow, deny); + public Task SetChannelUserPermissions(string channelId, User user, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) + => SetChannelPermissions(_channels[channelId], user?.Id, PermissionTarget.Member, allow, deny); + public Task SetChannelUserPermissions(Channel channel, string userId, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) + => SetChannelPermissions(channel, userId, PermissionTarget.Member, allow, deny); + public Task SetChannelUserPermissions(string channelId, string userId, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) + => SetChannelPermissions(_channels[channelId], userId, PermissionTarget.Member, allow, deny); + + public Task SetChannelRolePermissions(Channel channel, Role role, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) + => SetChannelPermissions(channel, role?.Id, PermissionTarget.Role, allow, deny); + public Task SetChannelRolePermissions(string channelId, Role role, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) + => SetChannelPermissions(_channels[channelId], role?.Id, PermissionTarget.Role, allow, deny); + public Task SetChannelRolePermissions(Channel channel, string userId, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) + => SetChannelPermissions(channel, userId, PermissionTarget.Role, allow, deny); + public Task SetChannelRolePermissions(string channelId, string userId, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) + => SetChannelPermissions(_channels[channelId], userId, PermissionTarget.Role, allow, deny); + + private async Task SetChannelPermissions(Channel channel, string targetId, string targetType, PackedChannelPermissions allow = null, PackedChannelPermissions deny = null) + { + CheckReady(); + if (channel == null) throw new NullReferenceException(nameof(channel)); + if (targetId == null) throw new NullReferenceException(nameof(targetId)); + if (targetType == null) throw new NullReferenceException(nameof(targetType)); + + uint allowValue = allow?.RawValue ?? 0; + uint denyValue = deny?.RawValue ?? 0; + bool changed = false; + + var perms = channel.PermissionOverwrites.Where(x => x.TargetType != targetType || x.TargetId != targetId).FirstOrDefault(); + if (allowValue != 0 || denyValue != 0) + { + await _api.SetChannelPermissions(channel.Id, targetId, targetType, allowValue, denyValue); + if (perms != null) + { + perms.Allow.SetRawValueInternal(allowValue); + perms.Deny.SetRawValueInternal(denyValue); + } + else + { + var oldPerms = channel._permissionOverwrites; + var newPerms = new Channel.PermissionOverwrite[oldPerms.Length + 1]; + Array.Copy(oldPerms, newPerms, oldPerms.Length); + newPerms[oldPerms.Length] = new Channel.PermissionOverwrite(targetType, targetId, allowValue, denyValue); + channel._permissionOverwrites = newPerms; + } + changed = true; + } + else + { + try + { + await _api.DeleteChannelPermissions(channel.Id, targetId); + if (perms != null) + { + channel._permissionOverwrites = channel.PermissionOverwrites.Where(x => x.TargetType != targetType || x.TargetId != targetId).ToArray(); + changed = true; + } + } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } + } + + if (changed) + { + if (targetType == PermissionTarget.Role) + channel.InvalidatePermissionsCache(); + else if (targetType == PermissionTarget.Member) + channel.InvalidatePermissionsCache(targetId); + } + } + + public Task RemoveChannelUserPermissions(Channel channel, Member member) + => RemoveChannelPermissions(channel, member?.UserId, PermissionTarget.Member); + public Task RemoveChannelUserPermissions(string channelId, Member member) + => RemoveChannelPermissions(_channels[channelId], member?.UserId, PermissionTarget.Member); + public Task RemoveChannelUserPermissions(Channel channel, User user) + => RemoveChannelPermissions(channel, user?.Id, PermissionTarget.Member); + public Task RemoveChannelUserPermissions(string channelId, User user) + => RemoveChannelPermissions(_channels[channelId], user?.Id, PermissionTarget.Member); + public Task RemoveChannelUserPermissions(Channel channel, string userId) + => RemoveChannelPermissions(channel, userId, PermissionTarget.Member); + public Task RemoveChannelUserPermissions(string channelId, string userId) + => RemoveChannelPermissions(_channels[channelId], userId, PermissionTarget.Member); + + public Task RemoveChannelRolePermissions(Channel channel, Role role) + => RemoveChannelPermissions(channel, role?.Id, PermissionTarget.Role); + public Task RemoveChannelRolePermissions(string channelId, Role role) + => RemoveChannelPermissions(_channels[channelId], role?.Id, PermissionTarget.Role); + public Task RemoveChannelRolePermissions(Channel channel, string roleId) + => RemoveChannelPermissions(channel, roleId, PermissionTarget.Role); + public Task RemoveChannelRolePermissions(string channelId, string roleId) + => RemoveChannelPermissions(_channels[channelId], roleId, PermissionTarget.Role); + + private async Task RemoveChannelPermissions(Channel channel, string userOrRoleId, string idType) + { + CheckReady(); + if (channel == null) throw new NullReferenceException(nameof(channel)); + if (userOrRoleId == null) throw new NullReferenceException(nameof(userOrRoleId)); + if (idType == null) throw new NullReferenceException(nameof(idType)); + + try + { + var perms = channel.PermissionOverwrites.Where(x => x.TargetType != idType || x.TargetId != userOrRoleId).FirstOrDefault(); + await _api.DeleteChannelPermissions(channel.Id, userOrRoleId).ConfigureAwait(false); + if (perms != null) + { + channel.PermissionOverwrites.Where(x => x.TargetType != idType || x.TargetId != userOrRoleId).ToArray(); + + if (idType == PermissionTarget.Role) + channel.InvalidatePermissionsCache(); + else if (idType == PermissionTarget.Member) + channel.InvalidatePermissionsCache(userOrRoleId); + } + } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } + } + } +} \ No newline at end of file diff --git a/src/Discord.Net/DiscordClient.Roles.cs b/src/Discord.Net/DiscordClient.Roles.cs new file mode 100644 index 000000000..4fbdd3cd5 --- /dev/null +++ b/src/Discord.Net/DiscordClient.Roles.cs @@ -0,0 +1,149 @@ +using Discord.Collections; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Discord +{ + public partial class DiscordClient + { + public event EventHandler RoleCreated; + private void RaiseRoleCreated(Role role) + { + if (RoleCreated != null) + RaiseEvent(nameof(RoleCreated), () => RoleCreated(this, new RoleEventArgs(role))); + } + public event EventHandler RoleUpdated; + private void RaiseRoleDeleted(Role role) + { + if (RoleDeleted != null) + RaiseEvent(nameof(RoleDeleted), () => RoleDeleted(this, new RoleEventArgs(role))); + } + public event EventHandler RoleDeleted; + private void RaiseRoleUpdated(Role role) + { + if (RoleUpdated != null) + RaiseEvent(nameof(RoleUpdated), () => RoleUpdated(this, new RoleEventArgs(role))); + } + + /// Returns a collection of all role-server pairs this client can currently see. + public Roles Roles => _roles; + private readonly Roles _roles; + + /// Returns the role with the specified id, or null if none was found. + public Role GetRole(string id) => _roles[id]; + /// Returns all roles with the specified server and name. + /// Name formats supported: Name and @Name. Search is case-insensitive. + public IEnumerable FindRoles(Server server, string name) => FindRoles(server?.Id, name); + /// Returns all roles with the specified server and name. + /// Name formats supported: Name and @Name. Search is case-insensitive. + public IEnumerable FindRoles(string serverId, string name) + { + if (serverId == null) throw new ArgumentNullException(nameof(serverId)); + if (name == null) throw new ArgumentNullException(nameof(name)); + + if (name.StartsWith("@")) + { + string name2 = name.Substring(1); + return _roles.Where(x => x.ServerId == serverId && + string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) || string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); + } + else + { + return _roles.Where(x => x.ServerId == serverId && + string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); + } + } + + /// Note: due to current API limitations, the created role cannot be returned. + public Task CreateRole(Server server, string name) + => CreateRole(server?.Id, name); + /// Note: due to current API limitations, the created role cannot be returned. + public async Task CreateRole(string serverId, string name) + { + CheckReady(); + if (serverId == null) throw new NullReferenceException(nameof(serverId)); + + var response = await _api.CreateRole(serverId).ConfigureAwait(false); + var role = _roles.GetOrAdd(response.Id, serverId); + role.Update(response); + + await EditRole(role, name: name); + + return role; + } + + public Task EditRole(Role role, string name = null, PackedServerPermissions permissions = null, PackedColor color = null, bool? hoist = null, int? position = null) + => EditRole(role.ServerId, role.Id, name: name, permissions: permissions, color: color, hoist: hoist, position: position); + public async Task EditRole(string serverId, string roleId, string name = null, PackedServerPermissions permissions = null, PackedColor color = null, bool? hoist = null, int? position = null) + { + CheckReady(); + if (serverId == null) throw new NullReferenceException(nameof(serverId)); + if (roleId == null) throw new NullReferenceException(nameof(roleId)); + + var response = await _api.EditRole(serverId, roleId, name: name, + permissions: permissions?.RawValue, color: color?.RawValue, hoist: hoist); + + var role = _roles[response.Id]; + if (role != null) + role.Update(response); + + if (position != null) + { + int oldPos = role.Position; + int newPos = position.Value; + int minPos; + Role[] roles = role.Server.Roles.OrderBy(x => x.Position).ToArray(); + + if (oldPos < newPos) //Moving Down + { + minPos = oldPos; + for (int i = oldPos; i < newPos; i++) + roles[i] = roles[i + 1]; + roles[newPos] = role; + } + else //(oldPos > newPos) Moving Up + { + minPos = newPos; + for (int i = oldPos; i > newPos; i--) + roles[i] = roles[i - 1]; + roles[newPos] = role; + } + await _api.ReorderRoles(role.ServerId, roles.Skip(minPos).Select(x => x.Id), minPos); + } + } + + public Task DeleteRole(Role role) + => DeleteRole(role?.ServerId, role?.Id); + public Task DeleteRole(string serverId, string roleId) + { + CheckReady(); + if (serverId == null) throw new NullReferenceException(nameof(serverId)); + if (roleId == null) throw new NullReferenceException(nameof(roleId)); + + return _api.DeleteRole(serverId, roleId); + } + + public Task ReorderRoles(Server server, IEnumerable roles, int startPos = 0) + => ReorderChannels(server.Id, roles, startPos); + public Task ReorderRoles(string serverId, IEnumerable roles, int startPos = 0) + { + if (serverId == null) throw new ArgumentNullException(nameof(serverId)); + if (roles == null) throw new ArgumentNullException(nameof(roles)); + if (startPos < 0) throw new ArgumentOutOfRangeException(nameof(startPos), "startPos must be a positive integer."); + + var roleIds = roles.Select(x => + { + if (x is string) + return x as string; + else if (x is Role) + return (x as Role).Id; + else + throw new ArgumentException("Channels must be a collection of string or Role.", nameof(roles)); + }); + + return _api.ReorderRoles(serverId, roleIds, startPos); + } + } +} \ No newline at end of file diff --git a/src/Discord.Net/DiscordClient.Servers.cs b/src/Discord.Net/DiscordClient.Servers.cs new file mode 100644 index 000000000..ae3c6a5f9 --- /dev/null +++ b/src/Discord.Net/DiscordClient.Servers.cs @@ -0,0 +1,102 @@ +using Discord.Net; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; + +namespace Discord +{ + public sealed class ServerEventArgs : EventArgs + { + public Server Server { get; } + public string ServerId => Server.Id; + + internal ServerEventArgs(Server server) { Server = server; } + } + + public partial class DiscordClient + { + public event EventHandler ServerCreated; + private void RaiseServerCreated(Server server) + { + if (ServerCreated != null) + RaiseEvent(nameof(ServerCreated), () => ServerCreated(this, new ServerEventArgs(server))); + } + public event EventHandler ServerDestroyed; + private void RaiseServerDestroyed(Server server) + { + if (ServerDestroyed != null) + RaiseEvent(nameof(ServerDestroyed), () => ServerDestroyed(this, new ServerEventArgs(server))); + } + public event EventHandler ServerUpdated; + private void RaiseServerUpdated(Server server) + { + if (ServerUpdated != null) + RaiseEvent(nameof(ServerUpdated), () => ServerUpdated(this, new ServerEventArgs(server))); + } + public event EventHandler ServerUnavailable; + private void RaiseServerUnavailable(Server server) + { + if (ServerUnavailable != null) + RaiseEvent(nameof(ServerUnavailable), () => ServerUnavailable(this, new ServerEventArgs(server))); + } + public event EventHandler ServerAvailable; + private void RaiseServerAvailable(Server server) + { + if (ServerAvailable != null) + RaiseEvent(nameof(ServerAvailable), () => ServerAvailable(this, new ServerEventArgs(server))); + } + + /// Returns the server with the specified id, or null if none was found. + public Server GetServer(string id) => _servers[id]; + /// Returns all servers with the specified name. + /// Search is case-insensitive. + public IEnumerable FindServers(string name) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + + return _servers.Where(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); + } + + /// Creates a new server with the provided name and region (see Regions). + public async Task CreateServer(string name, string region) + { + CheckReady(); + if (name == null) throw new ArgumentNullException(nameof(name)); + if (region == null) throw new ArgumentNullException(nameof(region)); + + var response = await _api.CreateServer(name, region).ConfigureAwait(false); + var server = _servers.GetOrAdd(response.Id); + server.Update(response); + return server; + } + + /// Edits the provided server, changing only non-null attributes. + public Task EditServer(string serverId, string name = null, string region = null, ImageType iconType = ImageType.Png, byte[] icon = null) + => EditServer(_servers[serverId], name: name, region: region, iconType: iconType, icon: icon); + /// Edits the provided server, changing only non-null attributes. + public async Task EditServer(Server server, string name = null, string region = null, ImageType iconType = ImageType.Png, byte[] icon = null) + { + CheckReady(); + if (server == null) throw new ArgumentNullException(nameof(server)); + + var response = await _api.EditServer(server.Id, name: name ?? server.Name, region: region, iconType: iconType, icon: icon); + server.Update(response); + } + + /// Leaves the provided server, destroying it if you are the owner. + public Task LeaveServer(Server server) + => LeaveServer(server?.Id); + /// Leaves the provided server, destroying it if you are the owner. + public async Task LeaveServer(string serverId) + { + CheckReady(); + if (serverId == null) throw new ArgumentNullException(nameof(serverId)); + + try { await _api.LeaveServer(serverId).ConfigureAwait(false); } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } + return _servers.TryRemove(serverId); + } + } +} \ No newline at end of file diff --git a/src/Discord.Net/DiscordClient.Users.cs b/src/Discord.Net/DiscordClient.Users.cs new file mode 100644 index 000000000..829f60c80 --- /dev/null +++ b/src/Discord.Net/DiscordClient.Users.cs @@ -0,0 +1,130 @@ +using Discord.API; +using Discord.Collections; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Discord +{ + public sealed class UserEventArgs : EventArgs + { + public User User { get; } + public string UserId => User.Id; + + internal UserEventArgs(User user) { User = user; } + } + + public partial class DiscordClient + { + public event EventHandler UserAdded; + private void RaiseUserAdded(Member member) + { + if (UserAdded != null) + RaiseEvent(nameof(UserAdded), () => UserAdded(this, new MemberEventArgs(member))); + } + public event EventHandler UserRemoved; + private void RaiseUserRemoved(Member member) + { + if (UserRemoved != null) + RaiseEvent(nameof(UserRemoved), () => UserRemoved(this, new MemberEventArgs(member))); + } + public event EventHandler UserUpdated; + private void RaiseUserUpdated(User user) + { + if (UserUpdated != null) + RaiseEvent(nameof(UserUpdated), () => UserUpdated(this, new UserEventArgs(user))); + } + public event EventHandler MemberUpdated; + private void RaiseMemberUpdated(Member member) + { + if (MemberUpdated != null) + RaiseEvent(nameof(MemberUpdated), () => MemberUpdated(this, new MemberEventArgs(member))); + } + public event EventHandler UserPresenceUpdated; + private void RaiseUserPresenceUpdated(Member member) + { + if (UserPresenceUpdated != null) + RaiseEvent(nameof(UserPresenceUpdated), () => UserPresenceUpdated(this, new MemberEventArgs(member))); + } + public event EventHandler UserVoiceStateUpdated; + private void RaiseUserVoiceStateUpdated(Member member) + { + if (UserVoiceStateUpdated != null) + RaiseEvent(nameof(UserVoiceStateUpdated), () => UserVoiceStateUpdated(this, new MemberEventArgs(member))); + } + + /// Returns a collection of all users this client can currently see. + public Users Users => _users; + private readonly Users _users; + /// Returns the current logged-in user. + public User CurrentUser => _currentUser; + private User _currentUser; + + /// Returns the user with the specified id, or null if none was found. + public User GetUser(string id) => _users[id]; + /// Returns the user with the specified name and discriminator, or null if none was found. + /// Name formats supported: Name and @Name. Search is case-insensitive. + public User GetUser(string username, string discriminator) + { + if (username == null) throw new ArgumentNullException(nameof(username)); + if (discriminator == null) throw new ArgumentNullException(nameof(discriminator)); + + if (username.StartsWith("@")) + username = username.Substring(1); + + return _users.Where(x => + string.Equals(x.Name, username, StringComparison.OrdinalIgnoreCase) && + x.Discriminator == discriminator + ) + .FirstOrDefault(); + } + + /// Returns all users with the specified name across all servers. + /// Name formats supported: Name and @Name. Search is case-insensitive. + public IEnumerable FindUsers(string name) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + + if (name.StartsWith("@")) + { + string name2 = name.Substring(1); + return _users.Where(x => + string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) || string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); + } + else + { + return _users.Where(x => + string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); + } + } + + public Task EditProfile(string currentPassword = "", + string username = null, string email = null, string password = null, + ImageType avatarType = ImageType.Png, byte[] avatar = null) + { + if (currentPassword == null) throw new ArgumentNullException(nameof(currentPassword)); + + return _api.EditUser(currentPassword: currentPassword, username: username ?? _currentUser?.Name, email: email ?? _currentUser?.Email, password: password, + avatarType: avatarType, avatar: avatar); + } + + public Task SetStatus(string status) + { + if (status != UserStatus.Online && status != UserStatus.Idle) + throw new ArgumentException($"Invalid status, must be {UserStatus.Online} or {UserStatus.Idle}"); + _status = status; + return SendStatus(); + } + public Task SetGame(int? gameId) + { + _gameId = gameId; + return SendStatus(); + } + private Task SendStatus() + { + _dataSocket.SendStatus(_status == UserStatus.Idle ? EpochTime.GetMilliseconds() - (10 * 60 * 1000) : (ulong?)null, _gameId); + return TaskHelper.CompletedTask; + } + } +} \ No newline at end of file diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index 6fca3fdbe..6244b3962 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -13,6 +13,54 @@ using System.Threading.Tasks; namespace Discord { + public sealed class MessageEventArgs : EventArgs + { + public Message Message { get; } + public string MessageId => Message.Id; + public Member Member => Message.Member; + public Channel Channel => Message.Channel; + public string ChannelId => Message.ChannelId; + public Server Server => Message.Server; + public string ServerId => Message.ServerId; + public User User => Member.User; + public string UserId => Message.UserId; + + internal MessageEventArgs(Message msg) { Message = msg; } + } + public sealed class RoleEventArgs : EventArgs + { + public Role Role { get; } + public string RoleId => Role.Id; + public Server Server => Role.Server; + public string ServerId => Role.ServerId; + + internal RoleEventArgs(Role role) { Role = role; } + } + public sealed class BanEventArgs : EventArgs + { + public User User { get; } + public string UserId { get; } + public Server Server { get; } + public string ServerId => Server.Id; + + internal BanEventArgs(User user, string userId, Server server) + { + User = user; + UserId = userId; + Server = server; + } + } + public sealed class MemberEventArgs : EventArgs + { + public Member Member { get; } + public User User => Member.User; + public string UserId => Member.UserId; + public Server Server => Member.Server; + public string ServerId => Member.ServerId; + + internal MemberEventArgs(Member member) { Member = member; } + } + /// Provides a connection to the DiscordApp service. public partial class DiscordClient : DiscordWSClient { @@ -28,29 +76,10 @@ namespace Discord public new DiscordClientConfig Config => _config as DiscordClientConfig; - /// Returns the current logged-in user. - public User CurrentUser => _currentUser; - private User _currentUser; - - /// Returns a collection of all channels this client is a member of. - public Channels Channels => _channels; - private readonly Channels _channels; - /// Returns a collection of all user-server pairs this client can currently see. - public Members Members => _members; - private readonly Members _members; - /// Returns a collection of all messages this client has seen since logging in and currently has in cache. - public Messages Messages => _messages; - private readonly Messages _messages; - //TODO: Do we need the roles cache? - /// Returns a collection of all role-server pairs this client can currently see. - public Roles Roles => _roles; - private readonly Roles _roles; + /// Returns a collection of all servers this client is a member of. public Servers Servers => _servers; private readonly Servers _servers; - /// Returns a collection of all users this client can currently see. - public Users Users => _users; - private readonly Users _users; /// Initializes a new instance of the DiscordClient class. public DiscordClient(DiscordClientConfig config = null) @@ -69,8 +98,8 @@ namespace Discord _messages = new Messages(this, cacheLock); _roles = new Roles(this, cacheLock); _servers = new Servers(this, cacheLock); - _users = new Users(this, cacheLock); _status = UserStatus.Online; + _users = new Users(this, cacheLock); this.Connected += async (s, e) => { @@ -321,47 +350,6 @@ namespace Discord return base.GetTasks(); } - private Task MessageQueueLoop() - { - var cancelToken = CancelToken; - int interval = Config.MessageQueueInterval; - - return Task.Run(async () => - { - Message msg; - while (!cancelToken.IsCancellationRequested) - { - while (_pendingMessages.TryDequeue(out msg)) - { - bool hasFailed = false; - SendMessageResponse response = null; - try - { - response = await _api.SendMessage(msg.ChannelId, msg.RawText, msg.MentionIds, msg.Nonce, msg.IsTTS).ConfigureAwait(false); - } - catch (WebException) { break; } - catch (HttpException) { hasFailed = true; } - - if (!hasFailed) - { - _messages.Remap(msg.Id, response.Id); - msg.Id = response.Id; - msg.Update(response); - } - msg.IsQueued = false; - msg.HasFailed = hasFailed; - RaiseMessageSent(msg); - } - await Task.Delay(interval).ConfigureAwait(false); - } - }); - } - private string GenerateNonce() - { - lock (_rand) - return _rand.Next().ToString(); - } - internal override async Task OnReceivedEvent(WebSocketEventEventArgs e) { try @@ -656,7 +644,7 @@ namespace Discord { var data = e.Payload.ToObject(_serializer); var channel = _channels[data.ChannelId]; - var user = _users[data.UserId]; + var user = _members[data.UserId, channel.ServerId]; if (user != null) {