| @@ -154,17 +154,35 @@ | |||
| <Compile Include="..\Discord.Net\DiscordAPIClientConfig.cs"> | |||
| <Link>DiscordAPIClientConfig.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\DiscordClient.API.cs"> | |||
| <Link>DiscordClient.API.cs</Link> | |||
| <Compile Include="..\Discord.Net\DiscordClient.Bans.cs"> | |||
| <Link>DiscordClient.Bans.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\DiscordClient.Cache.cs"> | |||
| <Link>DiscordClient.Cache.cs</Link> | |||
| <Compile Include="..\Discord.Net\DiscordClient.Channels.cs"> | |||
| <Link>DiscordClient.Channels.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\DiscordClient.cs"> | |||
| <Link>DiscordClient.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\DiscordClient.Events.cs"> | |||
| <Link>DiscordClient.Events.cs</Link> | |||
| <Compile Include="..\Discord.Net\DiscordClient.Invites.cs"> | |||
| <Link>DiscordClient.Invites.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\DiscordClient.Members.cs"> | |||
| <Link>DiscordClient.Members.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\DiscordClient.Messages.cs"> | |||
| <Link>DiscordClient.Messages.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\DiscordClient.Permissions.cs"> | |||
| <Link>DiscordClient.Permissions.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\DiscordClient.Roles.cs"> | |||
| <Link>DiscordClient.Roles.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\DiscordClient.Servers.cs"> | |||
| <Link>DiscordClient.Servers.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\DiscordClient.Users.cs"> | |||
| <Link>DiscordClient.Users.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\DiscordClientConfig.cs"> | |||
| <Link>DiscordClientConfig.cs</Link> | |||
| @@ -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<TValue> GetEnumerator() | |||
| { | |||
| @@ -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<Channel> Find(string serverId, string name, string type = null) | |||
| internal Channel this[string id] | |||
| { | |||
| if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
| IEnumerable<Channel> 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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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<Member> 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(); | |||
| } | |||
| } | |||
| } | |||
| @@ -1,11 +1,11 @@ | |||
| namespace Discord.Collections | |||
| using System; | |||
| namespace Discord.Collections | |||
| { | |||
| public sealed class Messages : AsyncCollection<Message> | |||
| { | |||
| 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); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -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<Role> 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); | |||
| } | |||
| } | |||
| } | |||
| @@ -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<Server> Find(string name) | |||
| { | |||
| if (name == null) throw new ArgumentNullException(nameof(name)); | |||
| return this.Where(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); | |||
| } | |||
| } | |||
| } | |||
| @@ -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<User> 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); | |||
| } | |||
| } | |||
| } | |||
| @@ -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 | |||
| /// <summary> Bans a user from the provided server. </summary> | |||
| public Task Ban(Member member) | |||
| => Ban(member?.ServerId, member?.UserId); | |||
| /// <summary> Bans a user from the provided server. </summary> | |||
| public Task Ban(Server server, User user) | |||
| => Ban(server?.Id, user?.Id); | |||
| /// <summary> Bans a user from the provided server. </summary> | |||
| public Task Ban(Server server, string userId) | |||
| => Ban(server?.Id, userId); | |||
| /// <summary> Bans a user from the provided server. </summary> | |||
| public Task Ban(string server, User user) | |||
| => Ban(server, user?.Id); | |||
| /// <summary> Bans a user from the provided server. </summary> | |||
| 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); | |||
| } | |||
| /// <summary> Unbans a user from the provided server. </summary> | |||
| public Task Unban(Member member) | |||
| => Unban(member?.ServerId, member?.UserId); | |||
| /// <summary> Unbans a user from the provided server. </summary> | |||
| public Task Unban(Server server, User user) | |||
| => Unban(server?.Id, user?.Id); | |||
| /// <summary> Unbans a user from the provided server. </summary> | |||
| public Task Unban(Server server, string userId) | |||
| => Unban(server?.Id, userId); | |||
| /// <summary> Unbans a user from the provided server. </summary> | |||
| public Task Unban(string server, User user) | |||
| => Unban(server, user?.Id); | |||
| /// <summary> Unbans a user from the provided server. </summary> | |||
| 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 | |||
| /// <summary> Creates a new channel with the provided name and type (see ChannelTypes). </summary> | |||
| public Task<Channel> CreateChannel(Server server, string name, string type = ChannelTypes.Text) | |||
| => CreateChannel(server?.Id, name, type); | |||
| /// <summary> Creates a new channel with the provided name and type (see ChannelTypes). </summary> | |||
| public async Task<Channel> 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; | |||
| } | |||
| /// <summary> Returns the private channel with the provided user, creating one if it does not currently exist. </summary> | |||
| public Task<Channel> CreatePMChannel(string userId) => CreatePMChannel(_users[userId], userId); | |||
| /// <summary> Returns the private channel with the provided user, creating one if it does not currently exist. </summary> | |||
| public Task<Channel> CreatePMChannel(User user) => CreatePMChannel(user, user?.Id); | |||
| /// <summary> Returns the private channel with the provided user, creating one if it does not currently exist. </summary> | |||
| public Task<Channel> CreatePMChannel(Member member) => CreatePMChannel(member.User, member.UserId); | |||
| private async Task<Channel> 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; | |||
| } | |||
| /// <summary> Edits the provided channel, changing only non-null attributes. </summary> | |||
| public Task EditChannel(string channelId, string name = null, string topic = null, int? position = null) | |||
| => EditChannel(_channels[channelId], name: name, topic: topic, position: position); | |||
| /// <summary> Edits the provided channel, changing only non-null attributes. </summary> | |||
| 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<object> channels, int startPos = 0) | |||
| => ReorderChannels(server.Id, channels, startPos); | |||
| public Task ReorderChannels(string serverId, IEnumerable<object> 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); | |||
| } | |||
| /// <summary> Destroys the provided channel. </summary> | |||
| public Task<Channel> DestroyChannel(Channel channel) | |||
| => DestroyChannel(channel?.Id); | |||
| /// <summary> Destroys the provided channel. </summary> | |||
| public async Task<Channel> 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 | |||
| /// <summary> Creates a new invite to the default channel of the provided server. </summary> | |||
| /// <param name="maxAge"> Time (in seconds) until the invite expires. Set to 0 to never expire. </param> | |||
| /// <param name="tempMembership"> If true, a user accepting this invite will be kicked from the server after closing their client. </param> | |||
| /// <param name="hasXkcd"> If true, creates a human-readable link. Not supported if maxAge is set to 0. </param> | |||
| /// <param name="maxUses"> The max amount of times this invite may be used. Set to 0 to have unlimited uses. </param> | |||
| public Task<Invite> CreateInvite(Server server, int maxAge = 1800, int maxUses = 0, bool tempMembership = false, bool hasXkcd = false) | |||
| => CreateInvite(server?.DefaultChannelId, maxAge, maxUses, tempMembership, hasXkcd); | |||
| /// <summary> Creates a new invite to the provided channel. </summary> | |||
| /// <param name="maxAge"> Time (in seconds) until the invite expires. Set to 0 to never expire. </param> | |||
| /// <param name="tempMembership"> If true, a user accepting this invite will be kicked from the server after closing their client. </param> | |||
| /// <param name="hasXkcd"> If true, creates a human-readable link. Not supported if maxAge is set to 0. </param> | |||
| /// <param name="maxUses"> The max amount of times this invite may be used. Set to 0 to have unlimited uses. </param> | |||
| public Task<Invite> CreateInvite(Channel channel, int maxAge = 1800, int maxUses = 0, bool tempMembership = false, bool hasXkcd = false) | |||
| => CreateInvite(channel?.Id, maxAge, maxUses, tempMembership, hasXkcd); | |||
| /// <summary> Creates a new invite to the provided channel. </summary> | |||
| /// <param name="maxAge"> Time (in seconds) until the invite expires. Set to 0 to never expire. </param> | |||
| /// <param name="tempMembership"> If true, a user accepting this invite will be kicked from the server after closing their client. </param> | |||
| /// <param name="hasXkcd"> If true, creates a human-readable link. Not supported if maxAge is set to 0. </param> | |||
| /// <param name="maxUses"> The max amount of times this invite may be used. Set to 0 to have unlimited uses. </param> | |||
| public async Task<Invite> 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; | |||
| } | |||
| /// <summary> Deletes the provided invite. </summary> | |||
| 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) { } | |||
| } | |||
| /// <summary> Gets more info about the provided invite code. </summary> | |||
| /// <remarks> Supported formats: inviteCode, xkcdCode, https://discord.gg/inviteCode, https://discord.gg/xkcdCode </remarks> | |||
| public async Task<Invite> 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; | |||
| } | |||
| /// <summary> Accepts the provided invite. </summary> | |||
| public Task AcceptInvite(Invite invite) | |||
| { | |||
| CheckReady(); | |||
| if (invite == null) throw new ArgumentNullException(nameof(invite)); | |||
| return _api.AcceptInvite(invite.Id); | |||
| } | |||
| /// <summary> Accepts the provided invite. </summary> | |||
| 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<object> roles = null) | |||
| => EditMember(member?.ServerId, member?.UserId, mute, deaf, roles); | |||
| public Task EditMember(Server server, User user, bool? mute = null, bool? deaf = null, IEnumerable<object> roles = null) | |||
| => EditMember(server?.Id, user?.Id, mute, deaf, roles); | |||
| public Task EditMember(Server server, string userId, bool? mute = null, bool? deaf = null, IEnumerable<string> roles = null) | |||
| => EditMember(server?.Id, userId, mute, deaf, roles); | |||
| public Task EditMember(string serverId, User user, bool? mute = null, bool? deaf = null, IEnumerable<object> roles = null) | |||
| => EditMember(serverId, user?.Id, mute, deaf, roles); | |||
| public Task EditMember(string serverId, string userId, bool? mute = null, bool? deaf = null, IEnumerable<object> 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 | |||
| /// <summary> Sends a message to the provided channel. To include a mention, see the Mention static helper class. </summary> | |||
| public Task<Message[]> SendMessage(Channel channel, string text) | |||
| => SendMessage(channel, text, MentionHelper.GetUserIds(text), false); | |||
| /// <summary> Sends a message to the provided channel. To include a mention, see the Mention static helper class. </summary> | |||
| public Task<Message[]> SendMessage(string channelId, string text) | |||
| => SendMessage(_channels[channelId], text, MentionHelper.GetUserIds(text), false); | |||
| private async Task<Message[]> SendMessage(Channel channel, string text, IEnumerable<object> 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; | |||
| } | |||
| /// <summary> Sends a private message to the provided user. </summary> | |||
| public Task<Message[]> SendPrivateMessage(Member member, string text) | |||
| => SendPrivateMessage(member?.UserId, text); | |||
| /// <summary> Sends a private message to the provided user. </summary> | |||
| public Task<Message[]> SendPrivateMessage(User user, string text) | |||
| => SendPrivateMessage(user?.Id, text); | |||
| /// <summary> Sends a private message to the provided user. </summary> | |||
| public async Task<Message[]> SendPrivateMessage(string userId, string text) | |||
| { | |||
| var channel = await CreatePMChannel(userId).ConfigureAwait(false); | |||
| return await SendMessage(channel, text, new string[0]).ConfigureAwait(false); | |||
| } | |||
| /// <summary> Sends a file to the provided channel. </summary> | |||
| public Task SendFile(Channel channel, string filePath) | |||
| => SendFile(channel?.Id, filePath); | |||
| /// <summary> Sends a file to the provided channel. </summary> | |||
| 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); | |||
| } | |||
| /// <summary> Edits the provided message, changing only non-null attributes. </summary> | |||
| /// <remarks> While not required, it is recommended to include a mention reference in the text (see Mention.User). </remarks> | |||
| public Task EditMessage(Message message, string text = null, IEnumerable<object> mentionedUsers = null) | |||
| => EditMessage(message?.ChannelId, message?.Id, text, mentionedUsers); | |||
| /// <summary> Edits the provided message, changing only non-null attributes. </summary> | |||
| /// <remarks> While not required, it is recommended to include a mention reference in the text (see Mention.User). </remarks> | |||
| public Task EditMessage(Channel channel, string messageId, string text = null, IEnumerable<object> mentionedUsers = null) | |||
| => EditMessage(channel?.Id, messageId, text, mentionedUsers); | |||
| /// <summary> Edits the provided message, changing only non-null attributes. </summary> | |||
| /// <remarks> While not required, it is recommended to include a mention reference in the text (see Mention.User). </remarks> | |||
| public async Task EditMessage(string channelId, string messageId, string text = null, IEnumerable<object> 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); | |||
| } | |||
| /// <summary> Deletes the provided message. </summary> | |||
| public Task DeleteMessage(Message msg) | |||
| => DeleteMessage(msg?.ChannelId, msg?.Id); | |||
| /// <summary> Deletes the provided message. </summary> | |||
| 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<Message> 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<string> 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) { } | |||
| } | |||
| } | |||
| /// <summary> Downloads last count messages from the server, starting at beforeMessageId if it's provided. </summary> | |||
| public Task<Message[]> DownloadMessages(Channel channel, int count, string beforeMessageId = null, bool cache = true) | |||
| => DownloadMessages(channel.Id, count, beforeMessageId, cache); | |||
| /// <summary> Downloads last count messages from the server, starting at beforeMessageId if it's provided. </summary> | |||
| public async Task<Message[]> 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<EditUserResponse> 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 | |||
| /// <summary> Note: due to current API limitations, the created role cannot be returned. </summary> | |||
| public Task<Role> CreateRole(Server server, string name) | |||
| => CreateRole(server?.Id, name); | |||
| /// <summary> Note: due to current API limitations, the created role cannot be returned. </summary> | |||
| public async Task<Role> 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<object> roles, int startPos = 0) | |||
| => ReorderChannels(server.Id, roles, startPos); | |||
| public Task ReorderRoles(string serverId, IEnumerable<object> 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 | |||
| /// <summary> Creates a new server with the provided name and region (see Regions). </summary> | |||
| public async Task<Server> 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; | |||
| } | |||
| /// <summary> Edits the provided server, changing only non-null attributes. </summary> | |||
| 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); | |||
| /// <summary> Edits the provided server, changing only non-null attributes. </summary> | |||
| 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); | |||
| } | |||
| /// <summary> Leaves the provided server, destroying it if you are the owner. </summary> | |||
| public Task<Server> LeaveServer(Server server) | |||
| => LeaveServer(server?.Id); | |||
| /// <summary> Leaves the provided server, destroying it if you are the owner. </summary> | |||
| public async Task<Server> 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); | |||
| } | |||
| } | |||
| } | |||
| @@ -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<BanEventArgs> 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<BanEventArgs> BanRemoved; | |||
| private void RaiseBanRemoved(string userId, Server server) | |||
| { | |||
| if (BanRemoved != null) | |||
| RaiseEvent(nameof(BanRemoved), () => BanRemoved(this, new BanEventArgs(_users[userId], userId, server))); | |||
| } | |||
| /// <summary> Bans a user from the provided server. </summary> | |||
| public Task Ban(Member member) | |||
| => Ban(member?.ServerId, member?.UserId); | |||
| /// <summary> Bans a user from the provided server. </summary> | |||
| public Task Ban(Server server, User user) | |||
| => Ban(server?.Id, user?.Id); | |||
| /// <summary> Bans a user from the provided server. </summary> | |||
| public Task Ban(Server server, string userId) | |||
| => Ban(server?.Id, userId); | |||
| /// <summary> Bans a user from the provided server. </summary> | |||
| public Task Ban(string server, User user) | |||
| => Ban(server, user?.Id); | |||
| /// <summary> Bans a user from the provided server. </summary> | |||
| 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); | |||
| } | |||
| /// <summary> Unbans a user from the provided server. </summary> | |||
| public Task Unban(Member member) | |||
| => Unban(member?.ServerId, member?.UserId); | |||
| /// <summary> Unbans a user from the provided server. </summary> | |||
| public Task Unban(Server server, User user) | |||
| => Unban(server?.Id, user?.Id); | |||
| /// <summary> Unbans a user from the provided server. </summary> | |||
| public Task Unban(Server server, string userId) | |||
| => Unban(server?.Id, userId); | |||
| /// <summary> Unbans a user from the provided server. </summary> | |||
| public Task Unban(string server, User user) | |||
| => Unban(server, user?.Id); | |||
| /// <summary> Unbans a user from the provided server. </summary> | |||
| 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) { } | |||
| } | |||
| } | |||
| } | |||
| @@ -1,59 +0,0 @@ | |||
| using System.Collections.Generic; | |||
| namespace Discord | |||
| { | |||
| public partial class DiscordClient | |||
| { | |||
| /// <summary> Returns the channel with the specified id, or null if none was found. </summary> | |||
| public Channel GetChannel(string id) => _channels[id]; | |||
| /// <summary> Returns all channels with the specified server and name. </summary> | |||
| /// <remarks> Name formats supported: Name and #Name. Search is case-insensitive. </remarks> | |||
| public IEnumerable<Channel> FindChannels(Server server, string name, string type = null) => _channels.Find(server?.Id, name, type); | |||
| /// <summary> Returns all channels with the specified server and name. </summary> | |||
| /// <remarks> Name formats supported: Name and #Name. Search is case-insensitive. </remarks> | |||
| public IEnumerable<Channel> FindChannels(string serverId, string name, string type = null) => _channels.Find(serverId, name, type); | |||
| /// <summary> Returns the user with the specified id, along with their server-specific data, or null if none was found. </summary> | |||
| public Member GetMember(string serverId, User user) => _members[user?.Id, serverId]; | |||
| /// <summary> Returns the user with the specified id, along with their server-specific data, or null if none was found. </summary> | |||
| public Member GetMember(Server server, User user) => _members[user?.Id, server?.Id]; | |||
| /// <summary> Returns the user with the specified id, along with their server-specific data, or null if none was found. </summary> | |||
| public Member GetMember(Server server, string userId) => _members[userId, server?.Id]; | |||
| /// <summary> Returns the user with the specified id, along with their server-specific data, or null if none was found. </summary> | |||
| public Member GetMember(string serverId, string userId) => _members[userId, serverId]; | |||
| /// <summary> Returns all users in with the specified server and name, along with their server-specific data. </summary> | |||
| /// <remarks> Name formats supported: Name and @Name. Search is case-insensitive.</remarks> | |||
| public IEnumerable<Member> FindMembers(Server server, string name) => _members.Find(server, name); | |||
| /// <summary> Returns all users in with the specified server and name, along with their server-specific data. </summary> | |||
| /// <remarks> Name formats supported: Name and @Name. Search is case-insensitive.</remarks> | |||
| public IEnumerable<Member> FindMembers(string serverId, string name) => _members.Find(_servers[serverId], name); | |||
| /// <summary> Returns the message with the specified id, or null if none was found. </summary> | |||
| public Message GetMessage(string id) => _messages[id]; | |||
| /// <summary> Returns the role with the specified id, or null if none was found. </summary> | |||
| public Role GetRole(string id) => _roles[id]; | |||
| /// <summary> Returns all roles with the specified server and name. </summary> | |||
| /// <remarks> Name formats supported: Name and @Name. Search is case-insensitive. </remarks> | |||
| public IEnumerable<Role> FindRoles(Server server, string name) => _roles.Find(server?.Id, name); | |||
| /// <summary> Returns all roles with the specified server and name. </summary> | |||
| /// <remarks> Name formats supported: Name and @Name. Search is case-insensitive. </remarks> | |||
| public IEnumerable<Role> FindRoles(string serverId, string name) => _roles.Find(serverId, name); | |||
| /// <summary> Returns the server with the specified id, or null if none was found. </summary> | |||
| public Server GetServer(string id) => _servers[id]; | |||
| /// <summary> Returns all servers with the specified name. </summary> | |||
| /// <remarks> Search is case-insensitive. </remarks> | |||
| public IEnumerable<Server> FindServers(string name) => _servers.Find(name); | |||
| /// <summary> Returns the user with the specified id, or null if none was found. </summary> | |||
| public User GetUser(string id) => _users[id]; | |||
| /// <summary> Returns the user with the specified name and discriminator, or null if none was found. </summary> | |||
| /// <remarks> Name formats supported: Name and @Name. Search is case-insensitive. </remarks> | |||
| public User GetUser(string name, string discriminator) => _members[name, discriminator]?.User; | |||
| /// <summary> Returns all users with the specified name across all servers. </summary> | |||
| /// <remarks> Name formats supported: Name and @Name. Search is case-insensitive. </remarks> | |||
| public IEnumerable<User> FindUsers(string name) => _users.Find(name); | |||
| } | |||
| } | |||
| @@ -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 | |||
| { | |||
| /// <summary> Returns a collection of all channels this client is a member of. </summary> | |||
| public Channels Channels => _channels; | |||
| private readonly Channels _channels; | |||
| public event EventHandler<ChannelEventArgs> ChannelCreated; | |||
| private void RaiseChannelCreated(Channel channel) | |||
| { | |||
| if (ChannelCreated != null) | |||
| RaiseEvent(nameof(ChannelCreated), () => ChannelCreated(this, new ChannelEventArgs(channel))); | |||
| } | |||
| public event EventHandler<ChannelEventArgs> ChannelDestroyed; | |||
| private void RaiseChannelDestroyed(Channel channel) | |||
| { | |||
| if (ChannelDestroyed != null) | |||
| RaiseEvent(nameof(ChannelDestroyed), () => ChannelDestroyed(this, new ChannelEventArgs(channel))); | |||
| } | |||
| public event EventHandler<ChannelEventArgs> ChannelUpdated; | |||
| private void RaiseChannelUpdated(Channel channel) | |||
| { | |||
| if (ChannelUpdated != null) | |||
| RaiseEvent(nameof(ChannelUpdated), () => ChannelUpdated(this, new ChannelEventArgs(channel))); | |||
| } | |||
| /// <summary> Returns the channel with the specified id, or null if none was found. </summary> | |||
| public Channel GetChannel(string id) => _channels[id]; | |||
| /// <summary> Returns all channels with the specified server and name. </summary> | |||
| /// <remarks> Name formats supported: Name and #Name. Search is case-insensitive. </remarks> | |||
| public IEnumerable<Channel> FindChannels(Server server, string name, string type = null) => FindChannels(server?.Id, name, type); | |||
| /// <summary> Returns all channels with the specified server and name. </summary> | |||
| /// <remarks> Name formats supported: Name and #Name. Search is case-insensitive. </remarks> | |||
| public IEnumerable<Channel> FindChannels(string serverId, string name, string type = null) | |||
| { | |||
| if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
| IEnumerable<Channel> 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; | |||
| } | |||
| /// <summary> Creates a new channel with the provided name and type (see ChannelTypes). </summary> | |||
| public Task<Channel> CreateChannel(Server server, string name, string type = ChannelTypes.Text) | |||
| => CreateChannel(server?.Id, name, type); | |||
| /// <summary> Creates a new channel with the provided name and type (see ChannelTypes). </summary> | |||
| public async Task<Channel> 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; | |||
| } | |||
| /// <summary> Returns the private channel with the provided user, creating one if it does not currently exist. </summary> | |||
| public Task<Channel> CreatePMChannel(string userId) => CreatePMChannel(_users[userId], userId); | |||
| /// <summary> Returns the private channel with the provided user, creating one if it does not currently exist. </summary> | |||
| public Task<Channel> CreatePMChannel(User user) => CreatePMChannel(user, user?.Id); | |||
| /// <summary> Returns the private channel with the provided user, creating one if it does not currently exist. </summary> | |||
| public Task<Channel> CreatePMChannel(Member member) => CreatePMChannel(member.User, member.UserId); | |||
| private async Task<Channel> 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; | |||
| } | |||
| /// <summary> Edits the provided channel, changing only non-null attributes. </summary> | |||
| public Task EditChannel(string channelId, string name = null, string topic = null, int? position = null) | |||
| => EditChannel(_channels[channelId], name: name, topic: topic, position: position); | |||
| /// <summary> Edits the provided channel, changing only non-null attributes. </summary> | |||
| 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<object> channels, int startPos = 0) | |||
| => ReorderChannels(server.Id, channels, startPos); | |||
| public Task ReorderChannels(string serverId, IEnumerable<object> 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); | |||
| } | |||
| /// <summary> Destroys the provided channel. </summary> | |||
| public Task<Channel> DestroyChannel(Channel channel) | |||
| => DestroyChannel(channel?.Id); | |||
| /// <summary> Destroys the provided channel. </summary> | |||
| public async Task<Channel> 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); | |||
| } | |||
| } | |||
| } | |||
| @@ -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<ServerEventArgs> ServerCreated; | |||
| private void RaiseServerCreated(Server server) | |||
| { | |||
| if (ServerCreated != null) | |||
| RaiseEvent(nameof(ServerCreated), () => ServerCreated(this, new ServerEventArgs(server))); | |||
| } | |||
| public event EventHandler<ServerEventArgs> ServerDestroyed; | |||
| private void RaiseServerDestroyed(Server server) | |||
| { | |||
| if (ServerDestroyed != null) | |||
| RaiseEvent(nameof(ServerDestroyed), () => ServerDestroyed(this, new ServerEventArgs(server))); | |||
| } | |||
| public event EventHandler<ServerEventArgs> ServerUpdated; | |||
| private void RaiseServerUpdated(Server server) | |||
| { | |||
| if (ServerUpdated != null) | |||
| RaiseEvent(nameof(ServerUpdated), () => ServerUpdated(this, new ServerEventArgs(server))); | |||
| } | |||
| public event EventHandler<ServerEventArgs> ServerUnavailable; | |||
| private void RaiseServerUnavailable(Server server) | |||
| { | |||
| if (ServerUnavailable != null) | |||
| RaiseEvent(nameof(ServerUnavailable), () => ServerUnavailable(this, new ServerEventArgs(server))); | |||
| } | |||
| public event EventHandler<ServerEventArgs> ServerAvailable; | |||
| private void RaiseServerAvailable(Server server) | |||
| { | |||
| if (ServerAvailable != null) | |||
| RaiseEvent(nameof(ServerAvailable), () => ServerAvailable(this, new ServerEventArgs(server))); | |||
| } | |||
| //Channel | |||
| public event EventHandler<ChannelEventArgs> ChannelCreated; | |||
| private void RaiseChannelCreated(Channel channel) | |||
| { | |||
| if (ChannelCreated != null) | |||
| RaiseEvent(nameof(ChannelCreated), () => ChannelCreated(this, new ChannelEventArgs(channel))); | |||
| } | |||
| public event EventHandler<ChannelEventArgs> ChannelDestroyed; | |||
| private void RaiseChannelDestroyed(Channel channel) | |||
| { | |||
| if (ChannelDestroyed != null) | |||
| RaiseEvent(nameof(ChannelDestroyed), () => ChannelDestroyed(this, new ChannelEventArgs(channel))); | |||
| } | |||
| public event EventHandler<ChannelEventArgs> ChannelUpdated; | |||
| private void RaiseChannelUpdated(Channel channel) | |||
| { | |||
| if (ChannelUpdated != null) | |||
| RaiseEvent(nameof(ChannelUpdated), () => ChannelUpdated(this, new ChannelEventArgs(channel))); | |||
| } | |||
| //Message | |||
| public event EventHandler<MessageEventArgs> MessageCreated; | |||
| private void RaiseMessageCreated(Message msg) | |||
| { | |||
| if (MessageCreated != null) | |||
| RaiseEvent(nameof(MessageCreated), () => MessageCreated(this, new MessageEventArgs(msg))); | |||
| } | |||
| public event EventHandler<MessageEventArgs> MessageDeleted; | |||
| private void RaiseMessageDeleted(Message msg) | |||
| { | |||
| if (MessageDeleted != null) | |||
| RaiseEvent(nameof(MessageDeleted), () => MessageDeleted(this, new MessageEventArgs(msg))); | |||
| } | |||
| public event EventHandler<MessageEventArgs> MessageUpdated; | |||
| private void RaiseMessageUpdated(Message msg) | |||
| { | |||
| if (MessageUpdated != null) | |||
| RaiseEvent(nameof(MessageUpdated), () => MessageUpdated(this, new MessageEventArgs(msg))); | |||
| } | |||
| public event EventHandler<MessageEventArgs> MessageReadRemotely; | |||
| private void RaiseMessageReadRemotely(Message msg) | |||
| { | |||
| if (MessageReadRemotely != null) | |||
| RaiseEvent(nameof(MessageReadRemotely), () => MessageReadRemotely(this, new MessageEventArgs(msg))); | |||
| } | |||
| public event EventHandler<MessageEventArgs> MessageSent; | |||
| private void RaiseMessageSent(Message msg) | |||
| { | |||
| if (MessageSent != null) | |||
| RaiseEvent(nameof(MessageSent), () => MessageSent(this, new MessageEventArgs(msg))); | |||
| } | |||
| //Role | |||
| public event EventHandler<RoleEventArgs> RoleCreated; | |||
| private void RaiseRoleCreated(Role role) | |||
| { | |||
| if (RoleCreated != null) | |||
| RaiseEvent(nameof(RoleCreated), () => RoleCreated(this, new RoleEventArgs(role))); | |||
| } | |||
| public event EventHandler<RoleEventArgs> RoleUpdated; | |||
| private void RaiseRoleDeleted(Role role) | |||
| { | |||
| if (RoleDeleted != null) | |||
| RaiseEvent(nameof(RoleDeleted), () => RoleDeleted(this, new RoleEventArgs(role))); | |||
| } | |||
| public event EventHandler<RoleEventArgs> RoleDeleted; | |||
| private void RaiseRoleUpdated(Role role) | |||
| { | |||
| if (RoleUpdated != null) | |||
| RaiseEvent(nameof(RoleUpdated), () => RoleUpdated(this, new RoleEventArgs(role))); | |||
| } | |||
| //Ban | |||
| public event EventHandler<BanEventArgs> 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<BanEventArgs> 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<MemberEventArgs> UserAdded; | |||
| private void RaiseUserAdded(Member member) | |||
| { | |||
| if (UserAdded != null) | |||
| RaiseEvent(nameof(UserAdded), () => UserAdded(this, new MemberEventArgs(member))); | |||
| } | |||
| public event EventHandler<MemberEventArgs> UserRemoved; | |||
| private void RaiseUserRemoved(Member member) | |||
| { | |||
| if (UserRemoved != null) | |||
| RaiseEvent(nameof(UserRemoved), () => UserRemoved(this, new MemberEventArgs(member))); | |||
| } | |||
| public event EventHandler<UserEventArgs> UserUpdated; | |||
| private void RaiseUserUpdated(User user) | |||
| { | |||
| if (UserUpdated != null) | |||
| RaiseEvent(nameof(UserUpdated), () => UserUpdated(this, new UserEventArgs(user))); | |||
| } | |||
| public event EventHandler<MemberEventArgs> MemberUpdated; | |||
| private void RaiseMemberUpdated(Member member) | |||
| { | |||
| if (MemberUpdated != null) | |||
| RaiseEvent(nameof(MemberUpdated), () => MemberUpdated(this, new MemberEventArgs(member))); | |||
| } | |||
| public event EventHandler<MemberEventArgs> UserPresenceUpdated; | |||
| private void RaiseUserPresenceUpdated(Member member) | |||
| { | |||
| if (UserPresenceUpdated != null) | |||
| RaiseEvent(nameof(UserPresenceUpdated), () => UserPresenceUpdated(this, new MemberEventArgs(member))); | |||
| } | |||
| public event EventHandler<MemberEventArgs> UserVoiceStateUpdated; | |||
| private void RaiseUserVoiceStateUpdated(Member member) | |||
| { | |||
| if (UserVoiceStateUpdated != null) | |||
| RaiseEvent(nameof(UserVoiceStateUpdated), () => UserVoiceStateUpdated(this, new MemberEventArgs(member))); | |||
| } | |||
| public event EventHandler<UserTypingEventArgs> UserIsTyping; | |||
| private void RaiseUserIsTyping(User user, Channel channel) | |||
| { | |||
| if (UserIsTyping != null) | |||
| RaiseEvent(nameof(UserIsTyping), () => UserIsTyping(this, new UserTypingEventArgs(user, channel))); | |||
| } | |||
| public event EventHandler<UserIsSpeakingEventArgs> UserIsSpeaking; | |||
| private void RaiseUserIsSpeaking(Member member, bool isSpeaking) | |||
| { | |||
| if (UserIsSpeaking != null) | |||
| RaiseEvent(nameof(UserIsSpeaking), () => UserIsSpeaking(this, new UserIsSpeakingEventArgs(member, isSpeaking))); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,96 @@ | |||
| using Discord.Net; | |||
| using System; | |||
| using System.Net; | |||
| using System.Threading.Tasks; | |||
| namespace Discord | |||
| { | |||
| public partial class DiscordClient | |||
| { | |||
| /// <summary> Creates a new invite to the default channel of the provided server. </summary> | |||
| /// <param name="maxAge"> Time (in seconds) until the invite expires. Set to 0 to never expire. </param> | |||
| /// <param name="tempMembership"> If true, a user accepting this invite will be kicked from the server after closing their client. </param> | |||
| /// <param name="hasXkcd"> If true, creates a human-readable link. Not supported if maxAge is set to 0. </param> | |||
| /// <param name="maxUses"> The max amount of times this invite may be used. Set to 0 to have unlimited uses. </param> | |||
| public Task<Invite> CreateInvite(Server server, int maxAge = 1800, int maxUses = 0, bool tempMembership = false, bool hasXkcd = false) | |||
| => CreateInvite(server?.DefaultChannelId, maxAge, maxUses, tempMembership, hasXkcd); | |||
| /// <summary> Creates a new invite to the provided channel. </summary> | |||
| /// <param name="maxAge"> Time (in seconds) until the invite expires. Set to 0 to never expire. </param> | |||
| /// <param name="tempMembership"> If true, a user accepting this invite will be kicked from the server after closing their client. </param> | |||
| /// <param name="hasXkcd"> If true, creates a human-readable link. Not supported if maxAge is set to 0. </param> | |||
| /// <param name="maxUses"> The max amount of times this invite may be used. Set to 0 to have unlimited uses. </param> | |||
| public Task<Invite> CreateInvite(Channel channel, int maxAge = 1800, int maxUses = 0, bool tempMembership = false, bool hasXkcd = false) | |||
| => CreateInvite(channel?.Id, maxAge, maxUses, tempMembership, hasXkcd); | |||
| /// <summary> Creates a new invite to the provided channel. </summary> | |||
| /// <param name="maxAge"> Time (in seconds) until the invite expires. Set to 0 to never expire. </param> | |||
| /// <param name="tempMembership"> If true, a user accepting this invite will be kicked from the server after closing their client. </param> | |||
| /// <param name="hasXkcd"> If true, creates a human-readable link. Not supported if maxAge is set to 0. </param> | |||
| /// <param name="maxUses"> The max amount of times this invite may be used. Set to 0 to have unlimited uses. </param> | |||
| public async Task<Invite> 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; | |||
| } | |||
| /// <summary> Deletes the provided invite. </summary> | |||
| 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) { } | |||
| } | |||
| /// <summary> Gets more info about the provided invite code. </summary> | |||
| /// <remarks> Supported formats: inviteCode, xkcdCode, https://discord.gg/inviteCode, https://discord.gg/xkcdCode </remarks> | |||
| public async Task<Invite> 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; | |||
| } | |||
| /// <summary> Accepts the provided invite. </summary> | |||
| public Task AcceptInvite(Invite invite) | |||
| { | |||
| CheckReady(); | |||
| if (invite == null) throw new ArgumentNullException(nameof(invite)); | |||
| return _api.AcceptInvite(invite.Id); | |||
| } | |||
| /// <summary> Accepts the provided invite. </summary> | |||
| 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); | |||
| } | |||
| } | |||
| } | |||
| @@ -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<MemberTypingEventArgs> UserIsTyping; | |||
| private void RaiseUserIsTyping(Member member, Channel channel) | |||
| { | |||
| if (UserIsTyping != null) | |||
| RaiseEvent(nameof(UserIsTyping), () => UserIsTyping(this, new MemberTypingEventArgs(member, channel))); | |||
| } | |||
| public event EventHandler<MemberIsSpeakingEventArgs> UserIsSpeaking; | |||
| private void RaiseUserIsSpeaking(Member member, bool isSpeaking) | |||
| { | |||
| if (UserIsSpeaking != null) | |||
| RaiseEvent(nameof(UserIsSpeaking), () => UserIsSpeaking(this, new MemberIsSpeakingEventArgs(member, isSpeaking))); | |||
| } | |||
| /// <summary> Returns a collection of all user-server pairs this client can currently see. </summary> | |||
| public Members Members => _members; | |||
| private readonly Members _members; | |||
| /// <summary> Returns the user with the specified id, along with their server-specific data, or null if none was found. </summary> | |||
| public Member GetMember(Server server, User user) => _members[user?.Id, server?.Id]; | |||
| /// <summary> Returns the user with the specified id, along with their server-specific data, or null if none was found. </summary> | |||
| public Member GetMember(Server server, string userId) => _members[userId, server?.Id]; | |||
| /// <summary> Returns the user with the specified id, along with their server-specific data, or null if none was found. </summary> | |||
| public Member GetMember(string serverId, User user) => _members[user?.Id, serverId]; | |||
| /// <summary> Returns the user with the specified id, along with their server-specific data, or null if none was found. </summary> | |||
| public Member GetMember(string serverId, string userId) => _members[userId, serverId]; | |||
| /// <summary> Returns the user with the specified name and discriminator, along withtheir server-specific data, or null if they couldn't be found. </summary> | |||
| /// <remarks> Name formats supported: Name and @Name. Search is case-insensitive. </remarks> | |||
| public Member GetMember(Server server, string username, string discriminator) | |||
| => GetMember(server?.Id, username, discriminator); | |||
| /// <summary> Returns the user with the specified name and discriminator, along withtheir server-specific data, or null if they couldn't be found. </summary> | |||
| /// <remarks> Name formats supported: Name and @Name. Search is case-insensitive. </remarks> | |||
| public Member GetMember(string serverId, string username, string discriminator) | |||
| { | |||
| User user = GetUser(username, discriminator); | |||
| return _members[user?.Id, serverId]; | |||
| } | |||
| /// <summary> Returns all users in with the specified server and name, along with their server-specific data. </summary> | |||
| /// <remarks> Name formats supported: Name and @Name. Search is case-insensitive.</remarks> | |||
| public IEnumerable<Member> FindMembers(string serverId, string name) => FindMembers(_servers[serverId], name); | |||
| /// <summary> Returns all users in with the specified server and name, along with their server-specific data. </summary> | |||
| /// <remarks> Name formats supported: Name and @Name. Search is case-insensitive.</remarks> | |||
| public IEnumerable<Member> 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<object> roles = null) | |||
| => EditMember(member?.ServerId, member?.UserId, mute, deaf, roles); | |||
| public Task EditMember(Server server, User user, bool? mute = null, bool? deaf = null, IEnumerable<object> roles = null) | |||
| => EditMember(server?.Id, user?.Id, mute, deaf, roles); | |||
| public Task EditMember(Server server, string userId, bool? mute = null, bool? deaf = null, IEnumerable<string> roles = null) | |||
| => EditMember(server?.Id, userId, mute, deaf, roles); | |||
| public Task EditMember(string serverId, User user, bool? mute = null, bool? deaf = null, IEnumerable<object> roles = null) | |||
| => EditMember(serverId, user?.Id, mute, deaf, roles); | |||
| public Task EditMember(string serverId, string userId, bool? mute = null, bool? deaf = null, IEnumerable<object> 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); | |||
| } | |||
| } | |||
| } | |||
| @@ -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; | |||
| /// <summary> Returns a collection of all messages this client has seen since logging in and currently has in cache. </summary> | |||
| public Messages Messages => _messages; | |||
| private readonly Messages _messages; | |||
| public event EventHandler<MessageEventArgs> MessageCreated; | |||
| private void RaiseMessageCreated(Message msg) | |||
| { | |||
| if (MessageCreated != null) | |||
| RaiseEvent(nameof(MessageCreated), () => MessageCreated(this, new MessageEventArgs(msg))); | |||
| } | |||
| public event EventHandler<MessageEventArgs> MessageDeleted; | |||
| private void RaiseMessageDeleted(Message msg) | |||
| { | |||
| if (MessageDeleted != null) | |||
| RaiseEvent(nameof(MessageDeleted), () => MessageDeleted(this, new MessageEventArgs(msg))); | |||
| } | |||
| public event EventHandler<MessageEventArgs> MessageUpdated; | |||
| private void RaiseMessageUpdated(Message msg) | |||
| { | |||
| if (MessageUpdated != null) | |||
| RaiseEvent(nameof(MessageUpdated), () => MessageUpdated(this, new MessageEventArgs(msg))); | |||
| } | |||
| public event EventHandler<MessageEventArgs> MessageReadRemotely; | |||
| private void RaiseMessageReadRemotely(Message msg) | |||
| { | |||
| if (MessageReadRemotely != null) | |||
| RaiseEvent(nameof(MessageReadRemotely), () => MessageReadRemotely(this, new MessageEventArgs(msg))); | |||
| } | |||
| public event EventHandler<MessageEventArgs> MessageSent; | |||
| private void RaiseMessageSent(Message msg) | |||
| { | |||
| if (MessageSent != null) | |||
| RaiseEvent(nameof(MessageSent), () => MessageSent(this, new MessageEventArgs(msg))); | |||
| } | |||
| /// <summary> Returns the message with the specified id, or null if none was found. </summary> | |||
| public Message GetMessage(string id) => _messages[id]; | |||
| /// <summary> Sends a message to the provided channel. To include a mention, see the Mention static helper class. </summary> | |||
| public Task<Message[]> SendMessage(Channel channel, string text) | |||
| => SendMessage(channel, text, MentionHelper.GetUserIds(text), false); | |||
| /// <summary> Sends a message to the provided channel. To include a mention, see the Mention static helper class. </summary> | |||
| public Task<Message[]> SendMessage(string channelId, string text) | |||
| => SendMessage(_channels[channelId], text, MentionHelper.GetUserIds(text), false); | |||
| private async Task<Message[]> SendMessage(Channel channel, string text, IEnumerable<object> 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; | |||
| } | |||
| /// <summary> Sends a private message to the provided user. </summary> | |||
| public Task<Message[]> SendPrivateMessage(Member member, string text) | |||
| => SendPrivateMessage(member?.UserId, text); | |||
| /// <summary> Sends a private message to the provided user. </summary> | |||
| public Task<Message[]> SendPrivateMessage(User user, string text) | |||
| => SendPrivateMessage(user?.Id, text); | |||
| /// <summary> Sends a private message to the provided user. </summary> | |||
| public async Task<Message[]> SendPrivateMessage(string userId, string text) | |||
| { | |||
| var channel = await CreatePMChannel(userId).ConfigureAwait(false); | |||
| return await SendMessage(channel, text, new string[0]).ConfigureAwait(false); | |||
| } | |||
| /// <summary> Sends a file to the provided channel. </summary> | |||
| public Task SendFile(Channel channel, string filePath) | |||
| => SendFile(channel?.Id, filePath); | |||
| /// <summary> Sends a file to the provided channel. </summary> | |||
| 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); | |||
| } | |||
| /// <summary> Edits the provided message, changing only non-null attributes. </summary> | |||
| /// <remarks> While not required, it is recommended to include a mention reference in the text (see Mention.User). </remarks> | |||
| public Task EditMessage(Message message, string text = null, IEnumerable<object> mentionedUsers = null) | |||
| => EditMessage(message?.ChannelId, message?.Id, text, mentionedUsers); | |||
| /// <summary> Edits the provided message, changing only non-null attributes. </summary> | |||
| /// <remarks> While not required, it is recommended to include a mention reference in the text (see Mention.User). </remarks> | |||
| public Task EditMessage(Channel channel, string messageId, string text = null, IEnumerable<object> mentionedUsers = null) | |||
| => EditMessage(channel?.Id, messageId, text, mentionedUsers); | |||
| /// <summary> Edits the provided message, changing only non-null attributes. </summary> | |||
| /// <remarks> While not required, it is recommended to include a mention reference in the text (see Mention.User). </remarks> | |||
| public async Task EditMessage(string channelId, string messageId, string text = null, IEnumerable<object> 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); | |||
| } | |||
| /// <summary> Deletes the provided message. </summary> | |||
| public Task DeleteMessage(Message msg) | |||
| => DeleteMessage(msg?.ChannelId, msg?.Id); | |||
| /// <summary> Deletes the provided message. </summary> | |||
| 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<Message> 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<string> 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) { } | |||
| } | |||
| } | |||
| /// <summary> Downloads last count messages from the server, starting at beforeMessageId if it's provided. </summary> | |||
| public Task<Message[]> DownloadMessages(Channel channel, int count, string beforeMessageId = null, bool cache = true) | |||
| => DownloadMessages(channel.Id, count, beforeMessageId, cache); | |||
| /// <summary> Downloads last count messages from the server, starting at beforeMessageId if it's provided. </summary> | |||
| public async Task<Message[]> 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(); | |||
| } | |||
| } | |||
| } | |||
| @@ -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) { } | |||
| } | |||
| } | |||
| } | |||
| @@ -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<RoleEventArgs> RoleCreated; | |||
| private void RaiseRoleCreated(Role role) | |||
| { | |||
| if (RoleCreated != null) | |||
| RaiseEvent(nameof(RoleCreated), () => RoleCreated(this, new RoleEventArgs(role))); | |||
| } | |||
| public event EventHandler<RoleEventArgs> RoleUpdated; | |||
| private void RaiseRoleDeleted(Role role) | |||
| { | |||
| if (RoleDeleted != null) | |||
| RaiseEvent(nameof(RoleDeleted), () => RoleDeleted(this, new RoleEventArgs(role))); | |||
| } | |||
| public event EventHandler<RoleEventArgs> RoleDeleted; | |||
| private void RaiseRoleUpdated(Role role) | |||
| { | |||
| if (RoleUpdated != null) | |||
| RaiseEvent(nameof(RoleUpdated), () => RoleUpdated(this, new RoleEventArgs(role))); | |||
| } | |||
| /// <summary> Returns a collection of all role-server pairs this client can currently see. </summary> | |||
| public Roles Roles => _roles; | |||
| private readonly Roles _roles; | |||
| /// <summary> Returns the role with the specified id, or null if none was found. </summary> | |||
| public Role GetRole(string id) => _roles[id]; | |||
| /// <summary> Returns all roles with the specified server and name. </summary> | |||
| /// <remarks> Name formats supported: Name and @Name. Search is case-insensitive. </remarks> | |||
| public IEnumerable<Role> FindRoles(Server server, string name) => FindRoles(server?.Id, name); | |||
| /// <summary> Returns all roles with the specified server and name. </summary> | |||
| /// <remarks> Name formats supported: Name and @Name. Search is case-insensitive. </remarks> | |||
| public IEnumerable<Role> 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)); | |||
| } | |||
| } | |||
| /// <summary> Note: due to current API limitations, the created role cannot be returned. </summary> | |||
| public Task<Role> CreateRole(Server server, string name) | |||
| => CreateRole(server?.Id, name); | |||
| /// <summary> Note: due to current API limitations, the created role cannot be returned. </summary> | |||
| public async Task<Role> 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<object> roles, int startPos = 0) | |||
| => ReorderChannels(server.Id, roles, startPos); | |||
| public Task ReorderRoles(string serverId, IEnumerable<object> 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); | |||
| } | |||
| } | |||
| } | |||
| @@ -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<ServerEventArgs> ServerCreated; | |||
| private void RaiseServerCreated(Server server) | |||
| { | |||
| if (ServerCreated != null) | |||
| RaiseEvent(nameof(ServerCreated), () => ServerCreated(this, new ServerEventArgs(server))); | |||
| } | |||
| public event EventHandler<ServerEventArgs> ServerDestroyed; | |||
| private void RaiseServerDestroyed(Server server) | |||
| { | |||
| if (ServerDestroyed != null) | |||
| RaiseEvent(nameof(ServerDestroyed), () => ServerDestroyed(this, new ServerEventArgs(server))); | |||
| } | |||
| public event EventHandler<ServerEventArgs> ServerUpdated; | |||
| private void RaiseServerUpdated(Server server) | |||
| { | |||
| if (ServerUpdated != null) | |||
| RaiseEvent(nameof(ServerUpdated), () => ServerUpdated(this, new ServerEventArgs(server))); | |||
| } | |||
| public event EventHandler<ServerEventArgs> ServerUnavailable; | |||
| private void RaiseServerUnavailable(Server server) | |||
| { | |||
| if (ServerUnavailable != null) | |||
| RaiseEvent(nameof(ServerUnavailable), () => ServerUnavailable(this, new ServerEventArgs(server))); | |||
| } | |||
| public event EventHandler<ServerEventArgs> ServerAvailable; | |||
| private void RaiseServerAvailable(Server server) | |||
| { | |||
| if (ServerAvailable != null) | |||
| RaiseEvent(nameof(ServerAvailable), () => ServerAvailable(this, new ServerEventArgs(server))); | |||
| } | |||
| /// <summary> Returns the server with the specified id, or null if none was found. </summary> | |||
| public Server GetServer(string id) => _servers[id]; | |||
| /// <summary> Returns all servers with the specified name. </summary> | |||
| /// <remarks> Search is case-insensitive. </remarks> | |||
| public IEnumerable<Server> FindServers(string name) | |||
| { | |||
| if (name == null) throw new ArgumentNullException(nameof(name)); | |||
| return _servers.Where(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); | |||
| } | |||
| /// <summary> Creates a new server with the provided name and region (see Regions). </summary> | |||
| public async Task<Server> 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; | |||
| } | |||
| /// <summary> Edits the provided server, changing only non-null attributes. </summary> | |||
| 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); | |||
| /// <summary> Edits the provided server, changing only non-null attributes. </summary> | |||
| 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); | |||
| } | |||
| /// <summary> Leaves the provided server, destroying it if you are the owner. </summary> | |||
| public Task<Server> LeaveServer(Server server) | |||
| => LeaveServer(server?.Id); | |||
| /// <summary> Leaves the provided server, destroying it if you are the owner. </summary> | |||
| public async Task<Server> 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); | |||
| } | |||
| } | |||
| } | |||
| @@ -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<MemberEventArgs> UserAdded; | |||
| private void RaiseUserAdded(Member member) | |||
| { | |||
| if (UserAdded != null) | |||
| RaiseEvent(nameof(UserAdded), () => UserAdded(this, new MemberEventArgs(member))); | |||
| } | |||
| public event EventHandler<MemberEventArgs> UserRemoved; | |||
| private void RaiseUserRemoved(Member member) | |||
| { | |||
| if (UserRemoved != null) | |||
| RaiseEvent(nameof(UserRemoved), () => UserRemoved(this, new MemberEventArgs(member))); | |||
| } | |||
| public event EventHandler<UserEventArgs> UserUpdated; | |||
| private void RaiseUserUpdated(User user) | |||
| { | |||
| if (UserUpdated != null) | |||
| RaiseEvent(nameof(UserUpdated), () => UserUpdated(this, new UserEventArgs(user))); | |||
| } | |||
| public event EventHandler<MemberEventArgs> MemberUpdated; | |||
| private void RaiseMemberUpdated(Member member) | |||
| { | |||
| if (MemberUpdated != null) | |||
| RaiseEvent(nameof(MemberUpdated), () => MemberUpdated(this, new MemberEventArgs(member))); | |||
| } | |||
| public event EventHandler<MemberEventArgs> UserPresenceUpdated; | |||
| private void RaiseUserPresenceUpdated(Member member) | |||
| { | |||
| if (UserPresenceUpdated != null) | |||
| RaiseEvent(nameof(UserPresenceUpdated), () => UserPresenceUpdated(this, new MemberEventArgs(member))); | |||
| } | |||
| public event EventHandler<MemberEventArgs> UserVoiceStateUpdated; | |||
| private void RaiseUserVoiceStateUpdated(Member member) | |||
| { | |||
| if (UserVoiceStateUpdated != null) | |||
| RaiseEvent(nameof(UserVoiceStateUpdated), () => UserVoiceStateUpdated(this, new MemberEventArgs(member))); | |||
| } | |||
| /// <summary> Returns a collection of all users this client can currently see. </summary> | |||
| public Users Users => _users; | |||
| private readonly Users _users; | |||
| /// <summary> Returns the current logged-in user. </summary> | |||
| public User CurrentUser => _currentUser; | |||
| private User _currentUser; | |||
| /// <summary> Returns the user with the specified id, or null if none was found. </summary> | |||
| public User GetUser(string id) => _users[id]; | |||
| /// <summary> Returns the user with the specified name and discriminator, or null if none was found. </summary> | |||
| /// <remarks> Name formats supported: Name and @Name. Search is case-insensitive. </remarks> | |||
| 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(); | |||
| } | |||
| /// <summary> Returns all users with the specified name across all servers. </summary> | |||
| /// <remarks> Name formats supported: Name and @Name. Search is case-insensitive. </remarks> | |||
| public IEnumerable<User> 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<EditUserResponse> 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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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; } | |||
| } | |||
| /// <summary> Provides a connection to the DiscordApp service. </summary> | |||
| public partial class DiscordClient : DiscordWSClient | |||
| { | |||
| @@ -28,29 +76,10 @@ namespace Discord | |||
| public new DiscordClientConfig Config => _config as DiscordClientConfig; | |||
| /// <summary> Returns the current logged-in user. </summary> | |||
| public User CurrentUser => _currentUser; | |||
| private User _currentUser; | |||
| /// <summary> Returns a collection of all channels this client is a member of. </summary> | |||
| public Channels Channels => _channels; | |||
| private readonly Channels _channels; | |||
| /// <summary> Returns a collection of all user-server pairs this client can currently see. </summary> | |||
| public Members Members => _members; | |||
| private readonly Members _members; | |||
| /// <summary> Returns a collection of all messages this client has seen since logging in and currently has in cache. </summary> | |||
| public Messages Messages => _messages; | |||
| private readonly Messages _messages; | |||
| //TODO: Do we need the roles cache? | |||
| /// <summary> Returns a collection of all role-server pairs this client can currently see. </summary> | |||
| public Roles Roles => _roles; | |||
| private readonly Roles _roles; | |||
| /// <summary> Returns a collection of all servers this client is a member of. </summary> | |||
| public Servers Servers => _servers; | |||
| private readonly Servers _servers; | |||
| /// <summary> Returns a collection of all users this client can currently see. </summary> | |||
| public Users Users => _users; | |||
| private readonly Users _users; | |||
| /// <summary> Initializes a new instance of the DiscordClient class. </summary> | |||
| 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<TypingStartEvent>(_serializer); | |||
| var channel = _channels[data.ChannelId]; | |||
| var user = _users[data.UserId]; | |||
| var user = _members[data.UserId, channel.ServerId]; | |||
| if (user != null) | |||
| { | |||