Browse Source

Re-added model functions, and added legacy extensions

tags/docs-0.9
RogueException 9 years ago
parent
commit
06fae0c95a
18 changed files with 1631 additions and 1372 deletions
  1. +1
    -1
      src/Discord.Net.Commands/CommandExtensions.cs
  2. +208
    -784
      src/Discord.Net/DiscordClient.Obsolete.cs
  3. +373
    -327
      src/Discord.Net/DiscordClient.cs
  4. +102
    -2
      src/Discord.Net/Extensions.cs
  5. +7
    -0
      src/Discord.Net/IService.cs
  6. +0
    -95
      src/Discord.Net/Mention.cs
  7. +292
    -21
      src/Discord.Net/Models/Channel.cs
  8. +22
    -5
      src/Discord.Net/Models/Invite.cs
  9. +128
    -12
      src/Discord.Net/Models/Message.cs
  10. +2
    -2
      src/Discord.Net/Models/Permissions.cs
  11. +40
    -2
      src/Discord.Net/Models/Profile.cs
  12. +56
    -3
      src/Discord.Net/Models/Role.cs
  13. +244
    -46
      src/Discord.Net/Models/Server.cs
  14. +101
    -39
      src/Discord.Net/Models/User.cs
  15. +4
    -5
      src/Discord.Net/Net/WebSockets/GatewaySocket.cs
  16. +0
    -28
      src/Discord.Net/Optional.cs
  17. +12
    -0
      src/Discord.Net/RelativeDirection.cs
  18. +39
    -0
      src/Discord.Net/ServiceManager.cs

+ 1
- 1
src/Discord.Net.Commands/CommandExtensions.cs View File

@@ -3,6 +3,6 @@
public static class CommandExtensions public static class CommandExtensions
{ {
public static CommandService Commands(this DiscordClient client, bool required = true) public static CommandService Commands(this DiscordClient client, bool required = true)
=> client.GetService<CommandService>(required);
=> client.Services.Get<CommandService>(required);
} }
} }

+ 208
- 784
src/Discord.Net/DiscordClient.Obsolete.cs
File diff suppressed because it is too large
View File


+ 373
- 327
src/Discord.Net/DiscordClient.cs View File

@@ -28,7 +28,6 @@ namespace Discord
private readonly ConcurrentDictionary<ulong, Channel> _channels; private readonly ConcurrentDictionary<ulong, Channel> _channels;
private readonly ConcurrentDictionary<ulong, Channel> _privateChannels; //Key = RecipientId private readonly ConcurrentDictionary<ulong, Channel> _privateChannels; //Key = RecipientId
private readonly JsonSerializer _serializer; private readonly JsonSerializer _serializer;
private readonly Region _unknownRegion;
private Dictionary<string, Region> _regions; private Dictionary<string, Region> _regions;
private CancellationTokenSource _cancelTokenSource; private CancellationTokenSource _cancelTokenSource;


@@ -42,6 +41,8 @@ namespace Discord
public RestClient StatusAPI { get; } public RestClient StatusAPI { get; }
/// <summary> Gets the internal WebSocket for the Gateway event stream. </summary> /// <summary> Gets the internal WebSocket for the Gateway event stream. </summary>
public GatewaySocket GatewaySocket { get; } public GatewaySocket GatewaySocket { get; }
/// <summary> Gets the service manager used for adding extensions to this client. </summary>
public ServiceManager Services { get; }
/// <summary> Gets the queue used for outgoing messages, if enabled. </summary> /// <summary> Gets the queue used for outgoing messages, if enabled. </summary>
internal MessageQueue MessageQueue { get; } internal MessageQueue MessageQueue { get; }
/// <summary> Gets the logger used for this client. </summary> /// <summary> Gets the logger used for this client. </summary>
@@ -66,6 +67,8 @@ namespace Discord
// public IEnumerable<Channel> Channels => _servers.Select(x => x.Value); // public IEnumerable<Channel> Channels => _servers.Select(x => x.Value);
/// <summary> Gets a collection of all private channels this client is a member of. </summary> /// <summary> Gets a collection of all private channels this client is a member of. </summary>
public IEnumerable<Channel> PrivateChannels => _channels.Select(x => x.Value); public IEnumerable<Channel> PrivateChannels => _channels.Select(x => x.Value);
/// <summary> Gets a collection of all voice regions currently offered by Discord. </summary>
public IEnumerable<Region> Regions => _regions.Select(x => x.Value);
/// <summary> Initializes a new instance of the DiscordClient class. </summary> /// <summary> Initializes a new instance of the DiscordClient class. </summary>
public DiscordClient(DiscordConfig config = null) public DiscordClient(DiscordConfig config = null)
@@ -75,8 +78,8 @@ namespace Discord
State = (int)ConnectionState.Disconnected; State = (int)ConnectionState.Disconnected;
Status = UserStatus.Online; Status = UserStatus.Online;
//Services
//Logging
Log = new LogManager(this); Log = new LogManager(this);
Logger = Log.CreateLogger("Discord"); Logger = Log.CreateLogger("Discord");


@@ -91,7 +94,6 @@ namespace Discord
_servers = new ConcurrentDictionary<ulong, Server>(); _servers = new ConcurrentDictionary<ulong, Server>();
_channels = new ConcurrentDictionary<ulong, Channel>(); _channels = new ConcurrentDictionary<ulong, Channel>();
_privateChannels = new ConcurrentDictionary<ulong, Channel>(); _privateChannels = new ConcurrentDictionary<ulong, Channel>();
_unknownRegion = new Region("", "Unknown", "", 0);


//Serialization //Serialization
_serializer = new JsonSerializer(); _serializer = new JsonSerializer();
@@ -126,10 +128,13 @@ namespace Discord
ClientAPI.CancelToken = CancelToken; ClientAPI.CancelToken = CancelToken;
await SendStatus(); await SendStatus();
}; };
//Import/Export
//_messageImporter = new JsonSerializer();
//_messageImporter.ContractResolver = new Message.ImportResolver();

//Extensibility
Services = new ServiceManager(this);

//Import/Export
//_messageImporter = new JsonSerializer();
//_messageImporter.ContractResolver = new Message.ImportResolver();
} }


/// <summary> Connects to the Discord server with the provided email and password. </summary> /// <summary> Connects to the Discord server with the provided email and password. </summary>
@@ -167,7 +172,8 @@ namespace Discord
await Login(email, password, token).ConfigureAwait(false); await Login(email, password, token).ConfigureAwait(false);


ClientAPI.Token = token; ClientAPI.Token = token;
await GatewaySocket.Connect(token).ConfigureAwait(false);
GatewaySocket.Token = token;
await GatewaySocket.Connect().ConfigureAwait(false);


List<Task> tasks = new List<Task>(); List<Task> tasks = new List<Task>();
tasks.Add(CancelToken.Wait()); tasks.Add(CancelToken.Wait());
@@ -265,7 +271,10 @@ namespace Discord


await ClientAPI.Send(new LogoutRequest()).ConfigureAwait(false); await ClientAPI.Send(new LogoutRequest()).ConfigureAwait(false);


_servers.Clear();
ClientAPI.Token = null;
GatewaySocket.Token = null;

_servers.Clear();
_channels.Clear(); _channels.Clear();
_privateChannels.Clear(); _privateChannels.Clear();


