@@ -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) | |||
{ | |||