Browse Source

Re-partialed DiscordClient by object type and moved Finds from collections.

pull/6/head
RogueException 9 years ago
parent
commit
02876f2542
21 changed files with 1392 additions and 1279 deletions
  1. +24
    -6
      src/Discord.Net.Net45/Discord.Net.csproj
  2. +2
    -2
      src/Discord.Net/Collections/AsyncCollection.cs
  3. +4
    -22
      src/Discord.Net/Collections/Channels.cs
  4. +0
    -46
      src/Discord.Net/Collections/Members.cs
  5. +12
    -5
      src/Discord.Net/Collections/Messages.cs
  6. +4
    -17
      src/Discord.Net/Collections/Roles.cs
  7. +1
    -9
      src/Discord.Net/Collections/Servers.cs
  8. +4
    -16
      src/Discord.Net/Collections/Users.cs
  9. +0
    -756
      src/Discord.Net/DiscordClient.API.cs
  10. +68
    -0
      src/Discord.Net/DiscordClient.Bans.cs
  11. +0
    -59
      src/Discord.Net/DiscordClient.Cache.cs
  12. +182
    -0
      src/Discord.Net/DiscordClient.Channels.cs
  13. +0
    -278
      src/Discord.Net/DiscordClient.Events.cs
  14. +96
    -0
      src/Discord.Net/DiscordClient.Invites.cs
  15. +135
    -0
      src/Discord.Net/DiscordClient.Members.cs
  16. +296
    -0
      src/Discord.Net/DiscordClient.Messages.cs
  17. +132
    -0
      src/Discord.Net/DiscordClient.Permissions.cs
  18. +149
    -0
      src/Discord.Net/DiscordClient.Roles.cs
  19. +102
    -0
      src/Discord.Net/DiscordClient.Servers.cs
  20. +130
    -0
      src/Discord.Net/DiscordClient.Users.cs
  21. +51
    -63
      src/Discord.Net/DiscordClient.cs

+ 24
- 6
src/Discord.Net.Net45/Discord.Net.csproj View File

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


+ 2
- 2
src/Discord.Net/Collections/AsyncCollection.cs View File

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


+ 4
- 22
src/Discord.Net/Collections/Channels.cs View File

@@ -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;
} }
} }
} }

+ 0
- 46
src/Discord.Net/Collections/Members.cs View File

@@ -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();
}
} }
} }

+ 12
- 5
src/Discord.Net/Collections/Messages.cs View File

@@ -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);
}
}
} }
} }

+ 4
- 17
src/Discord.Net/Collections/Roles.cs View File

@@ -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);
} }
} }
} }


+ 1
- 9
src/Discord.Net/Collections/Servers.cs View File

@@ -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));
}
} }
} }

+ 4
- 16
src/Discord.Net/Collections/Users.cs View File

@@ -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);
} }
} }
} }


+ 0
- 756
src/Discord.Net/DiscordClient.API.cs View File

@@ -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);
}
}
}

+ 68
- 0
src/Discord.Net/DiscordClient.Bans.cs View File

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

+ 0
- 59
src/Discord.Net/DiscordClient.Cache.cs View File

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

}
}

+ 182
- 0
src/Discord.Net/DiscordClient.Channels.cs View File

@@ -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);
}
}
}

+ 0
- 278
src/Discord.Net/DiscordClient.Events.cs View File

@@ -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)));
}
}
}

+ 96
- 0
src/Discord.Net/DiscordClient.Invites.cs View File

@@ -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);
}
}
}

+ 135
- 0
src/Discord.Net/DiscordClient.Members.cs View File

@@ -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);
}
}
}

+ 296
- 0
src/Discord.Net/DiscordClient.Messages.cs View File

@@ -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();
}
}
}

+ 132
- 0
src/Discord.Net/DiscordClient.Permissions.cs View File

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

+ 149
- 0
src/Discord.Net/DiscordClient.Roles.cs View File

@@ -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);
}
}
}

+ 102
- 0
src/Discord.Net/DiscordClient.Servers.cs View File

@@ -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);
}
}
}

+ 130
- 0
src/Discord.Net/DiscordClient.Users.cs View File

@@ -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;
}
}
}

+ 51
- 63
src/Discord.Net/DiscordClient.cs View File

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


Loading…
Cancel
Save