@@ -276,45 +285,197 @@ namespace Discord
_connectedEvent.Reset(); _connectedEvent.Reset();
_disconnectedEvent.Set(); _disconnectedEvent.Set();
} }
private void OnReceivedEvent(WebSocketEventEventArgs e)
{
try
{
switch (e.Type)
{
//Global
case "READY": //Resync
{
var data = e.Payload.ToObject<ReadyEvent>(_serializer);
public Task SetStatus(UserStatus status)
{
if (status == null) throw new ArgumentNullException(nameof(status));
if (status != UserStatus.Online && status != UserStatus.Idle)
throw new ArgumentException($"Invalid status, must be {UserStatus.Online} or {UserStatus.Idle}", nameof(status));

Status = status;
return SendStatus();
}
public Task SetGame(int? gameId)
{
CurrentGameId = gameId;
return SendStatus();
}
private Task SendStatus()
{
GatewaySocket.SendUpdateStatus(Status == UserStatus.Idle ? EpochTime.GetMilliseconds() - (10 * 60 * 1000) : (long?)null, CurrentGameId);
return TaskHelper.CompletedTask;
}

#region Channels
private Channel AddChannel(ulong id, ulong? guildId, ulong? recipientId)
{
Channel channel;
if (recipientId != null)
{
channel = _privateChannels.GetOrAdd(recipientId.Value,
x => new Channel(this, x, new User(this, recipientId.Value, null)));
}
else
{
var server = GetServer(guildId.Value);
channel = server.AddChannel(id);
}
_channels[channel.Id] = channel;
return channel;
}
private Channel RemoveChannel(ulong id)
{
Channel channel;
if (_channels.TryRemove(id, out channel))
{
if (channel.IsPrivate)
_privateChannels.TryRemove(channel.Recipient.Id, out channel);
else
channel.Server.RemoveChannel(id);
}
return channel;
}

internal Channel GetChannel(ulong id)
{
Channel channel;
_channels.TryGetValue(id, out channel);
return channel;
}
internal Channel GetPrivateChannel(ulong recipientId)
{
Channel channel;
_privateChannels.TryGetValue(recipientId, out channel);
return channel;
}

internal async Task<Channel> CreatePrivateChannel(User user)
{
var channel = GetPrivateChannel(user.Id);
if (channel != null) return channel;

var request = new CreatePrivateChannelRequest() { RecipientId = user.Id };
var response = await ClientAPI.Send(request).ConfigureAwait(false);
channel = AddChannel(response.Id, null, response.Recipient.Id);
channel.Update(response);
return channel;
}
#endregion

#region Invites
/// <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)
{
if (inviteIdOrXkcd == null) throw new ArgumentNullException(nameof(inviteIdOrXkcd));

//Remove trailing slash
if (inviteIdOrXkcd.Length > 0 && inviteIdOrXkcd[inviteIdOrXkcd.Length - 1] == '/')
inviteIdOrXkcd = inviteIdOrXkcd.Substring(0, inviteIdOrXkcd.Length - 1);
//Remove leading URL
int index = inviteIdOrXkcd.LastIndexOf('/');
if (index >= 0)
inviteIdOrXkcd = inviteIdOrXkcd.Substring(index + 1);

var response = await ClientAPI.Send(new GetInviteRequest(inviteIdOrXkcd)).ConfigureAwait(false);
var invite = new Invite(this, response.Code, response.XkcdPass);
invite.Update(response);
return invite;
}
#endregion

#region Regions
public Region GetRegion(string id)
{
Region region;
if (_regions.TryGetValue(id, out region))
return region;
else
return new Region(id, id, "", 0);
}
#endregion

#region Servers
private Server AddServer(ulong id)
=> _servers.GetOrAdd(id, x => new Server(this, x));
private Server RemoveServer(ulong id)
{
Server server;
_servers.TryRemove(id, out server);
return server;
}

public Server GetServer(ulong id)
{
Server server;
_servers.TryGetValue(id, out server);
return server;
}
public IEnumerable<Server> FindServers(string name)
{
if (name == null) throw new ArgumentNullException(nameof(name));
return _servers.Select(x => x.Value).Find(name);
}

/// <summary> Creates a new server with the provided name and region. </summary>
public async Task<Server> CreateServer(string name, Region region, ImageType iconType = ImageType.None, Stream icon = null)
{
if (name == null) throw new ArgumentNullException(nameof(name));
if (region == null) throw new ArgumentNullException(nameof(region));

var request = new CreateGuildRequest()
{
Name = name,
Region = region.Id,
IconBase64 = icon.Base64(iconType, null)
};
var response = await ClientAPI.Send(request).ConfigureAwait(false);

var server = AddServer(response.Id);
server.Update(response);
return server;
}
#endregion

private void OnReceivedEvent(WebSocketEventEventArgs e)
{
try
{
switch (e.Type)
{
//Global
case "READY": //Resync
{
var data = e.Payload.ToObject<ReadyEvent>(_serializer);
//SessionId = data.SessionId; //SessionId = data.SessionId;
PrivateUser = new User(data.User.Id, null);
PrivateUser = new User(this, data.User.Id, null);
PrivateUser.Update(data.User); PrivateUser.Update(data.User);
CurrentUser.Update(data.User); CurrentUser.Update(data.User);
foreach (var model in data.Guilds) foreach (var model in data.Guilds)
{
if (model.Unavailable != true)
{
var server = AddServer(model.Id);
server.Update(model);
}
}
foreach (var model in data.PrivateChannels)
{
if (model.Unavailable != true)
{
var server = AddServer(model.Id);
server.Update(model);
}
}
foreach (var model in data.PrivateChannels)
{ {
var channel = AddChannel(model.Id, null, model.Recipient.Id); var channel = AddChannel(model.Id, null, model.Recipient.Id);
channel.Update(model);
}
}
break;

//Servers
case "GUILD_CREATE":
{
var data = e.Payload.ToObject<GuildCreateEvent>(_serializer);
if (data.Unavailable != true)
{
var server = AddServer(data.Id);
server.Update(data);
channel.Update(model);
}
}
break;
//Servers
case "GUILD_CREATE":
{
var data = e.Payload.ToObject<GuildCreateEvent>(_serializer);
if (data.Unavailable != true)
{
var server = AddServer(data.Id);
server.Update(data);
if (data.Unavailable != false) if (data.Unavailable != false)
{ {
Logger.Info($"Server Created: {server.Name}"); Logger.Info($"Server Created: {server.Name}");
@@ -324,26 +485,26 @@ namespace Discord
Logger.Info($"Server Available: {server.Name}"); Logger.Info($"Server Available: {server.Name}");
OnServerAvailable(server); OnServerAvailable(server);
} }
}
break;
case "GUILD_UPDATE":
{
var data = e.Payload.ToObject<GuildUpdateEvent>(_serializer);
var server = GetServer(data.Id);
}
break;
case "GUILD_UPDATE":
{
var data = e.Payload.ToObject<GuildUpdateEvent>(_serializer);
var server = GetServer(data.Id);
if (server != null) if (server != null)
{
server.Update(data);
{
server.Update(data);
Logger.Info($"Server Updated: {server.Name}"); Logger.Info($"Server Updated: {server.Name}");
OnServerUpdated(server); OnServerUpdated(server);
}
}
break;
case "GUILD_DELETE":
{
var data = e.Payload.ToObject<GuildDeleteEvent>(_serializer);
}
}
break;
case "GUILD_DELETE":
{
var data = e.Payload.ToObject<GuildDeleteEvent>(_serializer);
Server server = RemoveServer(data.Id); Server server = RemoveServer(data.Id);
if (server != null)
{
if (server != null)
{
if (data.Unavailable != true) if (data.Unavailable != true)
Logger.Info($"Server Destroyed: {server.Name}"); Logger.Info($"Server Destroyed: {server.Name}");
else else
@@ -353,61 +514,61 @@ namespace Discord
if (data.Unavailable != true) if (data.Unavailable != true)
OnLeftServer(server); OnLeftServer(server);
} }
}
break;
}
break;


//Channels
case "CHANNEL_CREATE":
{
var data = e.Payload.ToObject<ChannelCreateEvent>(_serializer);
//Channels
case "CHANNEL_CREATE":
{
var data = e.Payload.ToObject<ChannelCreateEvent>(_serializer);
Channel channel = AddChannel(data.Id, data.GuildId, data.Recipient.Id); Channel channel = AddChannel(data.Id, data.GuildId, data.Recipient.Id);
channel.Update(data);
channel.Update(data);
Logger.Info($"Channel Created: {channel.Server?.Name ?? "[Private]"}/{channel.Name}"); Logger.Info($"Channel Created: {channel.Server?.Name ?? "[Private]"}/{channel.Name}");
OnChannelCreated(channel); OnChannelCreated(channel);
}
break;
case "CHANNEL_UPDATE":
{
var data = e.Payload.ToObject<ChannelUpdateEvent>(_serializer);
}
break;
case "CHANNEL_UPDATE":
{
var data = e.Payload.ToObject<ChannelUpdateEvent>(_serializer);
var channel = GetChannel(data.Id); var channel = GetChannel(data.Id);
if (channel != null)
{
channel.Update(data);
if (channel != null)
{
channel.Update(data);
Logger.Info($"Channel Updated: {channel.Server?.Name ?? "[Private]"}/{channel.Name}"); Logger.Info($"Channel Updated: {channel.Server?.Name ?? "[Private]"}/{channel.Name}");
OnChannelUpdated(channel); OnChannelUpdated(channel);
}
}
break;
case "CHANNEL_DELETE":
{
var data = e.Payload.ToObject<ChannelDeleteEvent>(_serializer);
}
}
break;
case "CHANNEL_DELETE":
{
var data = e.Payload.ToObject<ChannelDeleteEvent>(_serializer);
var channel = RemoveChannel(data.Id); var channel = RemoveChannel(data.Id);
if (channel != null) if (channel != null)
{ {
Logger.Info($"Channel Destroyed: {channel.Server?.Name ?? "[Private]"}/{channel.Name}"); Logger.Info($"Channel Destroyed: {channel.Server?.Name ?? "[Private]"}/{channel.Name}");
OnChannelDestroyed(channel); OnChannelDestroyed(channel);
} }
}
break;
}
break;


//Members
case "GUILD_MEMBER_ADD":
{
var data = e.Payload.ToObject<GuildMemberAddEvent>(_serializer);
//Members
case "GUILD_MEMBER_ADD":
{
var data = e.Payload.ToObject<GuildMemberAddEvent>(_serializer);
var server = GetServer(data.GuildId.Value); var server = GetServer(data.GuildId.Value);
if (server != null) if (server != null)
{ {
var user = server.AddMember(data.User.Id);
var user = server.AddUser(data.User.Id);
user.Update(data); user.Update(data);
user.UpdateActivity(); user.UpdateActivity();
Logger.Info($"User Joined: {server.Name}/{user.Name}"); Logger.Info($"User Joined: {server.Name}/{user.Name}");
OnUserJoined(user); OnUserJoined(user);
} }
}
break;
case "GUILD_MEMBER_UPDATE":
{
var data = e.Payload.ToObject<GuildMemberUpdateEvent>(_serializer);
}
break;
case "GUILD_MEMBER_UPDATE":
{
var data = e.Payload.ToObject<GuildMemberUpdateEvent>(_serializer);
var server = GetServer(data.GuildId.Value); var server = GetServer(data.GuildId.Value);
if (server != null) if (server != null)
{ {
@@ -419,43 +580,43 @@ namespace Discord
OnUserUpdated(user); OnUserUpdated(user);
} }
} }
}
break;
case "GUILD_MEMBER_REMOVE":
{
var data = e.Payload.ToObject<GuildMemberRemoveEvent>(_serializer);
}
break;
case "GUILD_MEMBER_REMOVE":
{
var data = e.Payload.ToObject<GuildMemberRemoveEvent>(_serializer);
var server = GetServer(data.GuildId.Value); var server = GetServer(data.GuildId.Value);
if (server != null) if (server != null)
{ {
var user = server.RemoveMember(data.User.Id);
var user = server.RemoveUser(data.User.Id);
if (user != null) if (user != null)
{ {
Logger.Info($"User Left: {server.Name}/{user.Name}"); Logger.Info($"User Left: {server.Name}/{user.Name}");
OnUserLeft(user); OnUserLeft(user);
} }
} }
}
break;
case "GUILD_MEMBERS_CHUNK":
{
var data = e.Payload.ToObject<GuildMembersChunkEvent>(_serializer);
}
break;
case "GUILD_MEMBERS_CHUNK":
{
var data = e.Payload.ToObject<GuildMembersChunkEvent>(_serializer);
foreach (var memberData in data.Members) foreach (var memberData in data.Members)
{ {
var server = GetServer(memberData.GuildId.Value); var server = GetServer(memberData.GuildId.Value);
if (server != null) if (server != null)
{ {
var user = server.AddMember(memberData.User.Id);
var user = server.AddUser(memberData.User.Id);
user.Update(memberData); user.Update(memberData);
//OnUserAdded(user); //OnUserAdded(user);
} }
} }
} }
break;
break;


//Roles
case "GUILD_ROLE_CREATE":
{
var data = e.Payload.ToObject<GuildRoleCreateEvent>(_serializer);
//Roles
case "GUILD_ROLE_CREATE":
{
var data = e.Payload.ToObject<GuildRoleCreateEvent>(_serializer);
var server = GetServer(data.GuildId); var server = GetServer(data.GuildId);
if (server != null) if (server != null)
{ {
@@ -464,11 +625,11 @@ namespace Discord
Logger.Info($"Role Created: {server.Name}/{role.Name}"); Logger.Info($"Role Created: {server.Name}/{role.Name}");
OnRoleUpdated(role); OnRoleUpdated(role);
} }
}
break;
case "GUILD_ROLE_UPDATE":
{
var data = e.Payload.ToObject<GuildRoleUpdateEvent>(_serializer);
}
break;
case "GUILD_ROLE_UPDATE":
{
var data = e.Payload.ToObject<GuildRoleUpdateEvent>(_serializer);
var server = GetServer(data.GuildId); var server = GetServer(data.GuildId);
if (server != null) if (server != null)
{ {
@@ -480,11 +641,11 @@ namespace Discord
OnRoleUpdated(role); OnRoleUpdated(role);
} }
} }
}
break;
case "GUILD_ROLE_DELETE":
{
var data = e.Payload.ToObject<GuildRoleDeleteEvent>(_serializer);
}
break;
case "GUILD_ROLE_DELETE":
{
var data = e.Payload.ToObject<GuildRoleDeleteEvent>(_serializer);
var server = GetServer(data.GuildId); var server = GetServer(data.GuildId);
if (server != null) if (server != null)
{ {
@@ -495,41 +656,41 @@ namespace Discord
OnRoleDeleted(role); OnRoleDeleted(role);
} }
} }
}
break;
//Bans
case "GUILD_BAN_ADD":
{
var data = e.Payload.ToObject<GuildBanAddEvent>(_serializer);
var server = GetServer(data.GuildId);
}
break;
//Bans
case "GUILD_BAN_ADD":
{
var data = e.Payload.ToObject<GuildBanAddEvent>(_serializer);
var server = GetServer(data.GuildId);
if (server != null) if (server != null)
{
{
server.AddBan(data.UserId); server.AddBan(data.UserId);
Logger.Info($"User Banned: {server.Name}/{data.UserId}"); Logger.Info($"User Banned: {server.Name}/{data.UserId}");
OnUserBanned(server, data.UserId); OnUserBanned(server, data.UserId);
}
}
break;
case "GUILD_BAN_REMOVE":
{
var data = e.Payload.ToObject<GuildBanRemoveEvent>(_serializer);
var server = GetServer(data.GuildId);
}
}
break;
case "GUILD_BAN_REMOVE":
{
var data = e.Payload.ToObject<GuildBanRemoveEvent>(_serializer);
var server = GetServer(data.GuildId);
if (server != null) if (server != null)
{
{
if (server.RemoveBan(data.UserId)) if (server.RemoveBan(data.UserId))
{ {
Logger.Info($"User Unbanned: {server.Name}/{data.UserId}"); Logger.Info($"User Unbanned: {server.Name}/{data.UserId}");
OnUserUnbanned(server, data.UserId); OnUserUnbanned(server, data.UserId);
} }
}
}
break;
}
}
break;


//Messages
case "MESSAGE_CREATE":
{
var data = e.Payload.ToObject<MessageCreateEvent>(_serializer);
//Messages
case "MESSAGE_CREATE":
{
var data = e.Payload.ToObject<MessageCreateEvent>(_serializer);


Channel channel = GetChannel(data.ChannelId); Channel channel = GetChannel(data.ChannelId);
if (channel != null) if (channel != null)
@@ -548,7 +709,7 @@ namespace Discord
msg = channel.AddMessage(data.Id, data.Author.Id, data.Timestamp.Value); msg = channel.AddMessage(data.Id, data.Author.Id, data.Timestamp.Value);
//nonce = 0; //nonce = 0;
} }
msg.Update(data); msg.Update(data);
var user = msg.User; var user = msg.User;
if (user != null) if (user != null)
@@ -566,11 +727,11 @@ namespace Discord
Logger.Info($"Message Received: {channel.Server?.Name ?? "[Private]"}/{channel.Name}/{msg.Id}"); Logger.Info($"Message Received: {channel.Server?.Name ?? "[Private]"}/{channel.Name}/{msg.Id}");
OnMessageReceived(msg); OnMessageReceived(msg);
} }
}
break;
case "MESSAGE_UPDATE":
{
var data = e.Payload.ToObject<MessageUpdateEvent>(_serializer);
}
break;
case "MESSAGE_UPDATE":
{
var data = e.Payload.ToObject<MessageUpdateEvent>(_serializer);
var channel = GetChannel(data.ChannelId); var channel = GetChannel(data.ChannelId);
if (channel != null) if (channel != null)
{ {
@@ -583,11 +744,11 @@ namespace Discord
OnMessageUpdated(msg); OnMessageUpdated(msg);
} }
} }
}
break;
case "MESSAGE_DELETE":
{
var data = e.Payload.ToObject<MessageDeleteEvent>(_serializer);
}
break;
case "MESSAGE_DELETE":
{
var data = e.Payload.ToObject<MessageDeleteEvent>(_serializer);
var channel = GetChannel(data.ChannelId); var channel = GetChannel(data.ChannelId);
if (channel != null) if (channel != null)
{ {
@@ -598,11 +759,11 @@ namespace Discord
OnMessageDeleted(msg); OnMessageDeleted(msg);
} }
} }
}
break;
case "MESSAGE_ACK":
{
var data = e.Payload.ToObject<MessageAckEvent>(_serializer);
}
break;
case "MESSAGE_ACK":
{
var data = e.Payload.ToObject<MessageAckEvent>(_serializer);
var channel = GetChannel(data.ChannelId); var channel = GetChannel(data.ChannelId);
if (channel != null) if (channel != null)
{ {
@@ -613,13 +774,13 @@ namespace Discord
OnMessageAcknowledged(msg); OnMessageAcknowledged(msg);
} }
} }
}
break;
}
break;


