@@ -154,17 +154,35 @@ | |||||
<Compile Include="..\Discord.Net\DiscordAPIClientConfig.cs"> | <Compile Include="..\Discord.Net\DiscordAPIClientConfig.cs"> | ||||
<Link>DiscordAPIClientConfig.cs</Link> | <Link>DiscordAPIClientConfig.cs</Link> | ||||
</Compile> | </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> | ||||
<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> | ||||
<Compile Include="..\Discord.Net\DiscordClient.cs"> | <Compile Include="..\Discord.Net\DiscordClient.cs"> | ||||
<Link>DiscordClient.cs</Link> | <Link>DiscordClient.cs</Link> | ||||
</Compile> | </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> | ||||
<Compile Include="..\Discord.Net\DiscordClientConfig.cs"> | <Compile Include="..\Discord.Net\DiscordClientConfig.cs"> | ||||
<Link>DiscordClientConfig.cs</Link> | <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() | public IEnumerator<TValue> GetEnumerator() | ||||
{ | { | ||||
@@ -1,6 +1,4 @@ | |||||
using System; | using System; | ||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
namespace Discord.Collections | 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; | ||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
namespace Discord.Collections | namespace Discord.Collections | ||||
{ | { | ||||
@@ -45,52 +43,8 @@ namespace Discord.Collections | |||||
{ | { | ||||
if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | ||||
if (userId == null) throw new ArgumentNullException(nameof(userId)); | if (userId == null) throw new ArgumentNullException(nameof(userId)); | ||||
return Get(GetKey(userId, serverId)); | 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> | public sealed class Messages : AsyncCollection<Message> | ||||
{ | { | ||||
internal Messages(DiscordClient client, object writerLock) | 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 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); | internal new Message TryRemove(string id) => base.TryRemove(id); | ||||
@@ -26,6 +26,13 @@ | |||||
user.RemoveRef(); | 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; | ||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
namespace Discord.Collections | namespace Discord.Collections | ||||
{ | { | ||||
@@ -23,23 +21,12 @@ namespace Discord.Collections | |||||
item.Server.RemoveRole(item.Id); | 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 Server GetOrAdd(string id) => base.GetOrAdd(id, () => new Server(_client, id)); | ||||
internal new Server TryRemove(string id) => base.TryRemove(id); | internal new Server TryRemove(string id) => base.TryRemove(id); | ||||
protected override void OnCreated(Server item) { } | |||||
protected override void OnRemoved(Server item) | protected override void OnRemoved(Server item) | ||||
{ | { | ||||
var channels = _client.Channels; | var channels = _client.Channels; | ||||
@@ -29,12 +28,5 @@ namespace Discord.Collections | |||||
} | } | ||||
internal Server this[string id] => Get(id); | 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; | ||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
namespace Discord.Collections | namespace Discord.Collections | ||||
{ | { | ||||
@@ -15,22 +13,12 @@ namespace Discord.Collections | |||||
protected override void OnCreated(User item) { } | protected override void OnCreated(User item) { } | ||||
protected override void OnRemoved(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 | 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> | /// <summary> Provides a connection to the DiscordApp service. </summary> | ||||
public partial class DiscordClient : DiscordWSClient | public partial class DiscordClient : DiscordWSClient | ||||
{ | { | ||||
@@ -28,29 +76,10 @@ namespace Discord | |||||
public new DiscordClientConfig Config => _config as DiscordClientConfig; | 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> | /// <summary> Returns a collection of all servers this client is a member of. </summary> | ||||
public Servers Servers => _servers; | public Servers Servers => _servers; | ||||
private readonly 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> | /// <summary> Initializes a new instance of the DiscordClient class. </summary> | ||||
public DiscordClient(DiscordClientConfig config = null) | public DiscordClient(DiscordClientConfig config = null) | ||||
@@ -69,8 +98,8 @@ namespace Discord | |||||
_messages = new Messages(this, cacheLock); | _messages = new Messages(this, cacheLock); | ||||
_roles = new Roles(this, cacheLock); | _roles = new Roles(this, cacheLock); | ||||
_servers = new Servers(this, cacheLock); | _servers = new Servers(this, cacheLock); | ||||
_users = new Users(this, cacheLock); | |||||
_status = UserStatus.Online; | _status = UserStatus.Online; | ||||
_users = new Users(this, cacheLock); | |||||
this.Connected += async (s, e) => | this.Connected += async (s, e) => | ||||
{ | { | ||||
@@ -321,47 +350,6 @@ namespace Discord | |||||
return base.GetTasks(); | 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) | internal override async Task OnReceivedEvent(WebSocketEventEventArgs e) | ||||
{ | { | ||||
try | try | ||||
@@ -656,7 +644,7 @@ namespace Discord | |||||
{ | { | ||||
var data = e.Payload.ToObject<TypingStartEvent>(_serializer); | var data = e.Payload.ToObject<TypingStartEvent>(_serializer); | ||||
var channel = _channels[data.ChannelId]; | var channel = _channels[data.ChannelId]; | ||||
var user = _users[data.UserId]; | |||||
var user = _members[data.UserId, channel.ServerId]; | |||||
if (user != null) | if (user != null) | ||||
{ | { | ||||