//Statuses
case "PRESENCE_UPDATE":
{
var data = e.Payload.ToObject<PresenceUpdateEvent>(_serializer);
//Statuses
case "PRESENCE_UPDATE":
{
var data = e.Payload.ToObject<PresenceUpdateEvent>(_serializer);
User user; User user;
Server server; Server server;
if (data.GuildId == null) if (data.GuildId == null)
@@ -633,20 +794,20 @@ namespace Discord
user = server?.GetUser(data.User.Id); user = server?.GetUser(data.User.Id);
} }


if (user != null)
{
user.Update(data);
if (user != null)
{
user.Update(data);
Logger.Verbose($"Presence Updated: {server.Name}/{user.Name}"); Logger.Verbose($"Presence Updated: {server.Name}/{user.Name}");
OnUserPresenceUpdated(user); OnUserPresenceUpdated(user);
}
}
break;
case "TYPING_START":
{
var data = e.Payload.ToObject<TypingStartEvent>(_serializer);
var channel = GetChannel(data.ChannelId);
if (channel != null)
{
}
}
break;
case "TYPING_START":
{
var data = e.Payload.ToObject<TypingStartEvent>(_serializer);
var channel = GetChannel(data.ChannelId);
if (channel != null)
{
User user; User user;
if (channel.IsPrivate) if (channel.IsPrivate)
{ {
@@ -657,20 +818,20 @@ namespace Discord
} }
else else
user = channel.Server.GetUser(data.UserId); user = channel.Server.GetUser(data.UserId);
if (user != null)
if (user != null)
{ {
Logger.Verbose($"Is Typing: {channel.Server?.Name ?? "[Private]"}/{channel.Name}/{user.Name}"); Logger.Verbose($"Is Typing: {channel.Server?.Name ?? "[Private]"}/{channel.Name}/{user.Name}");
OnUserIsTypingUpdated(channel, user);
user.UpdateActivity();
OnUserIsTypingUpdated(channel, user);
user.UpdateActivity();
} }
} }
}
break;
}
break;


//Voice
case "VOICE_STATE_UPDATE":
{
var data = e.Payload.ToObject<VoiceStateUpdateEvent>(_serializer);
//Voice
case "VOICE_STATE_UPDATE":
{
var data = e.Payload.ToObject<VoiceStateUpdateEvent>(_serializer);
var server = GetServer(data.GuildId); var server = GetServer(data.GuildId);
if (server != null) if (server != null)
{ {
@@ -682,126 +843,46 @@ namespace Discord
OnUserVoiceStateUpdated(user); OnUserVoiceStateUpdated(user);
} }
} }
}
break;
}
break;


//Settings
case "USER_UPDATE":
{
var data = e.Payload.ToObject<UserUpdateEvent>(_serializer);
//Settings
case "USER_UPDATE":
{
var data = e.Payload.ToObject<UserUpdateEvent>(_serializer);
if (data.Id == CurrentUser.Id) if (data.Id == CurrentUser.Id)
{ {
CurrentUser.Update(data); CurrentUser.Update(data);
PrivateUser.Update(data); PrivateUser.Update(data);
foreach (var server in _servers)
server.Value.CurrentUser.Update(data);
foreach (var server in _servers)
server.Value.CurrentUser.Update(data);
Logger.Info("Profile Updated"); Logger.Info("Profile Updated");
OnProfileUpdated(CurrentUser); OnProfileUpdated(CurrentUser);
}
}
break;

//Ignored
case "USER_SETTINGS_UPDATE":
case "GUILD_INTEGRATIONS_UPDATE":
case "VOICE_SERVER_UPDATE":
break;
case "RESUMED": //Handled in DataWebSocket
break;

//Others
default:
Logger.Warning($"Unknown message type: {e.Type}");
break;
}
}
catch (Exception ex)
{
Logger.Error($"Error handling {e.Type} event", ex);
}
}
}
}
break;


public Task SetStatus(UserStatus status)
{
if (status == null) throw new ArgumentNullException(nameof(status));
if (status != UserStatus.Online && status != UserStatus.Idle)
throw new ArgumentException($"Invalid status, must be {UserStatus.Online} or {UserStatus.Idle}", nameof(status));
CheckReady();
//Ignored
case "USER_SETTINGS_UPDATE":
case "GUILD_INTEGRATIONS_UPDATE":
case "VOICE_SERVER_UPDATE":
break;


Status = status;
return SendStatus();
}
public Task SetGame(int? gameId)
{
CheckReady();
case "RESUMED": //Handled in DataWebSocket
break;


CurrentGameId = gameId;
return SendStatus();
}
private Task SendStatus()
{
GatewaySocket.SendUpdateStatus(Status == UserStatus.Idle ? EpochTime.GetMilliseconds() - (10 * 60 * 1000) : (long?)null, CurrentGameId);
return TaskHelper.CompletedTask;
}

private Server AddServer(ulong id)
=> _servers.GetOrAdd(id, x => new Server(this, x));
private Server RemoveServer(ulong id)
{
Server server;
_servers.TryRemove(id, out server);
return server;
}
public Server GetServer(ulong id)
{
Server server;
_servers.TryGetValue(id, out server);
return server;
}

private Channel AddChannel(ulong id, ulong? guildId, ulong? recipientId)
{
Channel channel;
if (recipientId != null)
{
channel = _privateChannels.GetOrAdd(recipientId.Value,
x => new Channel(this, x, new User(recipientId.Value, null)));
}
else
{
var server = GetServer(guildId.Value);
channel = server.AddChannel(id);
//Others
default:
Logger.Warning($"Unknown message type: {e.Type}");
break;
}
} }
_channels[channel.Id] = channel;
return channel;
}
private Channel RemoveChannel(ulong id)
{
Channel channel;
if (_channels.TryRemove(id, out channel))
catch (Exception ex)
{ {
if (channel.IsPrivate)
_privateChannels.TryRemove(channel.Recipient.Id, out channel);
else
channel.Server.RemoveChannel(id);
Logger.Error($"Error handling {e.Type} event", ex);
} }
return channel;
}
internal Channel GetChannel(ulong id)
{
Channel channel;
_channels.TryGetValue(id, out channel);
return channel;
}
internal Channel GetPrivateChannel(ulong recipientId)
{
Channel channel;
_privateChannels.TryGetValue(recipientId, out channel);
return channel;
} }



#region Async Wrapper #region Async Wrapper
/// <summary> Blocking call that will not return until client has been stopped. This is mainly intended for use in console applications. </summary> /// <summary> Blocking call that will not return until client has been stopped. This is mainly intended for use in console applications. </summary>
public void Run(Func<Task> asyncAction) public void Run(Func<Task> asyncAction)
@@ -843,19 +924,6 @@ namespace Discord
#endregion #endregion


//Helpers //Helpers
private void CheckReady()
{
switch (State)
{
case ConnectionState.Disconnecting:
throw new InvalidOperationException("The client is disconnecting.");
case ConnectionState.Disconnected:
throw new InvalidOperationException("The client is not connected to Discord");
case ConnectionState.Connecting:
throw new InvalidOperationException("The client is connecting.");
}
}

private string GetTokenCachePath(string email) private string GetTokenCachePath(string email)
{ {
using (var md5 = MD5.Create()) using (var md5 = MD5.Create())
@@ -922,29 +990,7 @@ namespace Discord
} }
} }


private static string Base64Image(ImageType type, Stream stream, string existingId)
{
if (type == ImageType.None)
return null;
else if (stream != null)
{
byte[] bytes = new byte[stream.Length - stream.Position];
stream.Read(bytes, 0, bytes.Length);

string base64 = Convert.ToBase64String(bytes);
string imageType = type == ImageType.Jpeg ? "image/jpeg;base64" : "image/png;base64";
return $"data:{imageType},{base64}";
}
return existingId;
}


public Region GetRegion(string regionName)
{
Region region;
if (_regions.TryGetValue(regionName, out region))
return region;
else
return _unknownRegion;
}
} }
} }

+ 102
- 2
src/Discord.Net/Extensions.cs View File

@@ -1,6 +1,9 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;


namespace Discord namespace Discord
@@ -13,8 +16,10 @@ namespace Discord
=> ulong.Parse(value, NumberStyles.None, _format); => ulong.Parse(value, NumberStyles.None, _format);
public static ulong? ToNullableId(this string value) public static ulong? ToNullableId(this string value)
=> value == null ? (ulong?)null : ulong.Parse(value, NumberStyles.None, _format); => value == null ? (ulong?)null : ulong.Parse(value, NumberStyles.None, _format);
public static string ToIdString(this ulong value)
public static bool TryToId(this string value, out ulong result)
=> ulong.TryParse(value, NumberStyles.None, _format, out result);

public static string ToIdString(this ulong value)
=> value.ToString(_format); => value.ToString(_format);
public static string ToIdString(this ulong? value) public static string ToIdString(this ulong? value)
=> value?.ToString(_format); => value?.ToString(_format);
@@ -33,5 +38,100 @@ namespace Discord
return true; return true;
} }
} }
public static IEnumerable<Channel> Find(this IEnumerable<Channel> channels, string name, ChannelType type = null, bool exactMatch = false)
{
//Search by name
var query = channels
.Where(x => string.Equals(x.Name, name, exactMatch ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase));

if (!exactMatch)
{
if (name.Length >= 2 && name[0] == '<' && name[1] == '#' && name[name.Length - 1] == '>') //Search by raw mention
{
ulong id;
if (name.Substring(2, name.Length - 3).TryToId(out id))
{
var channel = channels.Where(x => x.Id == id).FirstOrDefault();
if (channel != null)
query = query.Concat(new Channel[] { channel });
}
}
if (name.Length >= 1 && name[0] == '#' && (type == null || type == ChannelType.Text)) //Search by clean mention
{
string name2 = name.Substring(1);
query = query.Concat(channels
.Where(x => x.Type == ChannelType.Text && string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)));
}
}

if (type != null)
query = query.Where(x => x.Type == type);
return query;
}

public static IEnumerable<User> Find(this IEnumerable<User> users,
string name, ushort? discriminator = null, bool exactMatch = false)
{
//Search by name
var query = users
.Where(x => string.Equals(x.Name, name, exactMatch ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase));

if (!exactMatch)
{
if (name.Length >= 2 && name[0] == '<' && name[1] == '@' && name[name.Length - 1] == '>') //Search by raw mention
{
ulong id;
if (name.Substring(2, name.Length - 3).TryToId(out id))
{
var user = users.Where(x => x.Id == id).FirstOrDefault();
if (user != null)
query = query.Concat(new User[] { user });
}
}
if (name.Length >= 1 && name[0] == '@') //Search by clean mention
{
string name2 = name.Substring(1);
query = query.Concat(users
.Where(x => string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)));
}
}

if (discriminator != null)
query = query.Where(x => x.Discriminator == discriminator.Value);
return query;
}

public static IEnumerable<Role> Find(this IEnumerable<Role> roles, string name, bool exactMatch = false)
{
// if (name.StartsWith("@"))
// {
// string name2 = name.Substring(1);
// return _roles.Where(x => x.Server.Id == server.Id &&
// string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) ||
// string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase));
// }
// else
return roles.Where(x => string.Equals(x.Name, name, exactMatch ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase));
}

public static IEnumerable<Server> Find(this IEnumerable<Server> servers, string name, bool exactMatch = false)
=> servers.Where(x => string.Equals(x.Name, name, exactMatch ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase));

public static string Base64(this Stream stream, ImageType type, string existingId)
{
if (type == ImageType.None)
return null;
else if (stream != null)
{
byte[] bytes = new byte[stream.Length - stream.Position];
stream.Read(bytes, 0, bytes.Length);

string base64 = Convert.ToBase64String(bytes);
string imageType = type == ImageType.Jpeg ? "image/jpeg;base64" : "image/png;base64";
return $"data:{imageType},{base64}";
}
return existingId;
}
} }
} }

+ 7
- 0
src/Discord.Net/IService.cs View File

@@ -0,0 +1,7 @@
namespace Discord
{
public interface IService
{
void Install(DiscordClient client);
}
}

+ 0
- 95
src/Discord.Net/Mention.cs View File

@@ -1,95 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace Discord
{
public static class Mention
{
private static readonly Regex _userRegex = new Regex(@"<@([0-9]+)>", RegexOptions.Compiled);
private static readonly Regex _channelRegex = new Regex(@"<#([0-9]+)>", RegexOptions.Compiled);
private static readonly Regex _roleRegex = new Regex(@"@everyone", RegexOptions.Compiled);
/// <summary> Returns the string used to create a user mention. </summary>
[Obsolete("Use User.Mention instead")]
public static string User(User user)
=> $"<@{user.Id}>";
/// <summary> Returns the string used to create a channel mention. </summary>
[Obsolete("Use Channel.Mention instead")]
public static string Channel(Channel channel)
=> $"<#{channel.Id}>";
/// <summary> Returns the string used to create a mention to everyone in a channel. </summary>
[Obsolete("Use Server.EveryoneRole.Mention instead")]
public static string Everyone()
=> $"@everyone";

internal static string CleanUserMentions(DiscordClient client, Channel channel, string text, List<User> users = null)
{
return _userRegex.Replace(text, new MatchEvaluator(e =>
{
var id = e.Value.Substring(2, e.Value.Length - 3).ToId();
var user = channel.GetUser(id);
if (user != null)
{
if (users != null)
users.Add(user);
return '@' + user.Name;
}
else //User not found
return '@' + e.Value;
}));
}
internal static string CleanChannelMentions(DiscordClient client, Channel channel, string text, List<Channel> channels = null)
{
var server = channel.Server;
if (server == null) return text;

return _channelRegex.Replace(text, new MatchEvaluator(e =>
{
var id = e.Value.Substring(2, e.Value.Length - 3).ToId();
var mentionedChannel = server.GetChannel(id);
if (mentionedChannel != null && mentionedChannel.Server.Id == server.Id)
{
if (channels != null)
channels.Add(mentionedChannel);
return '#' + mentionedChannel.Name;
}
else //Channel not found
return '#' + e.Value;
}));
}
/*internal static string CleanRoleMentions(DiscordClient client, User user, Channel channel, string text, List<Role> roles = null)
{
var server = channel.Server;
if (server == null) return text;

return _roleRegex.Replace(text, new MatchEvaluator(e =>
{
if (roles != null && user.GetPermissions(channel).MentionEveryone)
roles.Add(server.EveryoneRole);
return e.Value;
}));
}*/

/// <summary>Resolves all mentions in a provided string to those users, channels or roles' names.</summary>
public static string Resolve(Message source, string text)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (text == null) throw new ArgumentNullException(nameof(text));

return Resolve(source.Channel, text);
}

/// <summary>Resolves all mentions in a provided string to those users, channels or roles' names.</summary>
public static string Resolve(Channel channel, string text)
{
if (text == null) throw new ArgumentNullException(nameof(text));

var client = channel.Client;
text = CleanUserMentions(client, channel, text);
text = CleanChannelMentions(client, channel, text);
//text = CleanRoleMentions(_client, channel, text);
return text;
}
}
}

+ 292
- 21
src/Discord.Net/Models/Channel.cs View File

@@ -1,8 +1,13 @@
using Discord.API.Client; using Discord.API.Client;
using Discord.API.Client.Rest;
using Discord.Net;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Net;
using System.Threading.Tasks;
using APIChannel = Discord.API.Client.Channel; using APIChannel = Discord.API.Client.Channel;


namespace Discord namespace Discord
@@ -38,9 +43,9 @@ namespace Discord
private readonly ConcurrentDictionary<ulong, Member> _users; private readonly ConcurrentDictionary<ulong, Member> _users;
private readonly ConcurrentDictionary<ulong, Message> _messages; private readonly ConcurrentDictionary<ulong, Message> _messages;
private Dictionary<ulong, PermissionOverwrite> _permissionOverwrites; private Dictionary<ulong, PermissionOverwrite> _permissionOverwrites;

/// <summary> Gets the client that generated this channel object. </summary>
internal DiscordClient Client { get; } internal DiscordClient Client { get; }

/// <summary> Gets the unique identifier for this channel. </summary> /// <summary> Gets the unique identifier for this channel. </summary>
public ulong Id { get; } public ulong Id { get; }
/// <summary> Gets the server owning this channel, if this is a public chat. </summary> /// <summary> Gets the server owning this channel, if this is a public chat. </summary>
@@ -149,32 +154,85 @@ namespace Discord
} }
} }


//Members
internal void AddUser(User user)
/// <summary> Edits this channel, changing only non-null attributes. </summary>
public async Task Edit(string name = null, string topic = null, int? position = null)
{ {
if (!Client.Config.UsePermissionsCache)
return;
if (name != null || topic != null)
{
var request = new UpdateChannelRequest(Id)
{
Name = name ?? Name,
Topic = topic ?? Topic,
Position = Position
};
await Client.ClientAPI.Send(request).ConfigureAwait(false);
}


var member = new Member(user);
if (_users.TryAdd(user.Id, member))
UpdatePermissions(user, member.Permissions);
if (position != null)
{
Channel[] channels = Server.Channels.Where(x => x.Type == Type).OrderBy(x => x.Position).ToArray();
int oldPos = Array.IndexOf(channels, this);
var newPosChannel = channels.Where(x => x.Position > position).FirstOrDefault();
int newPos = (newPosChannel != null ? Array.IndexOf(channels, newPosChannel) : channels.Length) - 1;
if (newPos < 0)
newPos = 0;
int minPos;

if (oldPos < newPos) //Moving Down
{
minPos = oldPos;
for (int i = oldPos; i < newPos; i++)
channels[i] = channels[i + 1];
channels[newPos] = this;
}
else //(oldPos > newPos) Moving Up
{
minPos = newPos;
for (int i = oldPos; i > newPos; i--)
channels[i] = channels[i - 1];
channels[newPos] = this;
}
Channel after = minPos > 0 ? channels.Skip(minPos - 1).FirstOrDefault() : null;
await Server.ReorderChannels(channels.Skip(minPos), after).ConfigureAwait(false);
}
} }
internal void RemoveUser(ulong id)
public async Task Delete()
{ {
if (!Client.Config.UsePermissionsCache)
return;

Member ignored;
_users.TryRemove(id, out ignored);
try { await Client.ClientAPI.Send(new DeleteChannelRequest(Id)).ConfigureAwait(false); }
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
} }
public User GetUser(ulong id)

#region Invites
/// <summary> Gets all active (non-expired) invites to this server. </summary>
public async Task<IEnumerable<Invite>> GetInvites()
=> (await Server.GetInvites()).Where(x => x.Channel.Id == Id);

/// <summary> Creates a new invite to this channel. </summary>
/// <param name="maxAge"> Time (in seconds) until the invite expires. Set to null to never expire. </param>
/// <param name="maxUses"> The max amount of times this invite may be used. Set to null to have unlimited uses. </param>
/// <param name="tempMembership"> If true, a user accepting this invite will be kicked from the server after closing their client. </param>
/// <param name="withXkcd"> If true, creates a human-readable link. Not supported if maxAge is set to null. </param>
public async Task<Invite> CreateInvite(int? maxAge = 1800, int? maxUses = null, bool tempMembership = false, bool withXkcd = false)
{ {
Member result;
_users.TryGetValue(id, out result);
return result.User;
if (maxAge < 0) throw new ArgumentOutOfRangeException(nameof(maxAge));
if (maxUses < 0) throw new ArgumentOutOfRangeException(nameof(maxUses));

var request = new CreateInviteRequest(Id)
{
MaxAge = maxAge ?? 0,
MaxUses = maxUses ?? 0,
IsTemporary = tempMembership,
WithXkcdPass = withXkcd
};

var response = await Client.ClientAPI.Send(request).ConfigureAwait(false);
var invite = new Invite(Client, response.Code, response.XkcdPass);
return invite;
} }
#endregion


//Messages
#region Messages
internal Message AddMessage(ulong id, ulong userId, DateTime timestamp) internal Message AddMessage(ulong id, ulong userId, DateTime timestamp)
{ {
Message message = new Message(id, this, userId); Message message = new Message(id, this, userId);
@@ -202,14 +260,117 @@ namespace Discord
} }
return null; return null;
} }

public Message GetMessage(ulong id) public Message GetMessage(ulong id)
{ {
Message result; Message result;
_messages.TryGetValue(id, out result); _messages.TryGetValue(id, out result);
return result; return result;
} }
public async Task<Message[]> DownloadMessages(int limit = 100, ulong? relativeMessageId = null,
RelativeDirection relativeDir = RelativeDirection.Before, bool useCache = true)
{
if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit));
if (limit == 0 || Type != ChannelType.Text) return new Message[0];
try
{
var request = new GetMessagesRequest(Id)
{
Limit = limit,
RelativeDir = relativeMessageId.HasValue ? relativeDir == RelativeDirection.Before ? "before" : "after" : null,
RelativeId = relativeMessageId ?? 0
};
var msgs = await Client.ClientAPI.Send(request).ConfigureAwait(false);
return msgs.Select(x =>
{
Message msg = null;
if (useCache)
{
msg = AddMessage(x.Id, x.Author.Id, x.Timestamp.Value);
var user = msg.User;
if (user != null)
user.UpdateActivity(msg.EditedTimestamp ?? msg.Timestamp);
}
else
msg = new Message(x.Id, this, x.Author.Id);
msg.Update(x);
return msg;
})
.ToArray();
}
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.Forbidden)
{
return new Message[0];
}
}

/// <summary> Returns all members of this channel with the specified name. </summary>
/// <remarks> Name formats supported: Name, @Name and &lt;@Id&gt;. Search is case-insensitive if exactMatch is false.</remarks>
public IEnumerable<User> FindUsers(string name, bool exactMatch = false)
{
if (name == null) throw new ArgumentNullException(nameof(name));

return _users.Select(x => x.Value.User).Find(name, exactMatch: exactMatch);
}

public Task<Message> SendMessage(string text)
{
if (text == null) throw new ArgumentNullException(nameof(text));
return SendMessageInternal(text, false);
}
public Task<Message> SendTTSMessage(string text)
{
if (text == null) throw new ArgumentNullException(nameof(text));
return SendMessageInternal(text, true);
}
private async Task<Message> SendMessageInternal(string text, bool isTTS)
{
Message msg = null;
var mentionedUsers = new List<User>();
text = Message.CleanUserMentions(this, text, mentionedUsers);
if (text.Length > DiscordConfig.MaxMessageSize)
throw new ArgumentOutOfRangeException(nameof(text), $"Message must be {DiscordConfig.MaxMessageSize} characters or less.");

if (Client.Config.UseMessageQueue)
Client.MessageQueue.QueueSend(Id, text, mentionedUsers.Select(x => x.Id).ToArray(), isTTS);
else
{
var request = new SendMessageRequest(Id)
{
Content = text,
MentionedUserIds = mentionedUsers.Select(x => x.Id).ToArray(),
Nonce = null,
IsTTS = isTTS
};
var model = await Client.ClientAPI.Send(request).ConfigureAwait(false);
msg = AddMessage(model.Id, model.Author.Id, model.Timestamp.Value);
msg.Update(model);
}
return msg;
}


//Permissions
public Task<Message> SendFile(string filePath)
=> SendFile(Path.GetFileName(filePath), File.OpenRead(filePath));
public async Task<Message> SendFile(string filename, Stream stream)
{
if (filename == null) throw new ArgumentNullException(nameof(filename));
if (stream == null) throw new ArgumentNullException(nameof(stream));

var request = new SendFileRequest(Id)
{
Filename = filename,
Stream = stream
};
var model = await Client.ClientAPI.Send(request).ConfigureAwait(false);

var msg = AddMessage(model.Id, model.Author.Id, model.Timestamp.Value);
msg.Update(model);
return msg;
}
#endregion

#region Permissions
internal void UpdatePermissions() internal void UpdatePermissions()
{ {
if (!Client.Config.UsePermissionsCache) if (!Client.Config.UsePermissionsCache)
@@ -291,6 +452,116 @@ namespace Discord
} }
} }


[Obsolete("Use Channel.GetPermissions")]
public DualChannelPermissions GetPermissionsRule(User user)
{
if (user == null) throw new ArgumentNullException(nameof(user));

return PermissionOverwrites
.Where(x => x.TargetType == PermissionTarget.User && x.TargetId == user.Id)
.Select(x => x.Permissions)
.FirstOrDefault();
}
[Obsolete("Use Channel.GetPermissions")]
public DualChannelPermissions GetPermissionsRule(Role role)
{
if (role == null) throw new ArgumentNullException(nameof(role));

return PermissionOverwrites
.Where(x => x.TargetType == PermissionTarget.Role && x.TargetId == role.Id)
.Select(x => x.Permissions)
.FirstOrDefault();
}

[Obsolete("Use Channel.SetPermissions")]
public Task AddPermissionsRule(User user, ChannelPermissions allow = null, ChannelPermissions deny = null)
{
if (user == null) throw new ArgumentNullException(nameof(user));

return AddPermissionsRule(user.Id, PermissionTarget.User, allow, deny);
}
[Obsolete("Use Channel.SetPermissions")]
public Task AddPermissionsRule(User user, DualChannelPermissions permissions = null)
{
if (user == null) throw new ArgumentNullException(nameof(user));

return AddPermissionsRule(user.Id, PermissionTarget.User, permissions?.Allow, permissions?.Deny);
}
[Obsolete("Use Channel.SetPermissions")]
public Task AddPermissionsRule(Role role, ChannelPermissions allow = null, ChannelPermissions deny = null)
{
if (role == null) throw new ArgumentNullException(nameof(role));

return AddPermissionsRule(role.Id, PermissionTarget.Role, allow, deny);
}
[Obsolete("Use Channel.SetPermissions")]
public Task AddPermissionsRule(Role role, DualChannelPermissions permissions = null)
{
if (role == null) throw new ArgumentNullException(nameof(role));

return AddPermissionsRule(role.Id, PermissionTarget.Role, permissions?.Allow, permissions?.Deny);
}
private Task AddPermissionsRule(ulong targetId, PermissionTarget targetType, ChannelPermissions allow = null, ChannelPermissions deny = null)
{
var request = new AddChannelPermissionsRequest(Id)
{
TargetId = targetId,
TargetType = targetType.Value,
Allow = allow?.RawValue ?? 0,
Deny = deny?.RawValue ?? 0
};
return Client.ClientAPI.Send(request);
}

[Obsolete("Use Channel.RemovePermissions")]
public Task RemovePermissionsRule(User user)
{
if (user == null) throw new ArgumentNullException(nameof(user));
return RemovePermissionsRule(user.Id, PermissionTarget.User);
}
[Obsolete("Use Channel.RemovePermissions")]
public Task RemovePermissionsRule(Role role)
{
if (role == null) throw new ArgumentNullException(nameof(role));
return RemovePermissionsRule(role.Id, PermissionTarget.Role);
}
private async Task RemovePermissionsRule(ulong userOrRoleId, PermissionTarget targetType)
{
try
{
var perms = PermissionOverwrites.Where(x => x.TargetType != targetType || x.TargetId != userOrRoleId).FirstOrDefault();
await Client.ClientAPI.Send(new RemoveChannelPermissionsRequest(Id, userOrRoleId)).ConfigureAwait(false);
}
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
}
#endregion

#region Users
internal void AddUser(User user)
{
if (!Client.Config.UsePermissionsCache)
return;

var member = new Member(user);
if (_users.TryAdd(user.Id, member))
UpdatePermissions(user, member.Permissions);
}
internal void RemoveUser(ulong id)
{
if (!Client.Config.UsePermissionsCache)
return;

Member ignored;
_users.TryRemove(id, out ignored);
}
public User GetUser(ulong id)
{
Member result;
_users.TryGetValue(id, out result);
return result.User;
}
#endregion

public override bool Equals(object obj) => obj is Channel && (obj as Channel).Id == Id; public override bool Equals(object obj) => obj is Channel && (obj as Channel).Id == Id;
public override int GetHashCode() => unchecked(Id.GetHashCode() + 5658); public override int GetHashCode() => unchecked(Id.GetHashCode() + 5658);
public override string ToString() => Name ?? Id.ToIdString(); public override string ToString() => Name ?? Id.ToIdString();


+ 22
- 5
src/Discord.Net/Models/Invite.cs View File

@@ -1,5 +1,9 @@
using Discord.API.Client; using Discord.API.Client;
using Discord.API.Client.Rest;
using Discord.Net;
using System; using System;
using System.Net;
using System.Threading.Tasks;
using APIInvite = Discord.API.Client.Invite; using APIInvite = Discord.API.Client.Invite;


namespace Discord namespace Discord
@@ -55,6 +59,10 @@ namespace Discord
} }
} }


private ulong _serverId, _channelId;

internal DiscordClient Client { get; }

/// <summary> Gets the unique code for this invite. </summary> /// <summary> Gets the unique code for this invite. </summary>
public string Code { get; } public string Code { get; }
/// <summary> Gets, if enabled, an alternative human-readable invite code. </summary> /// <summary> Gets, if enabled, an alternative human-readable invite code. </summary>
@@ -63,9 +71,9 @@ namespace Discord
/// <summary> Gets information about the server this invite is attached to. </summary> /// <summary> Gets information about the server this invite is attached to. </summary>
public ServerInfo Server { get; private set; } public ServerInfo Server { get; private set; }
/// <summary> Gets information about the channel this invite is attached to. </summary> /// <summary> Gets information about the channel this invite is attached to. </summary>
public ChannelInfo Channel { get; private set; }
/// <summary> Gets the time (in seconds) until the invite expires. </summary>
public int? MaxAge { get; private set; }
public ChannelInfo Channel { get; private set; }
/// <summary> Gets the time (in seconds) until the invite expires. </summary>
public int? MaxAge { get; private set; }
/// <summary> Gets the amount of times this invite has been used. </summary> /// <summary> Gets the amount of times this invite has been used. </summary>
public int Uses { get; private set; } public int Uses { get; private set; }
/// <summary> Gets the max amount of times this invite may be used. </summary> /// <summary> Gets the max amount of times this invite may be used. </summary>
@@ -80,8 +88,9 @@ namespace Discord
/// <summary> Returns a URL for this invite using XkcdCode if available or Id if not. </summary> /// <summary> Returns a URL for this invite using XkcdCode if available or Id if not. </summary>
public string Url => $"{DiscordConfig.InviteUrl}/{Code}"; public string Url => $"{DiscordConfig.InviteUrl}/{Code}";


internal Invite(string code, string xkcdPass)
internal Invite(DiscordClient client, string code, string xkcdPass)
{ {
Client = client;
Code = code; Code = code;
XkcdCode = xkcdPass; XkcdCode = xkcdPass;
} }
@@ -110,8 +119,16 @@ namespace Discord
if (model.CreatedAt != null) if (model.CreatedAt != null)
CreatedAt = model.CreatedAt.Value; CreatedAt = model.CreatedAt.Value;
} }
public async Task Delete()
{
try { await Client.ClientAPI.Send(new DeleteInviteRequest(Code)).ConfigureAwait(false); }
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
}
public Task Accept()
=> Client.ClientAPI.Send(new AcceptInviteRequest(Code));


public override bool Equals(object obj) => obj is Invite && (obj as Invite).Code == Code;
public override bool Equals(object obj) => obj is Invite && (obj as Invite).Code == Code;
public override int GetHashCode() => unchecked(Code.GetHashCode() + 9980); public override int GetHashCode() => unchecked(Code.GetHashCode() + 9980);
public override string ToString() => XkcdCode ?? Code; public override string ToString() => XkcdCode ?? Code;
} }


+ 128
- 12
src/Discord.Net/Models/Message.cs View File

@@ -1,9 +1,14 @@
using Newtonsoft.Json;
using Discord.API.Client.Rest;
using Discord.Net;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization; using Newtonsoft.Json.Serialization;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net;
using System.Reflection; using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using APIMessage = Discord.API.Client.Message; using APIMessage = Discord.API.Client.Message;


namespace Discord namespace Discord
@@ -16,8 +21,73 @@ namespace Discord
} }


public sealed class Message public sealed class Message
{
/*internal class ImportResolver : DefaultContractResolver
{
private static readonly Regex _userRegex = new Regex(@"<@([0-9]+)>", RegexOptions.Compiled);
private static readonly Regex _channelRegex = new Regex(@"<#([0-9]+)>", RegexOptions.Compiled);
private static readonly Regex _roleRegex = new Regex(@"@everyone", RegexOptions.Compiled);
private static readonly Attachment[] _initialAttachments = new Attachment[0];
private static readonly Embed[] _initialEmbeds = new Embed[0];

internal static string CleanUserMentions(Channel channel, string text, List<User> users = null)
{
return _userRegex.Replace(text, new MatchEvaluator(e =>
{
var id = e.Value.Substring(2, e.Value.Length - 3).ToId();
var user = channel.GetUser(id);
if (user != null)
{
if (users != null)
users.Add(user);
return '@' + user.Name;
}
else //User not found
return '@' + e.Value;
}));
}
internal static string CleanChannelMentions(Channel channel, string text, List<Channel> channels = null)
{
var server = channel.Server;
if (server == null) return text;

return _channelRegex.Replace(text, new MatchEvaluator(e =>
{
var id = e.Value.Substring(2, e.Value.Length - 3).ToId();
var mentionedChannel = server.GetChannel(id);
if (mentionedChannel != null && mentionedChannel.Server.Id == server.Id)
{
if (channels != null)
channels.Add(mentionedChannel);
return '#' + mentionedChannel.Name;
}
else //Channel not found
return '#' + e.Value;
}));
}
/*internal static string CleanRoleMentions(User user, Channel channel, string text, List<Role> roles = null)
{
var server = channel.Server;
if (server == null) return text;

return _roleRegex.Replace(text, new MatchEvaluator(e =>
{
if (roles != null && user.GetPermissions(channel).MentionEveryone)
roles.Add(server.EveryoneRole);
return e.Value;
}));
}*/
private static string Resolve(Channel channel, string text)
{
if (text == null) throw new ArgumentNullException(nameof(text));

var client = channel.Client;
text = CleanUserMentions(channel, text);
text = CleanChannelMentions(channel, text);
//text = CleanRoleMentions(Channel, text);
return text;
}

/*internal class ImportResolver : DefaultContractResolver
{ {
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{ {
@@ -33,7 +103,7 @@ namespace Discord
} }
}*/ }*/


public sealed class Attachment : File
public sealed class Attachment : File
{ {
/// <summary> Unique identifier for this file. </summary> /// <summary> Unique identifier for this file. </summary>
public string Id { get; internal set; } public string Id { get; internal set; }
@@ -89,11 +159,10 @@ namespace Discord
internal File() { } internal File() { }
} }


private static readonly Attachment[] _initialAttachments = new Attachment[0];
private static readonly Embed[] _initialEmbeds = new Embed[0];

private readonly ulong _userId; private readonly ulong _userId;


internal DiscordClient Client => Channel.Client;

/// <summary> Returns the unique identifier for this message. </summary> /// <summary> Returns the unique identifier for this message. </summary>
public ulong Id { get; } public ulong Id { get; }
/// <summary> Returns the channel this message was sent to. </summary> /// <summary> Returns the channel this message was sent to. </summary>
@@ -117,7 +186,7 @@ namespace Discord
public Attachment[] Attachments { get; private set; } public Attachment[] Attachments { get; private set; }
/// <summary> Returns a collection of all embeded content in this message. </summary> /// <summary> Returns a collection of all embeded content in this message. </summary>
public Embed[] Embeds { get; private set; } public Embed[] Embeds { get; private set; }
/// <summary> Returns a collection of all users mentioned in this message. </summary> /// <summary> Returns a collection of all users mentioned in this message. </summary>
public IEnumerable<User> MentionedUsers { get; internal set; } public IEnumerable<User> MentionedUsers { get; internal set; }
/// <summary> Returns a collection of all channels mentioned in this message. </summary> /// <summary> Returns a collection of all channels mentioned in this message. </summary>
@@ -210,11 +279,11 @@ namespace Discord
//var mentionedUsers = new List<User>(); //var mentionedUsers = new List<User>();
var mentionedChannels = new List<Channel>(); var mentionedChannels = new List<Channel>();
//var mentionedRoles = new List<Role>(); //var mentionedRoles = new List<Role>();
text = Mention.CleanUserMentions(Channel.Client, channel, text/*, mentionedUsers*/);
text = CleanUserMentions(Channel, text/*, mentionedUsers*/);
if (server != null) if (server != null)
{ {
text = Mention.CleanChannelMentions(Channel.Client, channel, text, mentionedChannels);
//text = Mention.CleanRoleMentions(_client, User, channel, text, mentionedRoles);
text = CleanChannelMentions(Channel, text, mentionedChannels);
//text = CleanRoleMentions(_client, User, channel, text, mentionedRoles);
} }
Text = text; Text = text;


@@ -236,7 +305,54 @@ namespace Discord
} }
} }


public override bool Equals(object obj) => obj is Message && (obj as Message).Id == Id;
public async Task Edit(string text)
{
if (text == null) throw new ArgumentNullException(nameof(text));

var channel = Channel;
var mentionedUsers = new List<User>();
if (!channel.IsPrivate)
text = CleanUserMentions(channel, text, mentionedUsers);

if (text.Length > DiscordConfig.MaxMessageSize)
throw new ArgumentOutOfRangeException(nameof(text), $"Message must be {DiscordConfig.MaxMessageSize} characters or less.");

if (Client.Config.UseMessageQueue)
Client.MessageQueue.QueueEdit(channel.Id, Id, text, mentionedUsers.Select(x => x.Id).ToArray());
else
{
var request = new UpdateMessageRequest(Channel.Id, Id)
{
Content = text,
MentionedUserIds = mentionedUsers.Select(x => x.Id).ToArray()
};
await Client.ClientAPI.Send(request).ConfigureAwait(false);
}
}

public async Task Delete()
{
var request = new DeleteMessageRequest(Channel.Id, Id);
try { await Client.ClientAPI.Send(request).ConfigureAwait(false); }
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
}
public Task Acknowledge()
{
if (_userId != Client.CurrentUser.Id)
return Client.ClientAPI.Send(new AckMessageRequest(Channel.Id, Id));
else
return TaskHelper.CompletedTask;
}

/// <summary>Resolves all mentions in a provided string to those users, channels or roles' names.</summary>
public string Resolve(string text)
{
if (text == null) throw new ArgumentNullException(nameof(text));
return Resolve(Channel, text);
}

public override bool Equals(object obj) => obj is Message && (obj as Message).Id == Id;
public override int GetHashCode() => unchecked(Id.GetHashCode() + 9979); public override int GetHashCode() => unchecked(Id.GetHashCode() + 9979);
public override string ToString() => $"{User}: {RawText}"; public override string ToString() => $"{User}: {RawText}";
} }


+ 2
- 2
src/Discord.Net/Models/Permissions.cs View File

@@ -6,8 +6,8 @@ namespace Discord
{ {
//General //General
CreateInstantInvite = 0, CreateInstantInvite = 0,
BanMembers = 1,
KickMembers = 2,
KickMembers = 1,
BanMembers = 2,
ManageRolesOrPermissions = 3, ManageRolesOrPermissions = 3,
ManageChannel = 4, ManageChannel = 4,
ManageServer = 5, ManageServer = 5,


+ 40
- 2
src/Discord.Net/Models/Profile.cs View File

@@ -1,10 +1,15 @@
using Newtonsoft.Json;
using Discord.API.Client.Rest;
using System;
using System.IO;
using System.Threading.Tasks;
using APIUser = Discord.API.Client.User; using APIUser = Discord.API.Client.User;


namespace Discord namespace Discord
{ {
public sealed class Profile public sealed class Profile
{ {
internal DiscordClient Client { get; }

/// <summary> Gets the unique identifier for this user. </summary> /// <summary> Gets the unique identifier for this user. </summary>
public ulong Id { get; private set; } public ulong Id { get; private set; }
/// <summary> Gets the email for this user. </summary> /// <summary> Gets the email for this user. </summary>
@@ -12,7 +17,10 @@ namespace Discord
/// <summary> Gets if the email for this user has been verified. </summary> /// <summary> Gets if the email for this user has been verified. </summary>
public bool? IsVerified { get; private set; } public bool? IsVerified { get; private set; }


internal Profile() { }
internal Profile(DiscordClient client)
{
Client = client;
}


internal void Update(APIUser model) internal void Update(APIUser model)
{ {
@@ -21,6 +29,36 @@ namespace Discord
IsVerified = model.IsVerified; IsVerified = model.IsVerified;
} }


public async Task Edit(string currentPassword = "",
string username = null, string email = null, string password = null,
Stream avatar = null, ImageType avatarType = ImageType.Png)
{
if (currentPassword == null) throw new ArgumentNullException(nameof(currentPassword));

var request = new UpdateProfileRequest()
{
CurrentPassword = currentPassword,
Email = email ?? Email,
Password = password,
Username = username ?? Client.PrivateUser.Name,
AvatarBase64 = avatar.Base64(avatarType, Client.PrivateUser.AvatarId)
};

await Client.ClientAPI.Send(request).ConfigureAwait(false);

if (password != null)
{
var loginRequest = new LoginRequest()
{
Email = Email,
Password = password
};
var loginResponse = await Client.ClientAPI.Send(loginRequest).ConfigureAwait(false);
Client.ClientAPI.Token = loginResponse.Token;
Client.GatewaySocket.Token = loginResponse.Token;
}
}

public override bool Equals(object obj) public override bool Equals(object obj)
=> (obj is Profile && (obj as Profile).Id == Id) || (obj is User && (obj as User).Id == Id); => (obj is Profile && (obj as Profile).Id == Id) || (obj is User && (obj as User).Id == Id);
public override int GetHashCode() => unchecked(Id.GetHashCode() + 2061); public override int GetHashCode() => unchecked(Id.GetHashCode() + 2061);


+ 56
- 3
src/Discord.Net/Models/Role.cs View File

@@ -1,13 +1,17 @@
using System;
using Discord.API.Client.Rest;
using Discord.Net;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net;
using System.Threading.Tasks;
using APIRole = Discord.API.Client.Role; using APIRole = Discord.API.Client.Role;


namespace Discord namespace Discord
{ {
public sealed class Role public sealed class Role
{ {
private readonly DiscordClient _client;
internal DiscordClient Client => Server.Client;


/// <summary> Gets the unique identifier for this role. </summary> /// <summary> Gets the unique identifier for this role. </summary>
public ulong Id { get; } public ulong Id { get; }
@@ -72,8 +76,57 @@ namespace Discord
foreach (var member in Members) foreach (var member in Members)
Server.UpdatePermissions(member); Server.UpdatePermissions(member);
} }
public async Task Edit(string name = null, ServerPermissions permissions = null, Color color = null, bool? isHoisted = null, int? position = null)
{
var updateRequest = new UpdateRoleRequest(Server.Id, Id)
{
Name = name ?? Name,
Permissions = (permissions ?? Permissions).RawValue,
Color = (color ?? Color).RawValue,
IsHoisted = isHoisted ?? IsHoisted
};

var updateResponse = await Client.ClientAPI.Send(updateRequest).ConfigureAwait(false);

if (position != null)
{
int oldPos = Position;
int newPos = position.Value;
int minPos;
Role[] roles = 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] = this;
}
else //(oldPos > newPos) Moving Up
{
minPos = newPos;
for (int i = oldPos; i > newPos; i--)
roles[i] = roles[i - 1];
roles[newPos] = this;
}

var reorderRequest = new ReorderRolesRequest(Server.Id)
{
RoleIds = roles.Skip(minPos).Select(x => x.Id).ToArray(),
StartPos = minPos
};
await Client.ClientAPI.Send(reorderRequest).ConfigureAwait(false);
}
}

public async Task Delete()
{
try { await Client.ClientAPI.Send(new DeleteRoleRequest(Server.Id, Id)).ConfigureAwait(false); }
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
}


public override bool Equals(object obj) => obj is Role && (obj as Role).Id == Id;
public override bool Equals(object obj) => obj is Role && (obj as Role).Id == Id;
public override int GetHashCode() => unchecked(Id.GetHashCode() + 6653); public override int GetHashCode() => unchecked(Id.GetHashCode() + 6653);
public override string ToString() => Name ?? Id.ToIdString(); public override string ToString() => Name ?? Id.ToIdString();
} }


+ 244
- 46
src/Discord.Net/Models/Server.cs View File

@@ -1,14 +1,22 @@
using Discord.API.Client; using Discord.API.Client;
using Discord.API.Client.Rest;
using Discord.Net;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Net;
using System.Threading.Tasks;


namespace Discord namespace Discord
{ {
/// <summary> Represents a Discord server (also known as a guild). </summary> /// <summary> Represents a Discord server (also known as a guild). </summary>
public sealed class Server public sealed class Server
{ {
internal static string GetIconUrl(ulong serverId, string iconId)
=> iconId != null ? $"{DiscordConfig.CDNUrl}/icons/{serverId}/{iconId}.jpg" : null;

private struct Member private struct Member
{ {
public readonly User User; public readonly User User;
@@ -19,7 +27,7 @@ namespace Discord
Permissions = new ServerPermissions(); Permissions = new ServerPermissions();
Permissions.Lock(); Permissions.Lock();
} }
}
}


private readonly ConcurrentDictionary<ulong, Role> _roles; private readonly ConcurrentDictionary<ulong, Role> _roles;
private readonly ConcurrentDictionary<ulong, Member> _users; private readonly ConcurrentDictionary<ulong, Member> _users;
@@ -27,9 +35,9 @@ namespace Discord
private readonly ConcurrentDictionary<ulong, bool> _bans; private readonly ConcurrentDictionary<ulong, bool> _bans;
private ulong _ownerId; private ulong _ownerId;
private ulong? _afkChannelId; private ulong? _afkChannelId;

/// <summary> Gets the client that generated this server object. </summary>
internal DiscordClient Client { get; } internal DiscordClient Client { get; }

/// <summary> Gets the unique identifier for this server. </summary> /// <summary> Gets the unique identifier for this server. </summary>
public ulong Id { get; } public ulong Id { get; }
/// <summary> Gets the default channel for this server. </summary> /// <summary> Gets the default channel for this server. </summary>
@@ -39,19 +47,14 @@ namespace Discord


/// <summary> Gets the name of this server. </summary> /// <summary> Gets the name of this server. </summary>
public string Name { get; private set; } public string Name { get; private set; }

/// <summary> Gets the amount of time (in seconds) a user must be inactive for until they are automatically moved to the AFK channel, if one is set. </summary>
public int AFKTimeout { get; private set; }
/// <summary> Gets the date and time you joined this server. </summary>
public DateTime JoinedAt { get; private set; }
/// <summary> Gets the voice region for this server. </summary> /// <summary> Gets the voice region for this server. </summary>
public Region Region { get; private set; } public Region Region { get; private set; }
/// <summary> Gets the unique identifier for this user's current avatar. </summary> /// <summary> Gets the unique identifier for this user's current avatar. </summary>
public string IconId { get; private set; } public string IconId { get; private set; }
/// <summary> Gets the URL to this user's current avatar. </summary>
public string IconUrl => GetIconUrl(Id, IconId);
internal static string GetIconUrl(ulong serverId, string iconId)
=> iconId != null ? $"{DiscordConfig.CDNUrl}/icons/{serverId}/{iconId}.jpg" : null;
/// <summary> Gets the amount of time (in seconds) a user must be inactive for until they are automatically moved to the AFK voice channel, if one is set. </summary>
public int AFKTimeout { get; private set; }
/// <summary> Gets the date and time you joined this server. </summary>
public DateTime JoinedAt { get; private set; }


/// <summary> Gets the user that created this server. </summary> /// <summary> Gets the user that created this server. </summary>
public User Owner => GetUser(_ownerId); public User Owner => GetUser(_ownerId);
@@ -59,16 +62,18 @@ namespace Discord
public Channel AFKChannel => _afkChannelId != null ? GetChannel(_afkChannelId.Value) : null; public Channel AFKChannel => _afkChannelId != null ? GetChannel(_afkChannelId.Value) : null;
/// <summary> Gets the current user in this server. </summary> /// <summary> Gets the current user in this server. </summary>
public User CurrentUser => GetUser(Client.CurrentUser.Id); public User CurrentUser => GetUser(Client.CurrentUser.Id);
/// <summary> Gets the URL to this user's current avatar. </summary>
public string IconUrl => GetIconUrl(Id, IconId);


/// <summary> Gets a collection of the ids of all users banned on this server. </summary> /// <summary> Gets a collection of the ids of all users banned on this server. </summary>
public IEnumerable<ulong> BannedUserIds => _bans.Select(x => x.Key); public IEnumerable<ulong> BannedUserIds => _bans.Select(x => x.Key);
/// <summary> Gets a collection of all channels within this server. </summary>
/// <summary> Gets a collection of all channels in this server. </summary>
public IEnumerable<Channel> Channels => _channels.Select(x => x.Value); public IEnumerable<Channel> Channels => _channels.Select(x => x.Value);
/// <summary> Gets a collection of all users within this server with their server-specific data. </summary>
/// <summary> Gets a collection of all members in this server. </summary>
public IEnumerable<User> Users => _users.Select(x => x.Value.User); public IEnumerable<User> Users => _users.Select(x => x.Value.User);
/// <summary> Gets a collection of all roles within this server. </summary>
/// <summary> Gets a collection of all roles in this server. </summary>
public IEnumerable<Role> Roles => _roles.Select(x => x.Value); public IEnumerable<Role> Roles => _roles.Select(x => x.Value);
internal Server(DiscordClient client, ulong id) internal Server(DiscordClient client, ulong id)
{ {
Client = client; Client = client;
@@ -80,7 +85,7 @@ namespace Discord
DefaultChannel = AddChannel(id); DefaultChannel = AddChannel(id);
EveryoneRole = AddRole(id); EveryoneRole = AddRole(id);
} }
internal void Update(GuildReference model) internal void Update(GuildReference model)
{ {
if (model.Name != null) if (model.Name != null)
@@ -120,7 +125,7 @@ namespace Discord
if (model.Members != null) if (model.Members != null)
{ {
foreach (var subModel in model.Members) foreach (var subModel in model.Members)
AddMember(subModel.User.Id).Update(subModel);
AddUser(subModel.User.Id).Update(subModel);
} }
if (model.VoiceStates != null) if (model.VoiceStates != null)
{ {
@@ -133,8 +138,39 @@ namespace Discord
GetUser(subModel.User.Id)?.Update(subModel); GetUser(subModel.User.Id)?.Update(subModel);
} }
} }
/// <summary> Edits this server, changing only non-null attributes. </summary>
public Task Edit(string name = null, string region = null, Stream icon = null, ImageType iconType = ImageType.Png)
{
var request = new UpdateGuildRequest(Id)
{
Name = name ?? Name,
Region = region ?? Region.Id,
IconBase64 = icon.Base64(iconType, IconId),
AFKChannelId = AFKChannel?.Id,
AFKTimeout = AFKTimeout
};
return Client.ClientAPI.Send(request);
}


//Bans
/// <summary> Leaves this server. This function will fail if you're the owner - use Delete instead. </summary>
public async Task Leave()
{
if (_ownerId == CurrentUser.Id)
throw new InvalidOperationException("Unable to leave a server you own, use Server.Delete instead");
try { await Client.ClientAPI.Send(new LeaveGuildRequest(Id)).ConfigureAwait(false); }
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
}
/// <summary> Deletes this server. This function will fail if you're not the owner - use Leave instead. </summary>
public async Task Delete()
{
if (_ownerId != CurrentUser.Id)
throw new InvalidOperationException("Unable to delete a server you don't own, use Server.Leave instead");
try { await Client.ClientAPI.Send(new LeaveGuildRequest(Id)).ConfigureAwait(false); }
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
}

#region Bans
internal void AddBan(ulong banId) internal void AddBan(ulong banId)
=> _bans.TryAdd(banId, true); => _bans.TryAdd(banId, true);
internal bool RemoveBan(ulong banId) internal bool RemoveBan(ulong banId)
@@ -143,7 +179,22 @@ namespace Discord
return _bans.TryRemove(banId, out ignored); return _bans.TryRemove(banId, out ignored);
} }


//Channels
public Task Ban(User user, int pruneDays = 0)
{
var request = new AddGuildBanRequest(user.Server.Id, user.Id);
request.PruneDays = pruneDays;
return Client.ClientAPI.Send(request);
}
public Task Unban(User user, int pruneDays = 0)
=> Unban(user.Id);
public async Task Unban(ulong userId)
{
try { await Client.ClientAPI.Send(new RemoveGuildBanRequest(Id, userId)).ConfigureAwait(false); }
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
}
#endregion

#region Channels
internal Channel AddChannel(ulong id) internal Channel AddChannel(ulong id)
=> _channels.GetOrAdd(id, x => new Channel(Client, x, this)); => _channels.GetOrAdd(id, x => new Channel(Client, x, this));
internal Channel RemoveChannel(ulong id) internal Channel RemoveChannel(ulong id)
@@ -152,6 +203,8 @@ namespace Discord
_channels.TryRemove(id, out channel); _channels.TryRemove(id, out channel);
return channel; return channel;
} }

/// <summary> Gets the channel with the provided id and owned by this server, or null if not found. </summary>
public Channel GetChannel(ulong id) public Channel GetChannel(ulong id)
{ {
Channel result; Channel result;
@@ -159,36 +212,67 @@ namespace Discord
return result; return result;
} }


//Members
internal User AddMember(ulong id)
/// <summary> Returns all channels with the specified server and name. </summary>
/// <remarks> Name formats supported: Name, #Name and &lt;#Id&gt;. Search is case-insensitive if exactMatch is false.</remarks>
public IEnumerable<Channel> FindChannels(string name, ChannelType type = null, bool exactMatch = false)
{ {
User newUser = null;
var user = _users.GetOrAdd(id, x => new Member(new User(id, this)));
if (user.User == newUser)
{
foreach (var channel in Channels)
channel.AddUser(newUser);
}
return user.User;
if (name == null) throw new ArgumentNullException(nameof(name));
if (type == null) throw new ArgumentNullException(nameof(type));

return _channels.Select(x => x.Value).Find(name, type, exactMatch);
} }
internal User RemoveMember(ulong id)
{
Member member;
if (_users.TryRemove(id, out member))
{
foreach (var channel in Channels)
channel.RemoveUser(id);
}
return member.User;

/// <summary> Creates a new channel. </summary>
public async Task<Channel> CreateChannel(string name, ChannelType type)
{
if (name == null) throw new ArgumentNullException(nameof(name));
if (type == null) throw new ArgumentNullException(nameof(type));

var request = new CreateChannelRequest(Id) { Name = name, Type = type.Value };
var response = await Client.ClientAPI.Send(request).ConfigureAwait(false);

var channel = AddChannel(response.Id);
channel.Update(response);
return channel;
} }
public User GetUser(ulong id)

/// <summary> Reorders the provided channels and optionally places them after a certain channel. </summary>
public Task ReorderChannels(IEnumerable<Channel> channels, Channel after = null)
{ {
Member result;
_users.TryGetValue(id, out result);
return result.User;
if (channels == null) throw new ArgumentNullException(nameof(channels));

var request = new ReorderChannelsRequest(Id)
{
ChannelIds = channels.Select(x => x.Id).ToArray(),
StartPos = after != null ? after.Position + 1 : channels.Min(x => x.Position)
};
return Client.ClientAPI.Send(request);
} }
#endregion


//Roles
#region Invites
/// <summary> Gets all active (non-expired) invites to this server. </summary>
public async Task<IEnumerable<Invite>> GetInvites()
{
var response = await Client.ClientAPI.Send(new GetInvitesRequest(Id)).ConfigureAwait(false);
return response.Select(x =>
{
var invite = new Invite(Client, x.Code, x.XkcdPass);
invite.Update(x);
return invite;
});
}

/// <summary> Creates a new invite to the default channel of this server. </summary>
/// <param name="maxAge"> Time (in seconds) until the invite expires. Set to null to never expire. </param>
/// <param name="maxUses"> The max amount of times this invite may be used. Set to null to have unlimited uses. </param>
/// <param name="tempMembership"> If true, a user accepting this invite will be kicked from the server after closing their client. </param>
/// <param name="withXkcd"> If true, creates a human-readable link. Not supported if maxAge is set to null. </param>
public Task<Invite> CreateInvite(int? maxAge = 1800, int? maxUses = null, bool tempMembership = false, bool withXkcd = false)
=> DefaultChannel.CreateInvite(maxAge, maxUses, tempMembership, withXkcd);
#endregion

#region Roles
internal Role AddRole(ulong id) internal Role AddRole(ulong id)
=> _roles.GetOrAdd(id, x => new Role(x, this)); => _roles.GetOrAdd(id, x => new Role(x, this));
internal Role RemoveRole(ulong id) internal Role RemoveRole(ulong id)
@@ -197,14 +281,59 @@ namespace Discord
_roles.TryRemove(id, out role); _roles.TryRemove(id, out role);
return role; return role;
} }

/// <summary> Gets the role with the provided id and owned by this server, or null if not found. </summary>
public Role GetRole(ulong id) public Role GetRole(ulong id)
{ {
Role result; Role result;
_roles.TryGetValue(id, out result); _roles.TryGetValue(id, out result);
return result; return result;
} }
/// <summary> Returns all roles with the specified server and name. </summary>
/// <remarks> Search is case-insensitive if exactMatch is false.</remarks>
public IEnumerable<Role> FindRoles(string name, bool exactMatch = false)
{
if (name == null) throw new ArgumentNullException(nameof(name));
return _roles.Select(x => x.Value).Find(name, exactMatch);
}
/// <summary> Creates a new role. </summary>
public async Task<Role> CreateRole(string name, ServerPermissions permissions = null, Color color = null, bool isHoisted = false)
{
if (name == null) throw new ArgumentNullException(nameof(name));


//Permissions
var createRequest = new CreateRoleRequest(Id);
var createResponse = await Client.ClientAPI.Send(createRequest).ConfigureAwait(false);
var role = AddRole(createResponse.Id);
role.Update(createResponse);

var editRequest = new UpdateRoleRequest(role.Server.Id, role.Id)
{
Name = name,
Permissions = (permissions ?? role.Permissions).RawValue,
Color = (color ?? Color.Default).RawValue,
IsHoisted = isHoisted
};
var editResponse = await Client.ClientAPI.Send(editRequest).ConfigureAwait(false);
role.Update(editResponse);

return role;
}

/// <summary> Reorders the provided roles and optionally places them after a certain role. </summary>
public Task ReorderRoles(IEnumerable<Role> roles, Role after = null)
{
if (roles == null) throw new ArgumentNullException(nameof(roles));

return Client.ClientAPI.Send(new ReorderRolesRequest(Id)
{
RoleIds = roles.Select(x => x.Id).ToArray(),
StartPos = after != null ? after.Position + 1 : roles.Min(x => x.Position)
});
}
#endregion

#region Permissions
internal ServerPermissions GetPermissions(User user) internal ServerPermissions GetPermissions(User user)
{ {
Member member; Member member;
@@ -213,12 +342,14 @@ namespace Discord
else else
return null; return null;
} }

internal void UpdatePermissions(User user) internal void UpdatePermissions(User user)
{ {
Member member; Member member;
if (_users.TryGetValue(user.Id, out member)) if (_users.TryGetValue(user.Id, out member))
UpdatePermissions(member.User, member.Permissions); UpdatePermissions(member.User, member.Permissions);
} }

private void UpdatePermissions(User user, ServerPermissions permissions) private void UpdatePermissions(User user, ServerPermissions permissions)
{ {
uint newPermissions = 0; uint newPermissions = 0;
@@ -241,8 +372,75 @@ namespace Discord
channel.Value.UpdatePermissions(user); channel.Value.UpdatePermissions(user);
} }
} }
#endregion

#region Users
internal User AddUser(ulong id)
{
User newUser = null;
var user = _users.GetOrAdd(id, x => new Member(new User(Client, id, this)));
if (user.User == newUser)
{
foreach (var channel in Channels)
channel.AddUser(newUser);
}
return user.User;
}
internal User RemoveUser(ulong id)
{
Member member;
if (_users.TryRemove(id, out member))
{
foreach (var channel in Channels)
channel.RemoveUser(id);
}
return member.User;
}

/// <summary> Gets the user with the provided id and is a member of this server, or null if not found. </summary>
public User GetUser(ulong id)
{
Member result;
_users.TryGetValue(id, out result);
return result.User;
}
/// <summary> Gets the user with the provided username and discriminator, that is a member of this server, or null if not found. </summary>
public User GetUser(string name, ushort discriminator)
{
if (name == null) throw new ArgumentNullException(nameof(name));

return _users.Select(x => x.Value.User).Find(name, discriminator: discriminator, exactMatch: false).FirstOrDefault();
}
/// <summary> Returns all members of this server with the specified name. </summary>
/// <remarks> Name formats supported: Name, @Name and &lt;@Id&gt;. Search is case-insensitive if exactMatch is false.</remarks>
public IEnumerable<User> FindUsers(string name, bool exactMatch = false)
{
if (name == null) throw new ArgumentNullException(nameof(name));

return _users.Select(x => x.Value.User).Find(name, exactMatch: exactMatch);
}

/// <summary> Kicks all users with an inactivity greater or equal to the provided number of days. </summary>
/// <param name="simulate">If true, no pruning will actually be done but instead return the number of users that would be pruned. </param>
public async Task<int> PruneUsers(int days = 30, bool simulate = false)
{
if (days <= 0) throw new ArgumentOutOfRangeException(nameof(days));

var request = new PruneMembersRequest(Id)
{
Days = days,
IsSimulation = simulate
};
var response = await Client.ClientAPI.Send(request).ConfigureAwait(false);
return response.Pruned;
}

/// <summary>When Config.UseLargeThreshold is enabled, running this command will request the Discord server to provide you with all offline users for this server.</summary>
public void RequestOfflineUsers()
=> Client.GatewaySocket.SendRequestMembers(Id, "", 0);
#endregion


public override bool Equals(object obj) => obj is Server && (obj as Server).Id == Id;
public override bool Equals(object obj) => obj is Server && (obj as Server).Id == Id;
public override int GetHashCode() => unchecked(Id.GetHashCode() + 5175); public override int GetHashCode() => unchecked(Id.GetHashCode() + 5175);
public override string ToString() => Name ?? Id.ToIdString(); public override string ToString() => Name ?? Id.ToIdString();
} }


+ 101
- 39
src/Discord.Net/Models/User.cs View File

@@ -1,13 +1,19 @@
using Discord.API.Client; using Discord.API.Client;
using Discord.API.Client.Rest;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using APIMember = Discord.API.Client.Member; using APIMember = Discord.API.Client.Member;


namespace Discord namespace Discord
{ {
public class User public class User
{
{
internal static string GetAvatarUrl(ulong userId, string avatarId)
=> avatarId != null ? $"{DiscordConfig.CDNUrl}/avatars/{userId}/{avatarId}.jpg" : null;

[Flags] [Flags]
private enum VoiceState : byte private enum VoiceState : byte
{ {
@@ -34,15 +40,13 @@ namespace Discord
=> unchecked(ServerId.GetHashCode() + UserId.GetHashCode() + 23); => unchecked(ServerId.GetHashCode() + UserId.GetHashCode() + 23);
} }


internal static string GetAvatarUrl(ulong userId, string avatarId) => avatarId != null ? $"{DiscordConfig.CDNUrl}/avatars/{userId}/{avatarId}.jpg" : null;

private VoiceState _voiceState; private VoiceState _voiceState;
private DateTime? _lastOnline; private DateTime? _lastOnline;
private ulong? _voiceChannelId; private ulong? _voiceChannelId;
private Dictionary<ulong, Role> _roles; private Dictionary<ulong, Role> _roles;

/// <summary> Gets the client that generated this user object. </summary>
internal DiscordClient Client { get; } internal DiscordClient Client { get; }

/// <summary> Gets the unique identifier for this user. </summary> /// <summary> Gets the unique identifier for this user. </summary>
public ulong Id { get; } public ulong Id { get; }
/// <summary> Gets the server this user is a member of. </summary> /// <summary> Gets the server this user is a member of. </summary>
@@ -129,9 +133,9 @@ namespace Discord
} }
} }



internal User(ulong id, Server server)
internal User(DiscordClient client, ulong id, Server server)
{ {
Client = client;
Server = server; Server = server;
_roles = new Dictionary<ulong, Role>(); _roles = new Dictionary<ulong, Role>();


@@ -220,51 +224,109 @@ namespace Discord
_voiceChannelId = model.ChannelId; //Allows null _voiceChannelId = model.ChannelId; //Allows null
} }
private void UpdateRoles(IEnumerable<Role> roles)
{
var newRoles = new Dictionary<ulong, Role>();
if (roles != null)
{
foreach (var r in roles)
{
if (r != null)
newRoles[r.Id] = r;
}
}

if (Server != null)
{
var everyone = Server.EveryoneRole;
newRoles[everyone.Id] = everyone;
}
_roles = newRoles;

if (Server != null)
Server.UpdatePermissions(this);
}


internal void UpdateActivity(DateTime? activity = null) internal void UpdateActivity(DateTime? activity = null)
{ {
if (LastActivityAt == null || activity > LastActivityAt.Value) if (LastActivityAt == null || activity > LastActivityAt.Value)
LastActivityAt = activity ?? DateTime.UtcNow; LastActivityAt = activity ?? DateTime.UtcNow;
} }
public ServerPermissions ServerPermissions => Server.GetPermissions(this);

public Task Edit(bool? isMuted = null, bool? isDeafened = null, Channel voiceChannel = null, IEnumerable<Role> roles = null)
{
if (Server == null) throw new InvalidOperationException("Unable to edit users in a private channel");

//Modify the roles collection and filter out the everyone role
var roleIds = roles == null ? null : Roles.Where(x => !x.IsEveryone).Select(x => x.Id);

var request = new UpdateMemberRequest(Server.Id, Id)
{
IsMuted = isMuted ?? IsServerMuted,
IsDeafened = isDeafened ?? IsServerDeafened,
VoiceChannelId = voiceChannel?.Id,
RoleIds = roleIds.ToArray()
};
return Client.ClientAPI.Send(request);
}
public Task Kick()
{
if (Server == null) throw new InvalidOperationException("Unable to kick users from a private channel");

var request = new KickMemberRequest(Server.Id, Id);
return Client.ClientAPI.Send(request);
}

#region Permissions
public ServerPermissions ServerPermissions => Server.GetPermissions(this);
public ChannelPermissions GetPermissions(Channel channel) public ChannelPermissions GetPermissions(Channel channel)
{ {
if (channel == null) throw new ArgumentNullException(nameof(channel)); if (channel == null) throw new ArgumentNullException(nameof(channel));

return channel.GetPermissions(this); return channel.GetPermissions(this);
} }
#endregion


public bool HasRole(Role role)
{
if (role == null) throw new ArgumentNullException(nameof(role));
return _roles.ContainsKey(role.Id);
}
#region Channels
public Task<Channel> CreateChannel()
=> Client.CreatePrivateChannel(this);
#endregion

#region Messages
public async Task<Message> SendMessage(string text)
{
if (text == null) throw new ArgumentNullException(nameof(text));

var channel = await CreateChannel().ConfigureAwait(false);
return await channel.SendMessage(text).ConfigureAwait(false);
}
public async Task<Message> SendFile(string filePath)
{
if (filePath == null) throw new ArgumentNullException(nameof(filePath));

var channel = await CreateChannel().ConfigureAwait(false);
return await channel.SendFile(filePath).ConfigureAwait(false);
}
public async Task<Message> SendFile(string filename, Stream stream)
{
if (filename == null) throw new ArgumentNullException(nameof(filename));
if (stream == null) throw new ArgumentNullException(nameof(stream));

var channel = await CreateChannel().ConfigureAwait(false);
return await channel.SendFile(filename, stream).ConfigureAwait(false);
}
#endregion

#region Roles
private void UpdateRoles(IEnumerable<Role> roles)
{
var newRoles = new Dictionary<ulong, Role>();
if (roles != null)
{
foreach (var r in roles)
{
if (r != null)
newRoles[r.Id] = r;
}
}

if (Server != null)
{
var everyone = Server.EveryoneRole;
newRoles[everyone.Id] = everyone;
}
_roles = newRoles;

if (Server != null)
Server.UpdatePermissions(this);
}
public bool HasRole(Role role)
{
if (role == null) throw new ArgumentNullException(nameof(role));

return _roles.ContainsKey(role.Id);
}
#endregion


public override bool Equals(object obj) => obj is User && (obj as User).Id == Id;
public override bool Equals(object obj) => obj is User && (obj as User).Id == Id;
public override int GetHashCode() => unchecked(Id.GetHashCode() + 7230); public override int GetHashCode() => unchecked(Id.GetHashCode() + 7230);
public override string ToString() => Name != null ? $"{Name}#{Discriminator}" : Id.ToIdString(); public override string ToString() => Name != null ? $"{Name}#{Discriminator}" : Id.ToIdString();
} }

+ 4
- 5
src/Discord.Net/Net/WebSockets/GatewaySocket.cs View File

@@ -14,7 +14,7 @@ namespace Discord.Net.WebSockets
private int _lastSequence; private int _lastSequence;
private string _sessionId; private string _sessionId;


public string Token { get; private set; }
public string Token { get; internal set; }


public GatewaySocket(DiscordClient client, JsonSerializer serializer, Logger logger) public GatewaySocket(DiscordClient client, JsonSerializer serializer, Logger logger)
: base(client, serializer, logger) : base(client, serializer, logger)
@@ -26,11 +26,10 @@ namespace Discord.Net.WebSockets
}; };
} }


public async Task Connect(string token)
public async Task Connect()
{ {
Token = token;
await BeginConnect().ConfigureAwait(false); await BeginConnect().ConfigureAwait(false);
SendIdentify(token);
SendIdentify(Token);
} }
private async Task Redirect() private async Task Redirect()
{ {
@@ -47,7 +46,7 @@ namespace Discord.Net.WebSockets
{ {
try try
{ {
await Connect(Token).ConfigureAwait(false);
await Connect().ConfigureAwait(false);
break; break;
} }
catch (OperationCanceledException) { throw; } catch (OperationCanceledException) { throw; }


+ 0
- 28
src/Discord.Net/Optional.cs View File

@@ -1,28 +0,0 @@
using System.Collections.Generic;

namespace Discord
{
/*public struct Optional<T>
{
public bool HasValue { get; }
public T Value { get; }
public Optional(T value)
{
HasValue = true;
Value = value;
}

public static implicit operator Optional<T>(T value) => new Optional<T>(value);
public static bool operator ==(Optional<T> a, Optional<T> b) =>
a.HasValue == b.HasValue && EqualityComparer<T>.Default.Equals(a.Value, b.Value);
public static bool operator !=(Optional<T> a, Optional<T> b) =>
a.HasValue != b.HasValue || EqualityComparer<T>.Default.Equals(a.Value, b.Value);
public override bool Equals(object obj) =>
this == ((Optional<T>)obj);
public override int GetHashCode() =>
unchecked(HasValue.GetHashCode() + Value?.GetHashCode() ?? 0);

public override string ToString() => Value?.ToString() ?? "null";
}*/
}

+ 12
- 0
src/Discord.Net/RelativeDirection.cs View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Discord
{
public enum RelativeDirection
{
Before, After
}
}

+ 39
- 0
src/Discord.Net/ServiceManager.cs View File

@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;

namespace Discord
{
public class ServiceManager
{
private readonly Dictionary<Type, IService> _services;

internal DiscordClient Client { get; }

internal ServiceManager(DiscordClient client)
{
Client = client;
_services = new Dictionary<Type, IService>();
}

public void Add<T>(T service)
where T : class, IService
{
_services.Add(typeof(T), service);
service.Install(Client);
}

public T Get<T>(bool isRequired = true)
where T : class, IService
{
IService service;
T singletonT = null;

if (_services.TryGetValue(typeof(T), out service))
singletonT = service as T;

if (singletonT == null && isRequired)
throw new InvalidOperationException($"This operation requires {typeof(T).Name} to be added to {nameof(DiscordClient)}.");
return singletonT;
}
}
}

Loading…
Cancel
Save