Cleaned up all the models
This commit is contained in:
@@ -34,7 +34,7 @@ namespace Discord.Modules
|
||||
public event EventHandler<UserEventArgs> UserUpdated;
|
||||
public event EventHandler<UserEventArgs> UserPresenceUpdated;
|
||||
public event EventHandler<UserEventArgs> UserVoiceStateUpdated;
|
||||
public event EventHandler<UserChannelEventArgs> UserIsTypingUpdated;
|
||||
public event EventHandler<ChannelEventArgs> UserIsTypingUpdated;
|
||||
|
||||
public event EventHandler<MessageEventArgs> MessageReceived;
|
||||
public event EventHandler<MessageEventArgs> MessageSent;
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
internal static class EventHelper
|
||||
{
|
||||
public static void Raise(Logger logger, string name, Action action)
|
||||
{
|
||||
try { action(); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
var ex2 = ex.GetBaseException();
|
||||
logger.Error($"{name}'s handler raised {ex2.GetType().Name}: ${ex2.Message}", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
internal static class IdConvert
|
||||
{
|
||||
internal static readonly IFormatProvider _format = CultureInfo.InvariantCulture;
|
||||
|
||||
public static ulong ToLong(string value)
|
||||
=> ulong.Parse(value, NumberStyles.None, _format);
|
||||
public static ulong? ToNullableLong(string value)
|
||||
=> value == null ? (ulong?)null : ulong.Parse(value, NumberStyles.None, _format);
|
||||
|
||||
public static string ToString(ulong value)
|
||||
=> value.ToString(_format);
|
||||
public static string ToString(ulong? value)
|
||||
=> value?.ToString(_format);
|
||||
}
|
||||
}
|
||||
@@ -21,9 +21,9 @@ namespace Discord.API.Client
|
||||
[JsonProperty("last_message_id"), JsonConverter(typeof(NullableLongStringConverter))]
|
||||
public ulong? LastMessageId { get; set; }
|
||||
[JsonProperty("is_private")]
|
||||
public bool IsPrivate { get; set; }
|
||||
public bool? IsPrivate { get; set; }
|
||||
[JsonProperty("position")]
|
||||
public int Position { get; set; }
|
||||
public int? Position { get; set; }
|
||||
[JsonProperty("topic")]
|
||||
public string Topic { get; set; }
|
||||
[JsonProperty("permission_overwrites")]
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Discord.API.Client
|
||||
public class MemberPresence : MemberReference
|
||||
{
|
||||
[JsonProperty("game_id")]
|
||||
public int? GameId { get; set; }
|
||||
public string GameId { get; set; }
|
||||
[JsonProperty("status")]
|
||||
public string Status { get; set; }
|
||||
[JsonProperty("roles"), JsonConverter(typeof(LongStringArrayConverter))]
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Discord.API.Client
|
||||
public class MemberReference
|
||||
{
|
||||
[JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))]
|
||||
public ulong GuildId { get; set; }
|
||||
public ulong? GuildId { get; set; }
|
||||
[JsonProperty("user")]
|
||||
public UserReference User { get; set; }
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Discord.API.Client.Rest
|
||||
StringBuilder query = new StringBuilder();
|
||||
this.AddQueryParam(query, "limit", Limit.ToString());
|
||||
if (RelativeDir != null)
|
||||
this.AddQueryParam(query, RelativeDir, RelativeId.Value.ToString());
|
||||
this.AddQueryParam(query, RelativeDir, RelativeId.ToString());
|
||||
return $"channels/{ChannelId}/messages{query}";
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ namespace Discord.API.Client.Rest
|
||||
|
||||
public int Limit { get; set; } = 100;
|
||||
public string RelativeDir { get; set; } = null;
|
||||
public ulong? RelativeId { get; set; } = 0;
|
||||
public ulong RelativeId { get; set; } = 0;
|
||||
|
||||
public GetMessagesRequest(ulong channelId)
|
||||
{
|
||||
|
||||
@@ -9,9 +9,9 @@ namespace Discord.API.Converters
|
||||
public override bool CanConvert(Type objectType)
|
||||
=> objectType == typeof(ulong);
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
=> IdConvert.ToLong((string)reader.Value);
|
||||
=> ((string)reader.Value).ToId();
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
=> writer.WriteValue(IdConvert.ToString((ulong)value));
|
||||
=> writer.WriteValue(((ulong)value).ToIdString());
|
||||
}
|
||||
|
||||
public class NullableLongStringConverter : JsonConverter
|
||||
@@ -19,9 +19,9 @@ namespace Discord.API.Converters
|
||||
public override bool CanConvert(Type objectType)
|
||||
=> objectType == typeof(ulong?);
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
=> IdConvert.ToNullableLong((string)reader.Value);
|
||||
=> ((string)reader.Value).ToNullableId();
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
=> writer.WriteValue(IdConvert.ToString((ulong?)value));
|
||||
=> writer.WriteValue(((ulong?)value).ToIdString());
|
||||
}
|
||||
|
||||
/*public class LongStringEnumerableConverter : JsonConverter
|
||||
@@ -66,7 +66,7 @@ namespace Discord.API.Converters
|
||||
reader.Read();
|
||||
while (reader.TokenType != JsonToken.EndArray)
|
||||
{
|
||||
result.Add(IdConvert.ToLong((string)reader.Value));
|
||||
result.Add(((string)reader.Value).ToId());
|
||||
reader.Read();
|
||||
}
|
||||
}
|
||||
@@ -81,7 +81,7 @@ namespace Discord.API.Converters
|
||||
writer.WriteStartArray();
|
||||
var a = (ulong[])value;
|
||||
for (int i = 0; i < a.Length; i++)
|
||||
writer.WriteValue(IdConvert.ToString(a[i]));
|
||||
writer.WriteValue(a[i].ToIdString());
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,223 +0,0 @@
|
||||
using Discord.API.Client.Rest;
|
||||
using Discord.Net;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
internal sealed class Channels : AsyncCollection<ulong, Channel>
|
||||
{
|
||||
public IEnumerable<Channel> PrivateChannels => _privateChannels.Select(x => x.Value);
|
||||
private ConcurrentDictionary<ulong, Channel> _privateChannels;
|
||||
|
||||
public Channels(DiscordClient client, object writerLock)
|
||||
: base(client, writerLock)
|
||||
{
|
||||
_privateChannels = new ConcurrentDictionary<ulong, Channel>();
|
||||
ItemCreated += (s, e) =>
|
||||
{
|
||||
if (e.Item.IsPrivate)
|
||||
_privateChannels.TryAdd(e.Item.Id, e.Item);
|
||||
};
|
||||
ItemDestroyed += (s, e) =>
|
||||
{
|
||||
if (e.Item.IsPrivate)
|
||||
{
|
||||
Channel ignored;
|
||||
_privateChannels.TryRemove(e.Item.Id, out ignored);
|
||||
}
|
||||
};
|
||||
Cleared += (s, e) => _privateChannels.Clear();
|
||||
}
|
||||
|
||||
public Channel GetOrAdd(ulong id, ulong? serverId, ulong? recipientId = null)
|
||||
=> GetOrAdd(id, () => new Channel(_client, id, serverId, recipientId));
|
||||
}
|
||||
|
||||
public class ChannelEventArgs : EventArgs
|
||||
{
|
||||
public Channel Channel { get; }
|
||||
public Server Server => Channel.Server;
|
||||
|
||||
public ChannelEventArgs(Channel channel) { Channel = channel; }
|
||||
}
|
||||
|
||||
public partial class DiscordClient
|
||||
{
|
||||
public event EventHandler<ChannelEventArgs> ChannelCreated;
|
||||
private void RaiseChannelCreated(Channel channel)
|
||||
{
|
||||
if (ChannelCreated != null)
|
||||
EventHelper.Raise(_logger, nameof(ChannelCreated), () => ChannelCreated(this, new ChannelEventArgs(channel)));
|
||||
}
|
||||
public event EventHandler<ChannelEventArgs> ChannelDestroyed;
|
||||
private void RaiseChannelDestroyed(Channel channel)
|
||||
{
|
||||
if (ChannelDestroyed != null)
|
||||
EventHelper.Raise(_logger, nameof(ChannelDestroyed), () => ChannelDestroyed(this, new ChannelEventArgs(channel)));
|
||||
}
|
||||
public event EventHandler<ChannelEventArgs> ChannelUpdated;
|
||||
private void RaiseChannelUpdated(Channel channel)
|
||||
{
|
||||
if (ChannelUpdated != null)
|
||||
EventHelper.Raise(_logger, nameof(ChannelUpdated), () => ChannelUpdated(this, new ChannelEventArgs(channel)));
|
||||
}
|
||||
|
||||
/// <summary> Returns a collection of all servers this client is a member of. </summary>
|
||||
public IEnumerable<Channel> PrivateChannels { get { CheckReady(); return _channels.PrivateChannels; } }
|
||||
internal Channels Channels => _channels;
|
||||
private readonly Channels _channels;
|
||||
|
||||
/// <summary> Returns the channel with the specified id, or null if none was found. </summary>
|
||||
public Channel GetChannel(ulong id)
|
||||
{
|
||||
CheckReady();
|
||||
|
||||
return _channels[id];
|
||||
}
|
||||
|
||||
/// <summary> Returns all channels with the specified server and name. </summary>
|
||||
/// <remarks> Name formats supported: Name, #Name and <#Id>. Search is case-insensitive if exactMatch is false.</remarks>
|
||||
public IEnumerable<Channel> FindChannels(Server server, string name, ChannelType type = null, bool exactMatch = false)
|
||||
{
|
||||
if (server == null) throw new ArgumentNullException(nameof(server));
|
||||
if (name == null) throw new ArgumentNullException(nameof(name));
|
||||
CheckReady();
|
||||
|
||||
var query = server.Channels.Where(x => string.Equals(x.Name, name, exactMatch ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (!exactMatch && name.Length >= 2)
|
||||
{
|
||||
if (name[0] == '<' && name[1] == '#' && name[name.Length - 1] == '>') //Parse mention
|
||||
{
|
||||
var id = IdConvert.ToLong(name.Substring(2, name.Length - 3));
|
||||
var channel = _channels[id];
|
||||
if (channel != null)
|
||||
query = query.Concat(new Channel[] { channel });
|
||||
}
|
||||
else if (name[0] == '#' && (type == null || type == ChannelType.Text)) //If we somehow get text starting with # but isn't a mention
|
||||
{
|
||||
string name2 = name.Substring(1);
|
||||
query = query.Concat(server.TextChannels.Where(x => string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)));
|
||||
}
|
||||
}
|
||||
|
||||
if (type != null)
|
||||
query = query.Where(x => x.Type == type);
|
||||
return query;
|
||||
}
|
||||
|
||||
/// <summary> Creates a new channel with the provided name and type. </summary>
|
||||
public async Task<Channel> CreateChannel(Server server, string name, ChannelType type)
|
||||
{
|
||||
if (server == null) throw new ArgumentNullException(nameof(server));
|
||||
if (name == null) throw new ArgumentNullException(nameof(name));
|
||||
if (type == null) throw new ArgumentNullException(nameof(type));
|
||||
CheckReady();
|
||||
|
||||
var request = new CreateChannelRequest(server.Id) { Name = name, Type = type.Value };
|
||||
var response = await _clientRest.Send(request).ConfigureAwait(false);
|
||||
|
||||
var channel = _channels.GetOrAdd(response.Id, response.GuildId, response.Recipient?.Id);
|
||||
channel.Update(response);
|
||||
return channel;
|
||||
}
|
||||
|
||||
/// <summary> Returns the private channel with the provided user, creating one if it does not currently exist. </summary>
|
||||
public async Task<Channel> CreatePMChannel(User user)
|
||||
{
|
||||
if (user == null) throw new ArgumentNullException(nameof(user));
|
||||
CheckReady();
|
||||
|
||||
Channel channel = null;
|
||||
if (user != null)
|
||||
channel = user.Global.PrivateChannel;
|
||||
if (channel == null)
|
||||
{
|
||||
var request = new CreatePrivateChannelRequest() { RecipientId = user.Id };
|
||||
var response = await _clientRest.Send(request).ConfigureAwait(false);
|
||||
|
||||
var recipient = _users.GetOrAdd(response.Recipient.Id, null);
|
||||
recipient.Update(response.Recipient);
|
||||
channel = _channels.GetOrAdd(response.Id, response.GuildId, response.Recipient.Id);
|
||||
channel.Update(response);
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
/// <summary> Edits the provided channel, changing only non-null attributes. </summary>
|
||||
public async Task EditChannel(Channel channel, string name = null, string topic = null, int? position = null)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
CheckReady();
|
||||
|
||||
if (name != null || topic != null)
|
||||
{
|
||||
var request = new UpdateChannelRequest(channel.Id)
|
||||
{
|
||||
Name = name ?? channel.Name,
|
||||
Topic = topic ?? channel.Topic,
|
||||
Position = channel.Position
|
||||
};
|
||||
await _clientRest.Send(request).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (position != null)
|
||||
{
|
||||
Channel[] channels = channel.Server.Channels.Where(x => x.Type == channel.Type).OrderBy(x => x.Position).ToArray();
|
||||
int oldPos = Array.IndexOf(channels, channel);
|
||||
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] = channel;
|
||||
}
|
||||
else //(oldPos > newPos) Moving Up
|
||||
{
|
||||
minPos = newPos;
|
||||
for (int i = oldPos; i > newPos; i--)
|
||||
channels[i] = channels[i - 1];
|
||||
channels[newPos] = channel;
|
||||
}
|
||||
Channel after = minPos > 0 ? channels.Skip(minPos - 1).FirstOrDefault() : null;
|
||||
await ReorderChannels(channel.Server, channels.Skip(minPos), after).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Reorders the provided channels in the server's channel list and places them after a certain channel. </summary>
|
||||
public Task ReorderChannels(Server server, IEnumerable<Channel> channels, Channel after = null)
|
||||
{
|
||||
if (server == null) throw new ArgumentNullException(nameof(server));
|
||||
if (channels == null) throw new ArgumentNullException(nameof(channels));
|
||||
CheckReady();
|
||||
|
||||
var request = new ReorderChannelsRequest(server.Id)
|
||||
{
|
||||
ChannelIds = channels.Select(x => x.Id).ToArray(),
|
||||
StartPos = after != null ? after.Position + 1 : channels.Min(x => x.Position)
|
||||
};
|
||||
return _clientRest.Send(request);
|
||||
}
|
||||
|
||||
/// <summary> Destroys the provided channel. </summary>
|
||||
public async Task DeleteChannel(Channel channel)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
CheckReady();
|
||||
|
||||
try { await _clientRest.Send(new DeleteChannelRequest(channel.Id)).ConfigureAwait(false); }
|
||||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
114
src/Discord.Net/DiscordClient.Events.cs
Normal file
114
src/Discord.Net/DiscordClient.Events.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public partial class DiscordClient
|
||||
{
|
||||
public event EventHandler Connected = delegate { };
|
||||
public event EventHandler<DisconnectedEventArgs> Disconnected = delegate { };
|
||||
public event EventHandler<ChannelEventArgs> ChannelCreated = delegate { };
|
||||
public event EventHandler<ChannelEventArgs> ChannelDestroyed = delegate { };
|
||||
public event EventHandler<ChannelEventArgs> ChannelUpdated = delegate { };
|
||||
public event EventHandler<MessageEventArgs> MessageAcknowledged = delegate { };
|
||||
public event EventHandler<MessageEventArgs> MessageDeleted = delegate { };
|
||||
public event EventHandler<MessageEventArgs> MessageReceived = delegate { };
|
||||
public event EventHandler<MessageEventArgs> MessageSent = delegate { };
|
||||
public event EventHandler<MessageEventArgs> MessageUpdated = delegate { };
|
||||
public event EventHandler<ProfileEventArgs> ProfileUpdated = delegate { };
|
||||
public event EventHandler<RoleEventArgs> RoleCreated = delegate { };
|
||||
public event EventHandler<RoleEventArgs> RoleUpdated = delegate { };
|
||||
public event EventHandler<RoleEventArgs> RoleDeleted = delegate { };
|
||||
public event EventHandler<ServerEventArgs> JoinedServer = delegate { };
|
||||
public event EventHandler<ServerEventArgs> LeftServer = delegate { };
|
||||
public event EventHandler<ServerEventArgs> ServerAvailable = delegate { };
|
||||
public event EventHandler<ServerEventArgs> ServerUpdated = delegate { };
|
||||
public event EventHandler<ServerEventArgs> ServerUnavailable = delegate { };
|
||||
public event EventHandler<BanEventArgs> UserBanned = delegate { };
|
||||
public event EventHandler<ChannelUserEventArgs> UserIsTypingUpdated = delegate { };
|
||||
public event EventHandler<UserEventArgs> UserJoined = delegate { };
|
||||
public event EventHandler<UserEventArgs> UserLeft = delegate { };
|
||||
public event EventHandler<UserEventArgs> UserPresenceUpdated = delegate { };
|
||||
public event EventHandler<UserEventArgs> UserUpdated = delegate { };
|
||||
public event EventHandler<BanEventArgs> UserUnbanned = delegate { };
|
||||
public event EventHandler<UserEventArgs> UserVoiceStateUpdated = delegate { };
|
||||
|
||||
private void OnConnected()
|
||||
=> OnEvent(Connected);
|
||||
private void OnDisconnected(bool wasUnexpected, Exception ex)
|
||||
=> OnEvent(Disconnected, new DisconnectedEventArgs(wasUnexpected, ex));
|
||||
|
||||
private void OnChannelCreated(Channel channel)
|
||||
=> OnEvent(ChannelCreated, new ChannelEventArgs(channel));
|
||||
private void OnChannelDestroyed(Channel channel)
|
||||
=> OnEvent(ChannelDestroyed, new ChannelEventArgs(channel));
|
||||
private void OnChannelUpdated(Channel channel)
|
||||
=> OnEvent(ChannelUpdated, new ChannelEventArgs(channel));
|
||||
|
||||
private void OnMessageAcknowledged(Message msg)
|
||||
=> OnEvent(MessageAcknowledged, new MessageEventArgs(msg));
|
||||
private void OnMessageDeleted(Message msg)
|
||||
=> OnEvent(MessageDeleted, new MessageEventArgs(msg));
|
||||
private void OnMessageReceived(Message msg)
|
||||
=> OnEvent(MessageReceived, new MessageEventArgs(msg));
|
||||
/*private void OnMessageSent(Message msg)
|
||||
=> OnEvent(MessageSent, new MessageEventArgs(msg));*/
|
||||
private void OnMessageUpdated(Message msg)
|
||||
=> OnEvent(MessageUpdated, new MessageEventArgs(msg));
|
||||
|
||||
private void OnProfileUpdated(Profile profile)
|
||||
=> OnEvent(ProfileUpdated, new ProfileEventArgs(profile));
|
||||
|
||||
private void OnRoleCreated(Role role)
|
||||
=> OnEvent(RoleCreated, new RoleEventArgs(role));
|
||||
private void OnRoleDeleted(Role role)
|
||||
=> OnEvent(RoleDeleted, new RoleEventArgs(role));
|
||||
private void OnRoleUpdated(Role role)
|
||||
=> OnEvent(RoleUpdated, new RoleEventArgs(role));
|
||||
|
||||
private void OnJoinedServer(Server server)
|
||||
=> OnEvent(JoinedServer, new ServerEventArgs(server));
|
||||
private void OnLeftServer(Server server)
|
||||
=> OnEvent(LeftServer, new ServerEventArgs(server));
|
||||
private void OnServerAvailable(Server server)
|
||||
=> OnEvent(ServerAvailable, new ServerEventArgs(server));
|
||||
private void OnServerUpdated(Server server)
|
||||
=> OnEvent(ServerUpdated, new ServerEventArgs(server));
|
||||
private void OnServerUnavailable(Server server)
|
||||
=> OnEvent(ServerUnavailable, new ServerEventArgs(server));
|
||||
|
||||
private void OnUserBanned(Server server, ulong userId)
|
||||
=> OnEvent(UserBanned, new BanEventArgs(server, userId));
|
||||
private void OnUserIsTypingUpdated(Channel channel, User user)
|
||||
=> OnEvent(UserIsTypingUpdated, new ChannelUserEventArgs(channel, user));
|
||||
private void OnUserJoined(User user)
|
||||
=> OnEvent(UserJoined, new UserEventArgs(user));
|
||||
private void OnUserLeft(User user)
|
||||
=> OnEvent(UserLeft, new UserEventArgs(user));
|
||||
private void OnUserPresenceUpdated(User user)
|
||||
=> OnEvent(UserPresenceUpdated, new UserEventArgs(user));
|
||||
private void OnUserUnbanned(Server server, ulong userId)
|
||||
=> OnEvent(UserUnbanned, new BanEventArgs(server, userId));
|
||||
private void OnUserUpdated(User user)
|
||||
=> OnEvent(UserUpdated, new UserEventArgs(user));
|
||||
private void OnUserVoiceStateUpdated(User user)
|
||||
=> OnEvent(UserVoiceStateUpdated, new UserEventArgs(user));
|
||||
|
||||
private void OnEvent<T>(EventHandler<T> handler, T eventArgs, [CallerMemberName] string callerName = null)
|
||||
{
|
||||
try { handler(this, eventArgs); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error($"{callerName.Substring(2)}'s handler encountered error {ex.GetType().Name}: ${ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
private void OnEvent(EventHandler handler, [CallerMemberName] string callerName = null)
|
||||
{
|
||||
try { handler(this, EventArgs.Empty); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error($"{callerName.Substring(2)}'s handler encountered error {ex.GetType().Name}: ${ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
using Discord.API.Client.Rest;
|
||||
using Discord.Net;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public partial class DiscordClient
|
||||
{
|
||||
/// <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));
|
||||
CheckReady();
|
||||
|
||||
//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 _clientRest.Send(new GetInviteRequest(inviteIdOrXkcd)).ConfigureAwait(false);
|
||||
var invite = new Invite(response.Code, response.XkcdPass);
|
||||
invite.Update(response);
|
||||
return invite;
|
||||
}
|
||||
|
||||
/// <summary> Gets all active (non-expired) invites to a provided server. </summary>
|
||||
public async Task<Invite[]> GetInvites(Server server)
|
||||
{
|
||||
if (server == null) throw new ArgumentNullException(nameof(server));
|
||||
CheckReady();
|
||||
|
||||
var response = await _clientRest.Send(new GetInvitesRequest(server.Id)).ConfigureAwait(false);
|
||||
return response.Select(x =>
|
||||
{
|
||||
var invite = new Invite(x.Code, x.XkcdPass);
|
||||
invite.Update(x);
|
||||
return invite;
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
/// <summary> Creates a new invite to the default channel of the provided server. </summary>
|
||||
/// <param name="maxAge"> Time (in seconds) until the invite expires. Set to 0 to never expire. </param>
|
||||
/// <param name="tempMembership"> If true, a user accepting this invite will be kicked from the server after closing their client. </param>
|
||||
/// <param name="hasXkcd"> If true, creates a human-readable link. Not supported if maxAge is set to 0. </param>
|
||||
/// <param name="maxUses"> The max amount of times this invite may be used. Set to 0 to have unlimited uses. </param>
|
||||
public Task<Invite> CreateInvite(Server server, int maxAge = 1800, int maxUses = 0, bool tempMembership = false, bool hasXkcd = false)
|
||||
{
|
||||
if (server == null) throw new ArgumentNullException(nameof(server));
|
||||
CheckReady();
|
||||
|
||||
return CreateInvite(server.DefaultChannel, maxAge, maxUses, tempMembership, hasXkcd);
|
||||
}
|
||||
/// <summary> Creates a new invite to the provided channel. </summary>
|
||||
/// <param name="maxAge"> Time (in seconds) until the invite expires. Set to 0 to never expire. </param>
|
||||
/// <param name="tempMembership"> If true, a user accepting this invite will be kicked from the server after closing their client. </param>
|
||||
/// <param name="hasXkcd"> If true, creates a human-readable link. Not supported if maxAge is set to 0. </param>
|
||||
/// <param name="maxUses"> The max amount of times this invite may be used. Set to 0 to have unlimited uses. </param>
|
||||
public async Task<Invite> CreateInvite(Channel channel, int maxAge = 1800, int maxUses = 0, bool isTemporary = false, bool withXkcd = false)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
if (maxAge < 0) throw new ArgumentOutOfRangeException(nameof(maxAge));
|
||||
if (maxUses < 0) throw new ArgumentOutOfRangeException(nameof(maxUses));
|
||||
CheckReady();
|
||||
|
||||
var request = new CreateInviteRequest(channel.Id)
|
||||
{
|
||||
MaxAge = maxAge,
|
||||
MaxUses = maxUses,
|
||||
IsTemporary = isTemporary,
|
||||
WithXkcdPass = withXkcd
|
||||
};
|
||||
|
||||
var response = await _clientRest.Send(request).ConfigureAwait(false);
|
||||
var invite = new Invite(response.Code, response.XkcdPass);
|
||||
return invite;
|
||||
}
|
||||
|
||||
/// <summary> Deletes the provided invite. </summary>
|
||||
public async Task DeleteInvite(Invite invite)
|
||||
{
|
||||
if (invite == null) throw new ArgumentNullException(nameof(invite));
|
||||
CheckReady();
|
||||
|
||||
try { await _clientRest.Send(new DeleteInviteRequest(invite.Code)).ConfigureAwait(false); }
|
||||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
|
||||
}
|
||||
|
||||
/// <summary> Accepts the provided invite. </summary>
|
||||
public Task AcceptInvite(Invite invite)
|
||||
{
|
||||
if (invite == null) throw new ArgumentNullException(nameof(invite));
|
||||
CheckReady();
|
||||
|
||||
return _clientRest.Send(new AcceptInviteRequest(invite.Code));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,429 +0,0 @@
|
||||
using Discord.API;
|
||||
using Discord.API.Client.Rest;
|
||||
using Discord.Net;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using APIMessage = Discord.API.Client.Message;
|
||||
using APIUser = Discord.API.Client.User;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public enum RelativeDirection { Before, After}
|
||||
internal sealed class Messages : AsyncCollection<ulong, Message>
|
||||
{
|
||||
private bool _isEnabled;
|
||||
|
||||
public Messages(DiscordClient client, object writerLock, bool isEnabled)
|
||||
: base(client, writerLock)
|
||||
{
|
||||
_isEnabled = isEnabled;
|
||||
}
|
||||
|
||||
public Message GetOrAdd(ulong id, ulong channelId, ulong userId)
|
||||
{
|
||||
if (_isEnabled)
|
||||
return GetOrAdd(id, () => new Message(_client, id, channelId, userId));
|
||||
else
|
||||
{
|
||||
var msg = new Message(_client, id, channelId, userId);
|
||||
msg.Cache(); //Builds references
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
public void Import(Dictionary<ulong, Message> messages)
|
||||
=> base.Import(messages);
|
||||
}
|
||||
|
||||
internal class MessageQueueItem
|
||||
{
|
||||
public readonly Message Message;
|
||||
public readonly string Text;
|
||||
public readonly ulong[] MentionedUsers;
|
||||
public MessageQueueItem(Message msg, string text, ulong[] userIds)
|
||||
{
|
||||
Message = msg;
|
||||
Text = text;
|
||||
MentionedUsers = userIds;
|
||||
}
|
||||
}
|
||||
|
||||
public class MessageEventArgs : EventArgs
|
||||
{
|
||||
public Message Message { get; }
|
||||
public User User => Message.User;
|
||||
public Channel Channel => Message.Channel;
|
||||
public Server Server => Message.Server;
|
||||
|
||||
public MessageEventArgs(Message msg) { Message = msg; }
|
||||
}
|
||||
|
||||
public partial class DiscordClient
|
||||
{
|
||||
public const int MaxMessageSize = 2000;
|
||||
|
||||
public event EventHandler<MessageEventArgs> MessageReceived;
|
||||
private void RaiseMessageReceived(Message msg)
|
||||
{
|
||||
if (MessageReceived != null)
|
||||
EventHelper.Raise(_logger, nameof(MessageReceived), () => MessageReceived(this, new MessageEventArgs(msg)));
|
||||
}
|
||||
public event EventHandler<MessageEventArgs> MessageSent;
|
||||
private void RaiseMessageSent(Message msg)
|
||||
{
|
||||
if (MessageSent != null)
|
||||
EventHelper.Raise(_logger, nameof(MessageSent), () => MessageSent(this, new MessageEventArgs(msg)));
|
||||
}
|
||||
public event EventHandler<MessageEventArgs> MessageDeleted;
|
||||
private void RaiseMessageDeleted(Message msg)
|
||||
{
|
||||
if (MessageDeleted != null)
|
||||
EventHelper.Raise(_logger, nameof(MessageDeleted), () => MessageDeleted(this, new MessageEventArgs(msg)));
|
||||
}
|
||||
public event EventHandler<MessageEventArgs> MessageUpdated;
|
||||
private void RaiseMessageUpdated(Message msg)
|
||||
{
|
||||
if (MessageUpdated != null)
|
||||
EventHelper.Raise(_logger, nameof(MessageUpdated), () => MessageUpdated(this, new MessageEventArgs(msg)));
|
||||
}
|
||||
public event EventHandler<MessageEventArgs> MessageAcknowledged;
|
||||
private void RaiseMessageAcknowledged(Message msg)
|
||||
{
|
||||
if (MessageAcknowledged != null)
|
||||
EventHelper.Raise(_logger, nameof(MessageAcknowledged), () => MessageAcknowledged(this, new MessageEventArgs(msg)));
|
||||
}
|
||||
|
||||
internal Messages Messages => _messages;
|
||||
private readonly Random _nonceRand;
|
||||
private readonly Messages _messages;
|
||||
private readonly JsonSerializer _messageImporter;
|
||||
private readonly ConcurrentQueue<MessageQueueItem> _pendingMessages;
|
||||
|
||||
/// <summary> Returns the message with the specified id, or null if none was found. </summary>
|
||||
public Message GetMessage(ulong id)
|
||||
{
|
||||
if (id <= 0) throw new ArgumentOutOfRangeException(nameof(id));
|
||||
CheckReady();
|
||||
|
||||
return _messages[id];
|
||||
}
|
||||
|
||||
/// <summary> Sends a message to the provided channel. To include a mention, see the Mention static helper class. </summary>
|
||||
public Task<Message> SendMessage(Channel channel, string text)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||
CheckReady();
|
||||
|
||||
return SendMessageInternal(channel, text, false);
|
||||
}
|
||||
/// <summary> Sends a private message to the provided user. </summary>
|
||||
public async Task<Message> SendMessage(User user, string text)
|
||||
{
|
||||
if (user == null) throw new ArgumentNullException(nameof(user));
|
||||
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||
CheckReady();
|
||||
|
||||
var channel = await CreatePMChannel(user).ConfigureAwait(false);
|
||||
return await SendMessageInternal(channel, text, false).ConfigureAwait(false);
|
||||
}
|
||||
/// <summary> Sends a text-to-speech message to the provided channel. To include a mention, see the Mention static helper class. </summary>
|
||||
public Task<Message> SendTTSMessage(Channel channel, string text)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||
CheckReady();
|
||||
|
||||
return SendMessageInternal(channel, text, true);
|
||||
}
|
||||
/// <summary> Sends a file to the provided channel. </summary>
|
||||
public Task<Message> SendFile(Channel channel, string filePath)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
if (filePath == null) throw new ArgumentNullException(nameof(filePath));
|
||||
CheckReady();
|
||||
|
||||
return SendFile(channel, Path.GetFileName(filePath), File.OpenRead(filePath));
|
||||
}
|
||||
/// <summary> Sends a file to the provided channel. </summary>
|
||||
public async Task<Message> SendFile(Channel channel, string filename, Stream stream)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
if (filename == null) throw new ArgumentNullException(nameof(filename));
|
||||
if (stream == null) throw new ArgumentNullException(nameof(stream));
|
||||
CheckReady();
|
||||
|
||||
var request = new SendFileRequest(channel.Id)
|
||||
{
|
||||
Filename = filename,
|
||||
Stream = stream
|
||||
};
|
||||
var model = await _clientRest.Send(request).ConfigureAwait(false);
|
||||
|
||||
var msg = _messages.GetOrAdd(model.Id, channel.Id, model.Author.Id);
|
||||
msg.Update(model);
|
||||
RaiseMessageSent(msg);
|
||||
return msg;
|
||||
}
|
||||
/// <summary> Sends a file to the provided channel. </summary>
|
||||
public async Task<Message> SendFile(User user, string filePath)
|
||||
{
|
||||
if (user == null) throw new ArgumentNullException(nameof(user));
|
||||
if (filePath == null) throw new ArgumentNullException(nameof(filePath));
|
||||
CheckReady();
|
||||
|
||||
var channel = await CreatePMChannel(user).ConfigureAwait(false);
|
||||
return await SendFile(channel, Path.GetFileName(filePath), File.OpenRead(filePath)).ConfigureAwait(false);
|
||||
}
|
||||
/// <summary> Sends a file to the provided channel. </summary>
|
||||
public async Task<Message> SendFile(User user, string filename, Stream stream)
|
||||
{
|
||||
if (user == null) throw new ArgumentNullException(nameof(user));
|
||||
if (filename == null) throw new ArgumentNullException(nameof(filename));
|
||||
if (stream == null) throw new ArgumentNullException(nameof(stream));
|
||||
CheckReady();
|
||||
|
||||
var channel = await CreatePMChannel(user).ConfigureAwait(false);
|
||||
return await SendFile(channel, filename, stream).ConfigureAwait(false);
|
||||
}
|
||||
private async Task<Message> SendMessageInternal(Channel channel, string text, bool isTextToSpeech)
|
||||
{
|
||||
Message msg;
|
||||
var server = channel.Server;
|
||||
|
||||
var mentionedUsers = new List<User>();
|
||||
text = Mention.CleanUserMentions(this, server, text, mentionedUsers);
|
||||
if (text.Length > MaxMessageSize)
|
||||
throw new ArgumentOutOfRangeException(nameof(text), $"Message must be {MaxMessageSize} characters or less.");
|
||||
|
||||
if (Config.UseMessageQueue)
|
||||
{
|
||||
var nonce = GenerateNonce();
|
||||
msg = new Message(this, 0, channel.Id, _currentUser.Id); //_messages.GetOrAdd(nonce, channel.Id, _privateUser.Id);
|
||||
var currentUser = msg.User;
|
||||
msg.Update(new APIMessage
|
||||
{
|
||||
Content = text,
|
||||
Timestamp = DateTime.UtcNow,
|
||||
Author = new APIUser { Avatar = currentUser.AvatarId, Discriminator = currentUser.Discriminator, Id = _currentUser.Id, Username = currentUser.Name },
|
||||
ChannelId = channel.Id,
|
||||
Nonce = IdConvert.ToString(nonce),
|
||||
IsTextToSpeech = isTextToSpeech
|
||||
});
|
||||
msg.State = MessageState.Queued;
|
||||
|
||||
_pendingMessages.Enqueue(new MessageQueueItem(msg, text, mentionedUsers.Select(x => x.Id).ToArray()));
|
||||
}
|
||||
else
|
||||
{
|
||||
var request = new SendMessageRequest(channel.Id)
|
||||
{
|
||||
Content = text,
|
||||
MentionedUserIds = mentionedUsers.Select(x => x.Id).ToArray(),
|
||||
Nonce = null,
|
||||
IsTTS = isTextToSpeech
|
||||
};
|
||||
var model = await _clientRest.Send(request).ConfigureAwait(false);
|
||||
msg = _messages.GetOrAdd(model.Id, channel.Id, model.Author.Id);
|
||||
msg.Update(model);
|
||||
RaiseMessageSent(msg);
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
/// <summary> Edits the provided message, changing only non-null attributes. </summary>
|
||||
/// <remarks> While not required, it is recommended to include a mention reference in the text (see Mention.User). </remarks>
|
||||
public async Task EditMessage(Message message, string text)
|
||||
{
|
||||
if (message == null) throw new ArgumentNullException(nameof(message));
|
||||
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||
CheckReady();
|
||||
|
||||
var channel = message.Channel;
|
||||
var mentionedUsers = new List<User>();
|
||||
if (!channel.IsPrivate)
|
||||
text = Mention.CleanUserMentions(this, channel.Server, text, mentionedUsers);
|
||||
|
||||
if (text.Length > MaxMessageSize)
|
||||
throw new ArgumentOutOfRangeException(nameof(text), $"Message must be {MaxMessageSize} characters or less.");
|
||||
|
||||
if (Config.UseMessageQueue)
|
||||
_pendingMessages.Enqueue(new MessageQueueItem(message, text, mentionedUsers.Select(x => x.Id).ToArray()));
|
||||
else
|
||||
{
|
||||
var request = new UpdateMessageRequest(message.Channel.Id, message.Id)
|
||||
{
|
||||
Content = text,
|
||||
MentionedUserIds = mentionedUsers.Select(x => x.Id).ToArray()
|
||||
};
|
||||
await _clientRest.Send(request).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Deletes the provided message. </summary>
|
||||
public async Task DeleteMessage(Message message)
|
||||
{
|
||||
if (message == null) throw new ArgumentNullException(nameof(message));
|
||||
CheckReady();
|
||||
|
||||
var request = new DeleteMessageRequest(message.Id, message.Channel.Id);
|
||||
try { await _clientRest.Send(request).ConfigureAwait(false); }
|
||||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
|
||||
}
|
||||
public async Task DeleteMessages(IEnumerable<Message> messages)
|
||||
{
|
||||
if (messages == null) throw new ArgumentNullException(nameof(messages));
|
||||
CheckReady();
|
||||
|
||||
foreach (var message in messages)
|
||||
{
|
||||
var request = new DeleteMessageRequest(message.Id, message.Channel.Id);
|
||||
try { await _clientRest.Send(request).ConfigureAwait(false); }
|
||||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Downloads messages from the server, returning all messages before or after relativeMessageId, if it's provided. </summary>
|
||||
public async Task<Message[]> DownloadMessages(Channel channel, int limit = 100, ulong? relativeMessageId = null, RelativeDirection relativeDir = RelativeDirection.Before, bool useCache = true)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
if (limit < 0) throw new ArgumentNullException(nameof(limit));
|
||||
CheckReady();
|
||||
|
||||
if (limit == 0) return new Message[0];
|
||||
if (channel != null && channel.Type == ChannelType.Text)
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = new GetMessagesRequest(channel.Id)
|
||||
{
|
||||
Limit = limit,
|
||||
RelativeDir = relativeDir == RelativeDirection.Before ? "before" : "after",
|
||||
RelativeId = relativeMessageId
|
||||
};
|
||||
var msgs = await _clientRest.Send(request).ConfigureAwait(false);
|
||||
return msgs.Select(x =>
|
||||
{
|
||||
Message msg = null;
|
||||
if (useCache)
|
||||
{
|
||||
msg = _messages.GetOrAdd(x.Id, x.ChannelId, x.Author.Id);
|
||||
var user = msg.User;
|
||||
if (user != null)
|
||||
user.UpdateActivity(msg.EditedTimestamp ?? msg.Timestamp);
|
||||
}
|
||||
else
|
||||
msg = /*_messages[x.Id] ??*/ new Message(this, x.Id, x.ChannelId, x.Author.Id);
|
||||
msg.Update(x);
|
||||
return msg;
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.Forbidden){ } //Bad Permissions
|
||||
}
|
||||
return new Message[0];
|
||||
}
|
||||
|
||||
/// <summary> Marks a given message as read. </summary>
|
||||
public void AckMessage(Message message)
|
||||
{
|
||||
if (message == null) throw new ArgumentNullException(nameof(message));
|
||||
|
||||
if (!message.IsAuthor)
|
||||
_clientRest.Send(new AckMessageRequest(message.Id, message.Channel.Id));
|
||||
}
|
||||
|
||||
/// <summary> Deserializes messages from JSON format and imports them into the message cache.</summary>
|
||||
public IEnumerable<Message> ImportMessages(Channel channel, string json)
|
||||
{
|
||||
if (json == null) throw new ArgumentNullException(nameof(json));
|
||||
|
||||
var dic = JArray.Parse(json)
|
||||
.Select(x =>
|
||||
{
|
||||
var msg = new Message(this,
|
||||
x["Id"].Value<ulong>(),
|
||||
channel.Id,
|
||||
x["UserId"].Value<ulong>());
|
||||
|
||||
var reader = x.CreateReader();
|
||||
_messageImporter.Populate(reader, msg);
|
||||
msg.Text = Mention.Resolve(msg, msg.RawText);
|
||||
return msg;
|
||||
})
|
||||
.ToDictionary(x => x.Id);
|
||||
_messages.Import(dic);
|
||||
foreach (var msg in dic.Values)
|
||||
{
|
||||
var user = msg.User;
|
||||
if (user != null)
|
||||
user.UpdateActivity(msg.EditedTimestamp ?? msg.Timestamp);
|
||||
}
|
||||
return dic.Values;
|
||||
}
|
||||
|
||||
/// <summary> Serializes the message cache for a given channel to JSON.</summary>
|
||||
public string ExportMessages(Channel channel)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
|
||||
return JsonConvert.SerializeObject(channel.Messages);
|
||||
}
|
||||
|
||||
private Task MessageQueueAsync()
|
||||
{
|
||||
var cancelToken = _cancelToken;
|
||||
int interval = Config.MessageQueueInterval;
|
||||
|
||||
return Task.Run(async () =>
|
||||
{
|
||||
MessageQueueItem queuedMessage;
|
||||
|
||||
while (!cancelToken.IsCancellationRequested)
|
||||
{
|
||||
while (_pendingMessages.TryDequeue(out queuedMessage))
|
||||
{
|
||||
var msg = queuedMessage.Message;
|
||||
try
|
||||
{
|
||||
if (msg.Id == 0)
|
||||
{
|
||||
var request = new SendMessageRequest(msg.Channel.Id)
|
||||
{
|
||||
Content = queuedMessage.Text,
|
||||
MentionedUserIds = queuedMessage.MentionedUsers,
|
||||
Nonce = IdConvert.ToString(msg.Id), //Nonce
|
||||
IsTTS = msg.IsTTS
|
||||
};
|
||||
await _clientRest.Send(request).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
var request = new UpdateMessageRequest(msg.Channel.Id, msg.Id)
|
||||
{
|
||||
Content = queuedMessage.Text,
|
||||
MentionedUserIds = queuedMessage.MentionedUsers
|
||||
};
|
||||
await _clientRest.Send(request).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (WebException) { break; }
|
||||
catch (HttpException) { msg.State = MessageState.Failed; }
|
||||
}
|
||||
await Task.Delay(interval).ConfigureAwait(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
private ulong GenerateNonce()
|
||||
{
|
||||
lock (_nonceRand)
|
||||
return (ulong)_nonceRand.Next(1, int.MaxValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
972
src/Discord.Net/DiscordClient.Obsolete.cs
Normal file
972
src/Discord.Net/DiscordClient.Obsolete.cs
Normal file
@@ -0,0 +1,972 @@
|
||||
namespace Discord
|
||||
{
|
||||
/*public enum RelativeDirection { Before, After }
|
||||
public partial class DiscordClient
|
||||
{
|
||||
/// <summary> Returns the channel with the specified id, or null if none was found. </summary>
|
||||
public Channel GetChannel(ulong id)
|
||||
{
|
||||
CheckReady();
|
||||
|
||||
return _channels[id];
|
||||
}
|
||||
|
||||
/// <summary> Returns all channels with the specified server and name. </summary>
|
||||
/// <remarks> Name formats supported: Name, #Name and <#Id>. Search is case-insensitive if exactMatch is false.</remarks>
|
||||
public IEnumerable<Channel> FindChannels(Server server, string name, ChannelType type = null, bool exactMatch = false)
|
||||
{
|
||||
if (server == null) throw new ArgumentNullException(nameof(server));
|
||||
if (name == null) throw new ArgumentNullException(nameof(name));
|
||||
CheckReady();
|
||||
|
||||
var query = server.Channels.Where(x => string.Equals(x.Name, name, exactMatch ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (!exactMatch && name.Length >= 2)
|
||||
{
|
||||
if (name[0] == '<' && name[1] == '#' && name[name.Length - 1] == '>') //Parse mention
|
||||
{
|
||||
var id = IdConvert.ToLong(name.Substring(2, name.Length - 3));
|
||||
var channel = _channels[id];
|
||||
if (channel != null)
|
||||
query = query.Concat(new Channel[] { channel });
|
||||
}
|
||||
else if (name[0] == '#' && (type == null || type == ChannelType.Text)) //If we somehow get text starting with # but isn't a mention
|
||||
{
|
||||
string name2 = name.Substring(1);
|
||||
query = query.Concat(server.TextChannels.Where(x => string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)));
|
||||
}
|
||||
}
|
||||
|
||||
if (type != null)
|
||||
query = query.Where(x => x.Type == type);
|
||||
return query;
|
||||
}
|
||||
|
||||
/// <summary> Creates a new channel with the provided name and type. </summary>
|
||||
public async Task<Channel> CreateChannel(Server server, string name, ChannelType type)
|
||||
{
|
||||
if (server == null) throw new ArgumentNullException(nameof(server));
|
||||
if (name == null) throw new ArgumentNullException(nameof(name));
|
||||
if (type == null) throw new ArgumentNullException(nameof(type));
|
||||
CheckReady();
|
||||
|
||||
var request = new CreateChannelRequest(server.Id) { Name = name, Type = type.Value };
|
||||
var response = await _clientRest.Send(request).ConfigureAwait(false);
|
||||
|
||||
var channel = _channels.GetOrAdd(response.Id, response.GuildId, response.Recipient?.Id);
|
||||
channel.Update(response);
|
||||
return channel;
|
||||
}
|
||||
|
||||
/// <summary> Returns the private channel with the provided user, creating one if it does not currently exist. </summary>
|
||||
public async Task<Channel> CreatePMChannel(User user)
|
||||
{
|
||||
if (user == null) throw new ArgumentNullException(nameof(user));
|
||||
CheckReady();
|
||||
|
||||
Channel channel = null;
|
||||
if (user != null)
|
||||
channel = user.Global.PrivateChannel;
|
||||
if (channel == null)
|
||||
{
|
||||
var request = new CreatePrivateChannelRequest() { RecipientId = user.Id };
|
||||
var response = await _clientRest.Send(request).ConfigureAwait(false);
|
||||
|
||||
var recipient = _users.GetOrAdd(response.Recipient.Id, null);
|
||||
recipient.Update(response.Recipient);
|
||||
channel = _channels.GetOrAdd(response.Id, response.GuildId, response.Recipient.Id);
|
||||
channel.Update(response);
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
/// <summary> Edits the provided channel, changing only non-null attributes. </summary>
|
||||
public async Task EditChannel(Channel channel, string name = null, string topic = null, int? position = null)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
CheckReady();
|
||||
|
||||
if (name != null || topic != null)
|
||||
{
|
||||
var request = new UpdateChannelRequest(channel.Id)
|
||||
{
|
||||
Name = name ?? channel.Name,
|
||||
Topic = topic ?? channel.Topic,
|
||||
Position = channel.Position
|
||||
};
|
||||
await _clientRest.Send(request).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (position != null)
|
||||
{
|
||||
Channel[] channels = channel.Server.Channels.Where(x => x.Type == channel.Type).OrderBy(x => x.Position).ToArray();
|
||||
int oldPos = Array.IndexOf(channels, channel);
|
||||
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] = channel;
|
||||
}
|
||||
else //(oldPos > newPos) Moving Up
|
||||
{
|
||||
minPos = newPos;
|
||||
for (int i = oldPos; i > newPos; i--)
|
||||
channels[i] = channels[i - 1];
|
||||
channels[newPos] = channel;
|
||||
}
|
||||
Channel after = minPos > 0 ? channels.Skip(minPos - 1).FirstOrDefault() : null;
|
||||
await ReorderChannels(channel.Server, channels.Skip(minPos), after).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Reorders the provided channels in the server's channel list and places them after a certain channel. </summary>
|
||||
public Task ReorderChannels(Server server, IEnumerable<Channel> channels, Channel after = null)
|
||||
{
|
||||
if (server == null) throw new ArgumentNullException(nameof(server));
|
||||
if (channels == null) throw new ArgumentNullException(nameof(channels));
|
||||
CheckReady();
|
||||
|
||||
var request = new ReorderChannelsRequest(server.Id)
|
||||
{
|
||||
ChannelIds = channels.Select(x => x.Id).ToArray(),
|
||||
StartPos = after != null ? after.Position + 1 : channels.Min(x => x.Position)
|
||||
};
|
||||
return _clientRest.Send(request);
|
||||
}
|
||||
|
||||
/// <summary> Destroys the provided channel. </summary>
|
||||
public async Task DeleteChannel(Channel channel)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
CheckReady();
|
||||
|
||||
try { await _clientRest.Send(new DeleteChannelRequest(channel.Id)).ConfigureAwait(false); }
|
||||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
|
||||
}
|
||||
|
||||
/// <summary> Gets more info about the provided invite code. </summary>
|
||||
/// <remarks> Supported formats: inviteCode, xkcdCode, https://discord.gg/inviteCode, https://discord.gg/xkcdCode </remarks>
|
||||
public async Task<Invite> GetInvite(string inviteIdOrXkcd)
|
||||
{
|
||||
if (inviteIdOrXkcd == null) throw new ArgumentNullException(nameof(inviteIdOrXkcd));
|
||||
CheckReady();
|
||||
|
||||
//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 _clientRest.Send(new GetInviteRequest(inviteIdOrXkcd)).ConfigureAwait(false);
|
||||
var invite = new Invite(response.Code, response.XkcdPass);
|
||||
invite.Update(response);
|
||||
return invite;
|
||||
}
|
||||
|
||||
/// <summary> Gets all active (non-expired) invites to a provided server. </summary>
|
||||
public async Task<Invite[]> GetInvites(Server server)
|
||||
{
|
||||
if (server == null) throw new ArgumentNullException(nameof(server));
|
||||
CheckReady();
|
||||
|
||||
var response = await _clientRest.Send(new GetInvitesRequest(server.Id)).ConfigureAwait(false);
|
||||
return response.Select(x =>
|
||||
{
|
||||
var invite = new Invite(x.Code, x.XkcdPass);
|
||||
invite.Update(x);
|
||||
return invite;
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
/// <summary> Creates a new invite to the default channel of the provided server. </summary>
|
||||
/// <param name="maxAge"> Time (in seconds) until the invite expires. Set to 0 to never expire. </param>
|
||||
/// <param name="tempMembership"> If true, a user accepting this invite will be kicked from the server after closing their client. </param>
|
||||
/// <param name="hasXkcd"> If true, creates a human-readable link. Not supported if maxAge is set to 0. </param>
|
||||
/// <param name="maxUses"> The max amount of times this invite may be used. Set to 0 to have unlimited uses. </param>
|
||||
public Task<Invite> CreateInvite(Server server, int maxAge = 1800, int maxUses = 0, bool tempMembership = false, bool hasXkcd = false)
|
||||
{
|
||||
if (server == null) throw new ArgumentNullException(nameof(server));
|
||||
CheckReady();
|
||||
|
||||
return CreateInvite(server.DefaultChannel, maxAge, maxUses, tempMembership, hasXkcd);
|
||||
}
|
||||
/// <summary> Creates a new invite to the provided channel. </summary>
|
||||
/// <param name="maxAge"> Time (in seconds) until the invite expires. Set to 0 to never expire. </param>
|
||||
/// <param name="tempMembership"> If true, a user accepting this invite will be kicked from the server after closing their client. </param>
|
||||
/// <param name="hasXkcd"> If true, creates a human-readable link. Not supported if maxAge is set to 0. </param>
|
||||
/// <param name="maxUses"> The max amount of times this invite may be used. Set to 0 to have unlimited uses. </param>
|
||||
public async Task<Invite> CreateInvite(Channel channel, int maxAge = 1800, int maxUses = 0, bool isTemporary = false, bool withXkcd = false)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
if (maxAge < 0) throw new ArgumentOutOfRangeException(nameof(maxAge));
|
||||
if (maxUses < 0) throw new ArgumentOutOfRangeException(nameof(maxUses));
|
||||
CheckReady();
|
||||
|
||||
var request = new CreateInviteRequest(channel.Id)
|
||||
{
|
||||
MaxAge = maxAge,
|
||||
MaxUses = maxUses,
|
||||
IsTemporary = isTemporary,
|
||||
WithXkcdPass = withXkcd
|
||||
};
|
||||
|
||||
var response = await _clientRest.Send(request).ConfigureAwait(false);
|
||||
var invite = new Invite(response.Code, response.XkcdPass);
|
||||
return invite;
|
||||
}
|
||||
|
||||
/// <summary> Deletes the provided invite. </summary>
|
||||
public async Task DeleteInvite(Invite invite)
|
||||
{
|
||||
if (invite == null) throw new ArgumentNullException(nameof(invite));
|
||||
CheckReady();
|
||||
|
||||
try { await _clientRest.Send(new DeleteInviteRequest(invite.Code)).ConfigureAwait(false); }
|
||||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
|
||||
}
|
||||
|
||||
/// <summary> Accepts the provided invite. </summary>
|
||||
public Task AcceptInvite(Invite invite)
|
||||
{
|
||||
if (invite == null) throw new ArgumentNullException(nameof(invite));
|
||||
CheckReady();
|
||||
|
||||
return _clientRest.Send(new AcceptInviteRequest(invite.Code));
|
||||
}
|
||||
|
||||
|
||||
/// <summary> Returns the message with the specified id, or null if none was found. </summary>
|
||||
public Message GetMessage(ulong id)
|
||||
{
|
||||
if (id <= 0) throw new ArgumentOutOfRangeException(nameof(id));
|
||||
CheckReady();
|
||||
|
||||
return _messages[id];
|
||||
}
|
||||
|
||||
/// <summary> Sends a message to the provided channel. To include a mention, see the Mention static helper class. </summary>
|
||||
public Task<Message> SendMessage(Channel channel, string text)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||
CheckReady();
|
||||
|
||||
return SendMessageInternal(channel, text, false);
|
||||
}
|
||||
/// <summary> Sends a private message to the provided user. </summary>
|
||||
public async Task<Message> SendMessage(User user, string text)
|
||||
{
|
||||
if (user == null) throw new ArgumentNullException(nameof(user));
|
||||
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||
CheckReady();
|
||||
|
||||
var channel = await CreatePMChannel(user).ConfigureAwait(false);
|
||||
return await SendMessageInternal(channel, text, false).ConfigureAwait(false);
|
||||
}
|
||||
/// <summary> Sends a text-to-speech message to the provided channel. To include a mention, see the Mention static helper class. </summary>
|
||||
public Task<Message> SendTTSMessage(Channel channel, string text)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||
CheckReady();
|
||||
|
||||
return SendMessageInternal(channel, text, true);
|
||||
}
|
||||
/// <summary> Sends a file to the provided channel. </summary>
|
||||
public Task<Message> SendFile(Channel channel, string filePath)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
if (filePath == null) throw new ArgumentNullException(nameof(filePath));
|
||||
CheckReady();
|
||||
|
||||
return SendFile(channel, Path.GetFileName(filePath), File.OpenRead(filePath));
|
||||
}
|
||||
/// <summary> Sends a file to the provided channel. </summary>
|
||||
public async Task<Message> SendFile(Channel channel, string filename, Stream stream)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
if (filename == null) throw new ArgumentNullException(nameof(filename));
|
||||
if (stream == null) throw new ArgumentNullException(nameof(stream));
|
||||
CheckReady();
|
||||
|
||||
var request = new SendFileRequest(channel.Id)
|
||||
{
|
||||
Filename = filename,
|
||||
Stream = stream
|
||||
};
|
||||
var model = await _clientRest.Send(request).ConfigureAwait(false);
|
||||
|
||||
var msg = _messages.GetOrAdd(model.Id, channel.Id, model.Author.Id);
|
||||
msg.Update(model);
|
||||
RaiseMessageSent(msg);
|
||||
return msg;
|
||||
}
|
||||
/// <summary> Sends a file to the provided channel. </summary>
|
||||
public async Task<Message> SendFile(User user, string filePath)
|
||||
{
|
||||
if (user == null) throw new ArgumentNullException(nameof(user));
|
||||
if (filePath == null) throw new ArgumentNullException(nameof(filePath));
|
||||
CheckReady();
|
||||
|
||||
var channel = await CreatePMChannel(user).ConfigureAwait(false);
|
||||
return await SendFile(channel, Path.GetFileName(filePath), File.OpenRead(filePath)).ConfigureAwait(false);
|
||||
}
|
||||
/// <summary> Sends a file to the provided channel. </summary>
|
||||
public async Task<Message> SendFile(User user, string filename, Stream stream)
|
||||
{
|
||||
if (user == null) throw new ArgumentNullException(nameof(user));
|
||||
if (filename == null) throw new ArgumentNullException(nameof(filename));
|
||||
if (stream == null) throw new ArgumentNullException(nameof(stream));
|
||||
CheckReady();
|
||||
|
||||
var channel = await CreatePMChannel(user).ConfigureAwait(false);
|
||||
return await SendFile(channel, filename, stream).ConfigureAwait(false);
|
||||
}
|
||||
private async Task<Message> SendMessageInternal(Channel channel, string text, bool isTextToSpeech)
|
||||
{
|
||||
Message msg;
|
||||
var server = channel.Server;
|
||||
|
||||
var mentionedUsers = new List<User>();
|
||||
text = Mention.CleanUserMentions(this, server, text, mentionedUsers);
|
||||
if (text.Length > MaxMessageSize)
|
||||
throw new ArgumentOutOfRangeException(nameof(text), $"Message must be {MaxMessageSize} characters or less.");
|
||||
|
||||
if (Config.UseMessageQueue)
|
||||
{
|
||||
var nonce = GenerateNonce();
|
||||
msg = new Message(this, 0, channel.Id, _currentUser.Id); //_messages.GetOrAdd(nonce, channel.Id, _privateUser.Id);
|
||||
var currentUser = msg.User;
|
||||
msg.Update(new APIMessage
|
||||
{
|
||||
Content = text,
|
||||
Timestamp = DateTime.UtcNow,
|
||||
Author = new APIUser { Avatar = currentUser.AvatarId, Discriminator = currentUser.Discriminator, Id = _currentUser.Id, Username = currentUser.Name },
|
||||
ChannelId = channel.Id,
|
||||
Nonce = IdConvert.ToString(nonce),
|
||||
IsTextToSpeech = isTextToSpeech
|
||||
});
|
||||
msg.State = MessageState.Queued;
|
||||
|
||||
_pendingMessages.Enqueue(new MessageQueueItem(msg, text, mentionedUsers.Select(x => x.Id).ToArray()));
|
||||
}
|
||||
else
|
||||
{
|
||||
var request = new SendMessageRequest(channel.Id)
|
||||
{
|
||||
Content = text,
|
||||
MentionedUserIds = mentionedUsers.Select(x => x.Id).ToArray(),
|
||||
Nonce = null,
|
||||
IsTTS = isTextToSpeech
|
||||
};
|
||||
var model = await _clientRest.Send(request).ConfigureAwait(false);
|
||||
msg = _messages.GetOrAdd(model.Id, channel.Id, model.Author.Id);
|
||||
msg.Update(model);
|
||||
RaiseMessageSent(msg);
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
/// <summary> Edits the provided message, changing only non-null attributes. </summary>
|
||||
/// <remarks> While not required, it is recommended to include a mention reference in the text (see Mention.User). </remarks>
|
||||
public async Task EditMessage(Message message, string text)
|
||||
{
|
||||
if (message == null) throw new ArgumentNullException(nameof(message));
|
||||
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||
CheckReady();
|
||||
|
||||
var channel = message.Channel;
|
||||
var mentionedUsers = new List<User>();
|
||||
if (!channel.IsPrivate)
|
||||
text = Mention.CleanUserMentions(this, channel.Server, text, mentionedUsers);
|
||||
|
||||
if (text.Length > DiscordConfig.MaxMessageSize)
|
||||
throw new ArgumentOutOfRangeException(nameof(text), $"Message must be {DiscordConfig.MaxMessageSize} characters or less.");
|
||||
|
||||
if (Config.UseMessageQueue)
|
||||
_pendingMessages.Enqueue(new MessageQueueItem(message, text, mentionedUsers.Select(x => x.Id).ToArray()));
|
||||
else
|
||||
{
|
||||
var request = new UpdateMessageRequest(message.Channel.Id, message.Id)
|
||||
{
|
||||
Content = text,
|
||||
MentionedUserIds = mentionedUsers.Select(x => x.Id).ToArray()
|
||||
};
|
||||
await _clientRest.Send(request).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Deletes the provided message. </summary>
|
||||
public async Task DeleteMessage(Message message)
|
||||
{
|
||||
if (message == null) throw new ArgumentNullException(nameof(message));
|
||||
CheckReady();
|
||||
|
||||
var request = new DeleteMessageRequest(message.Id, message.Channel.Id);
|
||||
try { await _clientRest.Send(request).ConfigureAwait(false); }
|
||||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
|
||||
}
|
||||
public async Task DeleteMessages(IEnumerable<Message> messages)
|
||||
{
|
||||
if (messages == null) throw new ArgumentNullException(nameof(messages));
|
||||
CheckReady();
|
||||
|
||||
foreach (var message in messages)
|
||||
{
|
||||
var request = new DeleteMessageRequest(message.Id, message.Channel.Id);
|
||||
try { await _clientRest.Send(request).ConfigureAwait(false); }
|
||||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Downloads messages from the server, returning all messages before or after relativeMessageId, if it's provided. </summary>
|
||||
public async Task<Message[]> DownloadMessages(Channel channel, int limit = 100, ulong? relativeMessageId = null, RelativeDirection relativeDir = RelativeDirection.Before, bool useCache = true)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
if (limit < 0) throw new ArgumentNullException(nameof(limit));
|
||||
CheckReady();
|
||||
|
||||
if (limit == 0) return new Message[0];
|
||||
if (channel != null && channel.Type == ChannelType.Text)
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = new GetMessagesRequest(channel.Id)
|
||||
{
|
||||
Limit = limit,
|
||||
RelativeDir = relativeDir == RelativeDirection.Before ? "before" : "after",
|
||||
RelativeId = relativeMessageId
|
||||
};
|
||||
var msgs = await _clientRest.Send(request).ConfigureAwait(false);
|
||||
return msgs.Select(x =>
|
||||
{
|
||||
Message msg = null;
|
||||
if (useCache)
|
||||
{
|
||||
msg = _messages.GetOrAdd(x.Id, x.ChannelId, x.Author.Id);
|
||||
var user = msg.User;
|
||||
if (user != null)
|
||||
user.UpdateActivity(msg.EditedTimestamp ?? msg.Timestamp);
|
||||
}
|
||||
else
|
||||
msg = new Message(this, x.Id, x.ChannelId, x.Author.Id);
|
||||
msg.Update(x);
|
||||
return msg;
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.Forbidden) { } //Bad Permissions
|
||||
}
|
||||
return new Message[0];
|
||||
}
|
||||
|
||||
/// <summary> Marks a given message as read. </summary>
|
||||
public void AckMessage(Message message)
|
||||
{
|
||||
if (message == null) throw new ArgumentNullException(nameof(message));
|
||||
|
||||
if (!message.IsAuthor)
|
||||
_clientRest.Send(new AckMessageRequest(message.Id, message.Channel.Id));
|
||||
}
|
||||
|
||||
/// <summary> Deserializes messages from JSON format and imports them into the message cache.</summary>
|
||||
public IEnumerable<Message> ImportMessages(Channel channel, string json)
|
||||
{
|
||||
if (json == null) throw new ArgumentNullException(nameof(json));
|
||||
|
||||
var dic = JArray.Parse(json)
|
||||
.Select(x =>
|
||||
{
|
||||
var msg = new Message(this,
|
||||
x["Id"].Value<ulong>(),
|
||||
channel.Id,
|
||||
x["UserId"].Value<ulong>());
|
||||
|
||||
var reader = x.CreateReader();
|
||||
_messageImporter.Populate(reader, msg);
|
||||
msg.Text = Mention.Resolve(msg, msg.RawText);
|
||||
return msg;
|
||||
})
|
||||
.ToDictionary(x => x.Id);
|
||||
_messages.Import(dic);
|
||||
foreach (var msg in dic.Values)
|
||||
{
|
||||
var user = msg.User;
|
||||
if (user != null)
|
||||
user.UpdateActivity(msg.EditedTimestamp ?? msg.Timestamp);
|
||||
}
|
||||
return dic.Values;
|
||||
}
|
||||
|
||||
/// <summary> Serializes the message cache for a given channel to JSON.</summary>
|
||||
public string ExportMessages(Channel channel)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
|
||||
return JsonConvert.SerializeObject(channel.Messages);
|
||||
}
|
||||
|
||||
/// <summary> Returns the user with the specified id, along with their server-specific data, or null if none was found. </summary>
|
||||
public User GetUser(Server server, ulong userId)
|
||||
{
|
||||
if (server == null) throw new ArgumentNullException(nameof(server));
|
||||
CheckReady();
|
||||
|
||||
return _users[userId, server.Id];
|
||||
}
|
||||
/// <summary> Returns the user with the specified name and discriminator, along withtheir server-specific data, or null if they couldn't be found. </summary>
|
||||
public User GetUser(Server server, string username, ushort discriminator)
|
||||
{
|
||||
if (server == null) throw new ArgumentNullException(nameof(server));
|
||||
if (username == null) throw new ArgumentNullException(nameof(username));
|
||||
CheckReady();
|
||||
|
||||
return FindUsers(server.Members, server.Id, username, discriminator, true).FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary> Returns all users with the specified server and name, along with their server-specific data. </summary>
|
||||
/// <remarks> Name formats supported: Name, @Name and <@Id>. Search is case-insensitive if exactMatch is false.</remarks>
|
||||
public IEnumerable<User> FindUsers(Server server, string name, bool exactMatch = false)
|
||||
{
|
||||
if (server == null) throw new ArgumentNullException(nameof(server));
|
||||
if (name == null) throw new ArgumentNullException(nameof(name));
|
||||
CheckReady();
|
||||
|
||||
return FindUsers(server.Members, server.Id, name, exactMatch: exactMatch);
|
||||
}
|
||||
/// <summary> Returns all users with the specified channel and name, along with their server-specific data. </summary>
|
||||
/// <remarks> Name formats supported: Name, @Name and <@Id>. Search is case-insensitive if exactMatch is false.</remarks>
|
||||
public IEnumerable<User> FindUsers(Channel channel, string name, bool exactMatch = false)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
if (name == null) throw new ArgumentNullException(nameof(name));
|
||||
CheckReady();
|
||||
|
||||
return FindUsers(channel.Members, channel.IsPrivate ? (ulong?)null : channel.Server.Id, name, exactMatch: exactMatch);
|
||||
}
|
||||
|
||||
private IEnumerable<User> FindUsers(IEnumerable<User> users, ulong? serverId, string name, ushort? discriminator = null, bool exactMatch = false)
|
||||
{
|
||||
var query = users.Where(x => string.Equals(x.Name, name, exactMatch ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (!exactMatch && name.Length >= 2)
|
||||
{
|
||||
if (name[0] == '<' && name[1] == '@' && name[name.Length - 1] == '>') //Parse mention
|
||||
{
|
||||
ulong id = IdConvert.ToLong(name.Substring(2, name.Length - 3));
|
||||
var user = _users[id, serverId];
|
||||
if (user != null)
|
||||
query = query.Concat(new User[] { user });
|
||||
}
|
||||
else if (name[0] == '@') //If we somehow get text starting with @ but isn't a 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 Task EditUser(User user, bool? isMuted = null, bool? isDeafened = null, Channel voiceChannel = null, IEnumerable<Role> roles = null)
|
||||
{
|
||||
if (user == null) throw new ArgumentNullException(nameof(user));
|
||||
if (user.IsPrivate) throw new InvalidOperationException("Unable to edit users in a private channel");
|
||||
CheckReady();
|
||||
|
||||
//Modify the roles collection and filter out the everyone role
|
||||
var roleIds = roles == null ? null : user.Roles.Where(x => !x.IsEveryone).Select(x => x.Id);
|
||||
|
||||
var request = new UpdateMemberRequest(user.Server.Id, user.Id)
|
||||
{
|
||||
IsMuted = isMuted ?? user.IsServerMuted,
|
||||
IsDeafened = isDeafened ?? user.IsServerDeafened,
|
||||
VoiceChannelId = voiceChannel?.Id,
|
||||
RoleIds = roleIds.ToArray()
|
||||
};
|
||||
return _clientRest.Send(request);
|
||||
}
|
||||
|
||||
public Task KickUser(User user)
|
||||
{
|
||||
if (user == null) throw new ArgumentNullException(nameof(user));
|
||||
if (user.IsPrivate) throw new InvalidOperationException("Unable to kick users from a private channel");
|
||||
CheckReady();
|
||||
|
||||
var request = new KickMemberRequest(user.Server.Id, user.Id);
|
||||
return _clientRest.Send(request);
|
||||
}
|
||||
public Task BanUser(User user, int pruneDays = 0)
|
||||
{
|
||||
if (user == null) throw new ArgumentNullException(nameof(user));
|
||||
if (user.IsPrivate) throw new InvalidOperationException("Unable to ban users from a private channel");
|
||||
CheckReady();
|
||||
|
||||
var request = new AddGuildBanRequest(user.Server.Id, user.Id);
|
||||
request.PruneDays = pruneDays;
|
||||
return _clientRest.Send(request);
|
||||
}
|
||||
public async Task UnbanUser(Server server, ulong userId)
|
||||
{
|
||||
if (server == null) throw new ArgumentNullException(nameof(server));
|
||||
if (userId <= 0) throw new ArgumentOutOfRangeException(nameof(userId));
|
||||
CheckReady();
|
||||
|
||||
try { await _clientRest.Send(new RemoveGuildBanRequest(server.Id, userId)).ConfigureAwait(false); }
|
||||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
|
||||
}
|
||||
|
||||
public async Task<int> PruneUsers(Server server, int days, bool simulate = false)
|
||||
{
|
||||
if (server == null) throw new ArgumentNullException(nameof(server));
|
||||
if (days <= 0) throw new ArgumentOutOfRangeException(nameof(days));
|
||||
CheckReady();
|
||||
|
||||
var request = new PruneMembersRequest(server.Id)
|
||||
{
|
||||
Days = days,
|
||||
IsSimulation = simulate
|
||||
};
|
||||
var response = await _clientRest.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 a particular server.</summary>
|
||||
public void RequestOfflineUsers(Server server)
|
||||
{
|
||||
if (server == null) throw new ArgumentNullException(nameof(server));
|
||||
|
||||
_webSocket.SendRequestMembers(server.Id, "", 0);
|
||||
}
|
||||
|
||||
public async Task EditProfile(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));
|
||||
CheckReady();
|
||||
|
||||
var request = new UpdateProfileRequest()
|
||||
{
|
||||
CurrentPassword = currentPassword,
|
||||
Email = email ?? _currentUser?.Email,
|
||||
Password = password,
|
||||
Username = username ?? _privateUser?.Name,
|
||||
AvatarBase64 = Base64Image(avatarType, avatar, _privateUser?.AvatarId)
|
||||
};
|
||||
|
||||
await _clientRest.Send(request).ConfigureAwait(false);
|
||||
|
||||
if (password != null)
|
||||
{
|
||||
var loginRequest = new LoginRequest()
|
||||
{
|
||||
Email = _currentUser.Email,
|
||||
Password = password
|
||||
};
|
||||
var loginResponse = await _clientRest.Send(loginRequest).ConfigureAwait(false);
|
||||
_clientRest.SetToken(loginResponse.Token);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Returns the role with the specified id, or null if none was found. </summary>
|
||||
public Role GetRole(ulong id)
|
||||
{
|
||||
CheckReady();
|
||||
|
||||
return _roles[id];
|
||||
}
|
||||
/// <summary> Returns all roles with the specified server and name. </summary>
|
||||
/// <remarks> Name formats supported: Name and @Name. Search is case-insensitive. </remarks>
|
||||
public IEnumerable<Role> FindRoles(Server server, string name)
|
||||
{
|
||||
if (server == null) throw new ArgumentNullException(nameof(server));
|
||||
if (name == null) throw new ArgumentNullException(nameof(name));
|
||||
CheckReady();
|
||||
|
||||
// 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 => x.Server.Id == server.Id &&
|
||||
string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase));
|
||||
// }
|
||||
}
|
||||
|
||||
/// <summary> Note: due to current API limitations, the created role cannot be returned. </summary>
|
||||
public async Task<Role> CreateRole(Server server, string name, ServerPermissions permissions = null, Color color = null, bool isHoisted = false)
|
||||
{
|
||||
if (server == null) throw new ArgumentNullException(nameof(server));
|
||||
if (name == null) throw new ArgumentNullException(nameof(name));
|
||||
CheckReady();
|
||||
|
||||
var request1 = new CreateRoleRequest(server.Id);
|
||||
var response1 = await _clientRest.Send(request1).ConfigureAwait(false);
|
||||
var role = _roles.GetOrAdd(response1.Id, server.Id);
|
||||
role.Update(response1);
|
||||
|
||||
var request2 = new UpdateRoleRequest(role.Server.Id, role.Id)
|
||||
{
|
||||
Name = name,
|
||||
Permissions = (permissions ?? role.Permissions).RawValue,
|
||||
Color = (color ?? Color.Default).RawValue,
|
||||
IsHoisted = isHoisted
|
||||
};
|
||||
var response2 = await _clientRest.Send(request2).ConfigureAwait(false);
|
||||
role.Update(response2);
|
||||
|
||||
return role;
|
||||
}
|
||||
|
||||
public async Task EditRole(Role role, string name = null, ServerPermissions permissions = null, Color color = null, bool? isHoisted = null, int? position = null)
|
||||
{
|
||||
if (role == null) throw new ArgumentNullException(nameof(role));
|
||||
CheckReady();
|
||||
|
||||
var request1 = new UpdateRoleRequest(role.Server.Id, role.Id)
|
||||
{
|
||||
Name = name ?? role.Name,
|
||||
Permissions = (permissions ?? role.Permissions).RawValue,
|
||||
Color = (color ?? role.Color).RawValue,
|
||||
IsHoisted = isHoisted ?? role.IsHoisted
|
||||
};
|
||||
|
||||
var response = await _clientRest.Send(request1).ConfigureAwait(false);
|
||||
|
||||
if (position != null)
|
||||
{
|
||||
int oldPos = role.Position;
|
||||
int newPos = position.Value;
|
||||
int minPos;
|
||||
Role[] roles = role.Server.Roles.OrderBy(x => x.Position).ToArray();
|
||||
|
||||
if (oldPos < newPos) //Moving Down
|
||||
{
|
||||
minPos = oldPos;
|
||||
for (int i = oldPos; i < newPos; i++)
|
||||
roles[i] = roles[i + 1];
|
||||
roles[newPos] = role;
|
||||
}
|
||||
else //(oldPos > newPos) Moving Up
|
||||
{
|
||||
minPos = newPos;
|
||||
for (int i = oldPos; i > newPos; i--)
|
||||
roles[i] = roles[i - 1];
|
||||
roles[newPos] = role;
|
||||
}
|
||||
|
||||
var request2 = new ReorderRolesRequest(role.Server.Id)
|
||||
{
|
||||
RoleIds = roles.Skip(minPos).Select(x => x.Id).ToArray(),
|
||||
StartPos = minPos
|
||||
};
|
||||
await _clientRest.Send(request2).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteRole(Role role)
|
||||
{
|
||||
if (role == null) throw new ArgumentNullException(nameof(role));
|
||||
CheckReady();
|
||||
|
||||
try { await _clientRest.Send(new DeleteRoleRequest(role.Server.Id, role.Id)).ConfigureAwait(false); }
|
||||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
|
||||
}
|
||||
|
||||
public Task ReorderRoles(Server server, IEnumerable<Role> roles, int startPos = 0)
|
||||
{
|
||||
if (server == null) throw new ArgumentNullException(nameof(server));
|
||||
if (roles == null) throw new ArgumentNullException(nameof(roles));
|
||||
if (startPos < 0) throw new ArgumentOutOfRangeException(nameof(startPos), "startPos must be a positive integer.");
|
||||
CheckReady();
|
||||
|
||||
return _clientRest.Send(new ReorderRolesRequest(server.Id)
|
||||
{
|
||||
RoleIds = roles.Select(x => x.Id).ToArray(),
|
||||
StartPos = startPos
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary> Returns the server with the specified id, or null if none was found. </summary>
|
||||
public Server GetServer(ulong id)
|
||||
{
|
||||
CheckReady();
|
||||
|
||||
return _servers[id];
|
||||
}
|
||||
|
||||
/// <summary> Returns all servers with the specified name. </summary>
|
||||
/// <remarks> Search is case-insensitive. </remarks>
|
||||
public IEnumerable<Server> FindServers(string name)
|
||||
{
|
||||
if (name == null) throw new ArgumentNullException(nameof(name));
|
||||
CheckReady();
|
||||
|
||||
return _servers.Where(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
/// <summary> Creates a new server with the provided name and region (see Regions). </summary>
|
||||
public async Task<Server> CreateServer(string name, 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));
|
||||
CheckReady();
|
||||
|
||||
var request = new CreateGuildRequest()
|
||||
{
|
||||
Name = name,
|
||||
Region = region.Id,
|
||||
IconBase64 = Base64Image(iconType, icon, null)
|
||||
};
|
||||
var response = await _clientRest.Send(request).ConfigureAwait(false);
|
||||
|
||||
var server = _servers.GetOrAdd(response.Id);
|
||||
server.Update(response);
|
||||
return server;
|
||||
}
|
||||
|
||||
/// <summary> Edits the provided server, changing only non-null attributes. </summary>
|
||||
public async Task EditServer(Server server, string name = null, string region = null, Stream icon = null, ImageType iconType = ImageType.Png)
|
||||
{
|
||||
if (server == null) throw new ArgumentNullException(nameof(server));
|
||||
CheckReady();
|
||||
|
||||
var request = new UpdateGuildRequest(server.Id)
|
||||
{
|
||||
Name = name ?? server.Name,
|
||||
Region = region ?? server.Region,
|
||||
IconBase64 = Base64Image(iconType, icon, server.IconId),
|
||||
AFKChannelId = server.AFKChannel?.Id,
|
||||
AFKTimeout = server.AFKTimeout
|
||||
};
|
||||
var response = await _clientRest.Send(request).ConfigureAwait(false);
|
||||
server.Update(response);
|
||||
}
|
||||
|
||||
/// <summary> Leaves the provided server, destroying it if you are the owner. </summary>
|
||||
public async Task LeaveServer(Server server)
|
||||
{
|
||||
if (server == null) throw new ArgumentNullException(nameof(server));
|
||||
CheckReady();
|
||||
|
||||
try { await _clientRest.Send(new LeaveGuildRequest(server.Id)).ConfigureAwait(false); }
|
||||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Region>> GetVoiceRegions()
|
||||
{
|
||||
CheckReady();
|
||||
|
||||
var regions = await _clientRest.Send(new GetVoiceRegionsRequest()).ConfigureAwait(false);
|
||||
return regions.Select(x => new Region(x.Id, x.Name, x.Hostname, x.Port));
|
||||
}
|
||||
public DualChannelPermissions GetChannelPermissions(Channel channel, User user)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
if (user == null) throw new ArgumentNullException(nameof(user));
|
||||
CheckReady();
|
||||
|
||||
return channel.PermissionOverwrites
|
||||
.Where(x => x.TargetType == PermissionTarget.User && x.TargetId == user.Id)
|
||||
.Select(x => x.Permissions)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
public DualChannelPermissions GetChannelPermissions(Channel channel, Role role)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
if (role == null) throw new ArgumentNullException(nameof(role));
|
||||
CheckReady();
|
||||
|
||||
return channel.PermissionOverwrites
|
||||
.Where(x => x.TargetType == PermissionTarget.Role && x.TargetId == role.Id)
|
||||
.Select(x => x.Permissions)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
public Task SetChannelPermissions(Channel channel, User user, ChannelPermissions allow = null, ChannelPermissions deny = null)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
if (user == null) throw new ArgumentNullException(nameof(user));
|
||||
CheckReady();
|
||||
|
||||
return SetChannelPermissions(channel, user.Id, PermissionTarget.User, allow, deny);
|
||||
}
|
||||
public Task SetChannelPermissions(Channel channel, User user, DualChannelPermissions permissions = null)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
if (user == null) throw new ArgumentNullException(nameof(user));
|
||||
CheckReady();
|
||||
|
||||
return SetChannelPermissions(channel, user.Id, PermissionTarget.User, permissions?.Allow, permissions?.Deny);
|
||||
}
|
||||
public Task SetChannelPermissions(Channel channel, Role role, ChannelPermissions allow = null, ChannelPermissions deny = null)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
if (role == null) throw new ArgumentNullException(nameof(role));
|
||||
CheckReady();
|
||||
|
||||
return SetChannelPermissions(channel, role.Id, PermissionTarget.Role, allow, deny);
|
||||
}
|
||||
public Task SetChannelPermissions(Channel channel, Role role, DualChannelPermissions permissions = null)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
if (role == null) throw new ArgumentNullException(nameof(role));
|
||||
CheckReady();
|
||||
|
||||
return SetChannelPermissions(channel, role.Id, PermissionTarget.Role, permissions?.Allow, permissions?.Deny);
|
||||
}
|
||||
private Task SetChannelPermissions(Channel channel, ulong targetId, PermissionTarget targetType, ChannelPermissions allow = null, ChannelPermissions deny = null)
|
||||
{
|
||||
var request = new AddChannelPermissionsRequest(channel.Id)
|
||||
{
|
||||
TargetId = targetId,
|
||||
TargetType = targetType.Value,
|
||||
Allow = allow?.RawValue ?? 0,
|
||||
Deny = deny?.RawValue ?? 0
|
||||
};
|
||||
return _clientRest.Send(request);
|
||||
}
|
||||
|
||||
public Task RemoveChannelPermissions(Channel channel, User user)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
if (user == null) throw new ArgumentNullException(nameof(user));
|
||||
CheckReady();
|
||||
|
||||
return RemoveChannelPermissions(channel, user.Id, PermissionTarget.User);
|
||||
}
|
||||
public Task RemoveChannelPermissions(Channel channel, Role role)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
if (role == null) throw new ArgumentNullException(nameof(role));
|
||||
CheckReady();
|
||||
|
||||
return RemoveChannelPermissions(channel, role.Id, PermissionTarget.Role);
|
||||
}
|
||||
private async Task RemoveChannelPermissions(Channel channel, ulong userOrRoleId, PermissionTarget targetType)
|
||||
{
|
||||
try
|
||||
{
|
||||
var perms = channel.PermissionOverwrites.Where(x => x.TargetType != targetType || x.TargetId != userOrRoleId).FirstOrDefault();
|
||||
await _clientRest.Send(new RemoveChannelPermissionsRequest(channel.Id, userOrRoleId)).ConfigureAwait(false);
|
||||
}
|
||||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
|
||||
}
|
||||
}*/
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
using Discord.API.Client.Rest;
|
||||
using Discord.Net;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public partial class DiscordClient
|
||||
{
|
||||
public DualChannelPermissions GetChannelPermissions(Channel channel, User user)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
if (user == null) throw new ArgumentNullException(nameof(user));
|
||||
CheckReady();
|
||||
|
||||
return channel.PermissionOverwrites
|
||||
.Where(x => x.TargetType == PermissionTarget.User && x.TargetId == user.Id)
|
||||
.Select(x => x.Permissions)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
public DualChannelPermissions GetChannelPermissions(Channel channel, Role role)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
if (role == null) throw new ArgumentNullException(nameof(role));
|
||||
CheckReady();
|
||||
|
||||
return channel.PermissionOverwrites
|
||||
.Where(x => x.TargetType == PermissionTarget.Role && x.TargetId == role.Id)
|
||||
.Select(x => x.Permissions)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
public Task SetChannelPermissions(Channel channel, User user, ChannelPermissions allow = null, ChannelPermissions deny = null)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
if (user == null) throw new ArgumentNullException(nameof(user));
|
||||
CheckReady();
|
||||
|
||||
return SetChannelPermissions(channel, user.Id, PermissionTarget.User, allow, deny);
|
||||
}
|
||||
public Task SetChannelPermissions(Channel channel, User user, DualChannelPermissions permissions = null)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
if (user == null) throw new ArgumentNullException(nameof(user));
|
||||
CheckReady();
|
||||
|
||||
return SetChannelPermissions(channel, user.Id, PermissionTarget.User, permissions?.Allow, permissions?.Deny);
|
||||
}
|
||||
public Task SetChannelPermissions(Channel channel, Role role, ChannelPermissions allow = null, ChannelPermissions deny = null)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
if (role == null) throw new ArgumentNullException(nameof(role));
|
||||
CheckReady();
|
||||
|
||||
return SetChannelPermissions(channel, role.Id, PermissionTarget.Role, allow, deny);
|
||||
}
|
||||
public Task SetChannelPermissions(Channel channel, Role role, DualChannelPermissions permissions = null)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
if (role == null) throw new ArgumentNullException(nameof(role));
|
||||
CheckReady();
|
||||
|
||||
return SetChannelPermissions(channel, role.Id, PermissionTarget.Role, permissions?.Allow, permissions?.Deny);
|
||||
}
|
||||
private Task SetChannelPermissions(Channel channel, ulong targetId, PermissionTarget targetType, ChannelPermissions allow = null, ChannelPermissions deny = null)
|
||||
{
|
||||
var request = new AddChannelPermissionsRequest(channel.Id)
|
||||
{
|
||||
TargetId = targetId,
|
||||
TargetType = targetType.Value,
|
||||
Allow = allow?.RawValue ?? 0,
|
||||
Deny = deny?.RawValue ?? 0
|
||||
};
|
||||
return _clientRest.Send(request);
|
||||
}
|
||||
|
||||
public Task RemoveChannelPermissions(Channel channel, User user)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
if (user == null) throw new ArgumentNullException(nameof(user));
|
||||
CheckReady();
|
||||
|
||||
return RemoveChannelPermissions(channel, user.Id, PermissionTarget.User);
|
||||
}
|
||||
public Task RemoveChannelPermissions(Channel channel, Role role)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
if (role == null) throw new ArgumentNullException(nameof(role));
|
||||
CheckReady();
|
||||
|
||||
return RemoveChannelPermissions(channel, role.Id, PermissionTarget.Role);
|
||||
}
|
||||
private async Task RemoveChannelPermissions(Channel channel, ulong userOrRoleId, PermissionTarget targetType)
|
||||
{
|
||||
try
|
||||
{
|
||||
var perms = channel.PermissionOverwrites.Where(x => x.TargetType != targetType || x.TargetId != userOrRoleId).FirstOrDefault();
|
||||
await _clientRest.Send(new RemoveChannelPermissionsRequest(channel.Id, userOrRoleId)).ConfigureAwait(false);
|
||||
}
|
||||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
using Discord.API;
|
||||
using Discord.API.Client.Rest;
|
||||
using Discord.Net;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
internal sealed class Roles : AsyncCollection<ulong, Role>
|
||||
{
|
||||
public Roles(DiscordClient client, object writerLock)
|
||||
: base(client, writerLock) { }
|
||||
|
||||
public Role GetOrAdd(ulong id, ulong serverId)
|
||||
=> GetOrAdd(id, () => new Role(_client, id, serverId));
|
||||
}
|
||||
|
||||
public class RoleEventArgs : EventArgs
|
||||
{
|
||||
public Role Role { get; }
|
||||
public Server Server => Role.Server;
|
||||
|
||||
public RoleEventArgs(Role role) { Role = role; }
|
||||
}
|
||||
|
||||
public partial class DiscordClient
|
||||
{
|
||||
public event EventHandler<RoleEventArgs> RoleCreated;
|
||||
private void RaiseRoleCreated(Role role)
|
||||
{
|
||||
if (RoleCreated != null)
|
||||
EventHelper.Raise(_logger, nameof(RoleCreated), () => RoleCreated(this, new RoleEventArgs(role)));
|
||||
}
|
||||
public event EventHandler<RoleEventArgs> RoleUpdated;
|
||||
private void RaiseRoleDeleted(Role role)
|
||||
{
|
||||
if (RoleDeleted != null)
|
||||
EventHelper.Raise(_logger, nameof(RoleDeleted), () => RoleDeleted(this, new RoleEventArgs(role)));
|
||||
}
|
||||
public event EventHandler<RoleEventArgs> RoleDeleted;
|
||||
private void RaiseRoleUpdated(Role role)
|
||||
{
|
||||
if (RoleUpdated != null)
|
||||
EventHelper.Raise(_logger, nameof(RoleUpdated), () => RoleUpdated(this, new RoleEventArgs(role)));
|
||||
}
|
||||
|
||||
internal Roles Roles => _roles;
|
||||
private readonly Roles _roles;
|
||||
|
||||
/// <summary> Returns the role with the specified id, or null if none was found. </summary>
|
||||
public Role GetRole(ulong id)
|
||||
{
|
||||
CheckReady();
|
||||
|
||||
return _roles[id];
|
||||
}
|
||||
/// <summary> Returns all roles with the specified server and name. </summary>
|
||||
/// <remarks> Name formats supported: Name and @Name. Search is case-insensitive. </remarks>
|
||||
public IEnumerable<Role> FindRoles(Server server, string name)
|
||||
{
|
||||
if (server == null) throw new ArgumentNullException(nameof(server));
|
||||
if (name == null) throw new ArgumentNullException(nameof(name));
|
||||
CheckReady();
|
||||
|
||||
/*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 => x.Server.Id == server.Id &&
|
||||
string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase));
|
||||
//}
|
||||
}
|
||||
|
||||
/// <summary> Note: due to current API limitations, the created role cannot be returned. </summary>
|
||||
public async Task<Role> CreateRole(Server server, string name, ServerPermissions permissions = null, Color color = null, bool isHoisted = false)
|
||||
{
|
||||
if (server == null) throw new ArgumentNullException(nameof(server));
|
||||
if (name == null) throw new ArgumentNullException(nameof(name));
|
||||
CheckReady();
|
||||
|
||||
var request1 = new CreateRoleRequest(server.Id);
|
||||
var response1 = await _clientRest.Send(request1).ConfigureAwait(false);
|
||||
var role = _roles.GetOrAdd(response1.Id, server.Id);
|
||||
role.Update(response1);
|
||||
|
||||
var request2 = new UpdateRoleRequest(role.Server.Id, role.Id)
|
||||
{
|
||||
Name = name,
|
||||
Permissions = (permissions ?? role.Permissions).RawValue,
|
||||
Color = (color ?? Color.Default).RawValue,
|
||||
IsHoisted = isHoisted
|
||||
};
|
||||
var response2 = await _clientRest.Send(request2).ConfigureAwait(false);
|
||||
role.Update(response2);
|
||||
|
||||
return role;
|
||||
}
|
||||
|
||||
public async Task EditRole(Role role, string name = null, ServerPermissions permissions = null, Color color = null, bool? isHoisted = null, int? position = null)
|
||||
{
|
||||
if (role == null) throw new ArgumentNullException(nameof(role));
|
||||
CheckReady();
|
||||
|
||||
var request1 = new UpdateRoleRequest(role.Server.Id, role.Id)
|
||||
{
|
||||
Name = name ?? role.Name,
|
||||
Permissions = (permissions ?? role.Permissions).RawValue,
|
||||
Color = (color ?? role.Color).RawValue,
|
||||
IsHoisted = isHoisted ?? role.IsHoisted
|
||||
};
|
||||
|
||||
var response = await _clientRest.Send(request1).ConfigureAwait(false);
|
||||
|
||||
if (position != null)
|
||||
{
|
||||
int oldPos = role.Position;
|
||||
int newPos = position.Value;
|
||||
int minPos;
|
||||
Role[] roles = role.Server.Roles.OrderBy(x => x.Position).ToArray();
|
||||
|
||||
if (oldPos < newPos) //Moving Down
|
||||
{
|
||||
minPos = oldPos;
|
||||
for (int i = oldPos; i < newPos; i++)
|
||||
roles[i] = roles[i + 1];
|
||||
roles[newPos] = role;
|
||||
}
|
||||
else //(oldPos > newPos) Moving Up
|
||||
{
|
||||
minPos = newPos;
|
||||
for (int i = oldPos; i > newPos; i--)
|
||||
roles[i] = roles[i - 1];
|
||||
roles[newPos] = role;
|
||||
}
|
||||
|
||||
var request2 = new ReorderRolesRequest(role.Server.Id)
|
||||
{
|
||||
RoleIds = roles.Skip(minPos).Select(x => x.Id).ToArray(),
|
||||
StartPos = minPos
|
||||
};
|
||||
await _clientRest.Send(request2).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteRole(Role role)
|
||||
{
|
||||
if (role == null) throw new ArgumentNullException(nameof(role));
|
||||
CheckReady();
|
||||
|
||||
try { await _clientRest.Send(new DeleteRoleRequest(role.Server.Id, role.Id)).ConfigureAwait(false); }
|
||||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
|
||||
}
|
||||
|
||||
public Task ReorderRoles(Server server, IEnumerable<Role> roles, int startPos = 0)
|
||||
{
|
||||
if (server == null) throw new ArgumentNullException(nameof(server));
|
||||
if (roles == null) throw new ArgumentNullException(nameof(roles));
|
||||
if (startPos < 0) throw new ArgumentOutOfRangeException(nameof(startPos), "startPos must be a positive integer.");
|
||||
CheckReady();
|
||||
|
||||
return _clientRest.Send(new ReorderRolesRequest(server.Id)
|
||||
{
|
||||
RoleIds = roles.Select(x => x.Id).ToArray(),
|
||||
StartPos = startPos
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
using Discord.API.Client.Rest;
|
||||
using Discord.Net;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
internal sealed class Servers : AsyncCollection<ulong, Server>
|
||||
{
|
||||
public Servers(DiscordClient client, object writerLock)
|
||||
: base(client, writerLock) { }
|
||||
|
||||
public Server GetOrAdd(ulong id)
|
||||
=> GetOrAdd(id, () => new Server(_client, id));
|
||||
}
|
||||
|
||||
public class ServerEventArgs : EventArgs
|
||||
{
|
||||
public Server Server { get; }
|
||||
|
||||
public ServerEventArgs(Server server) { Server = server; }
|
||||
}
|
||||
|
||||
public partial class DiscordClient
|
||||
{
|
||||
public event EventHandler<ServerEventArgs> JoinedServer;
|
||||
private void RaiseJoinedServer(Server server)
|
||||
{
|
||||
if (JoinedServer != null)
|
||||
EventHelper.Raise(_logger, nameof(JoinedServer), () => JoinedServer(this, new ServerEventArgs(server)));
|
||||
}
|
||||
public event EventHandler<ServerEventArgs> LeftServer;
|
||||
private void RaiseLeftServer(Server server)
|
||||
{
|
||||
if (LeftServer != null)
|
||||
EventHelper.Raise(_logger, nameof(LeftServer), () => LeftServer(this, new ServerEventArgs(server)));
|
||||
}
|
||||
public event EventHandler<ServerEventArgs> ServerUpdated;
|
||||
private void RaiseServerUpdated(Server server)
|
||||
{
|
||||
if (ServerUpdated != null)
|
||||
EventHelper.Raise(_logger, nameof(ServerUpdated), () => ServerUpdated(this, new ServerEventArgs(server)));
|
||||
}
|
||||
public event EventHandler<ServerEventArgs> ServerUnavailable;
|
||||
private void RaiseServerUnavailable(Server server)
|
||||
{
|
||||
if (ServerUnavailable != null)
|
||||
EventHelper.Raise(_logger, nameof(ServerUnavailable), () => ServerUnavailable(this, new ServerEventArgs(server)));
|
||||
}
|
||||
public event EventHandler<ServerEventArgs> ServerAvailable;
|
||||
private void RaiseServerAvailable(Server server)
|
||||
{
|
||||
if (ServerAvailable != null)
|
||||
EventHelper.Raise(_logger, nameof(ServerAvailable), () => ServerAvailable(this, new ServerEventArgs(server)));
|
||||
}
|
||||
|
||||
/// <summary> Returns a collection of all servers this client is a member of. </summary>
|
||||
public IEnumerable<Server> AllServers { get { CheckReady(); return _servers; } }
|
||||
internal Servers Servers => _servers;
|
||||
private readonly Servers _servers;
|
||||
|
||||
/// <summary> Returns the server with the specified id, or null if none was found. </summary>
|
||||
public Server GetServer(ulong id)
|
||||
{
|
||||
CheckReady();
|
||||
|
||||
return _servers[id];
|
||||
}
|
||||
|
||||
/// <summary> Returns all servers with the specified name. </summary>
|
||||
/// <remarks> Search is case-insensitive. </remarks>
|
||||
public IEnumerable<Server> FindServers(string name)
|
||||
{
|
||||
if (name == null) throw new ArgumentNullException(nameof(name));
|
||||
CheckReady();
|
||||
|
||||
return _servers.Where(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
/// <summary> Creates a new server with the provided name and region (see Regions). </summary>
|
||||
public async Task<Server> CreateServer(string name, 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));
|
||||
CheckReady();
|
||||
|
||||
var request = new CreateGuildRequest()
|
||||
{
|
||||
Name = name,
|
||||
Region = region.Id,
|
||||
IconBase64 = Base64Image(iconType, icon, null)
|
||||
};
|
||||
var response = await _clientRest.Send(request).ConfigureAwait(false);
|
||||
|
||||
var server = _servers.GetOrAdd(response.Id);
|
||||
server.Update(response);
|
||||
return server;
|
||||
}
|
||||
|
||||
/// <summary> Edits the provided server, changing only non-null attributes. </summary>
|
||||
public async Task EditServer(Server server, string name = null, string region = null, Stream icon = null, ImageType iconType = ImageType.Png)
|
||||
{
|
||||
if (server == null) throw new ArgumentNullException(nameof(server));
|
||||
CheckReady();
|
||||
|
||||
var request = new UpdateGuildRequest(server.Id)
|
||||
{
|
||||
Name = name ?? server.Name,
|
||||
Region = region ?? server.Region,
|
||||
IconBase64 = Base64Image(iconType, icon, server.IconId),
|
||||
AFKChannelId = server.AFKChannel?.Id,
|
||||
AFKTimeout = server.AFKTimeout
|
||||
};
|
||||
var response = await _clientRest.Send(request).ConfigureAwait(false);
|
||||
server.Update(response);
|
||||
}
|
||||
|
||||
/// <summary> Leaves the provided server, destroying it if you are the owner. </summary>
|
||||
public async Task LeaveServer(Server server)
|
||||
{
|
||||
if (server == null) throw new ArgumentNullException(nameof(server));
|
||||
CheckReady();
|
||||
|
||||
try { await _clientRest.Send(new LeaveGuildRequest(server.Id)).ConfigureAwait(false); }
|
||||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Region>> GetVoiceRegions()
|
||||
{
|
||||
CheckReady();
|
||||
|
||||
var regions = await _clientRest.Send(new GetVoiceRegionsRequest()).ConfigureAwait(false);
|
||||
return regions.Select(x => new Region(x.Id, x.Name, x.Hostname, x.Port));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,329 +0,0 @@
|
||||
using Discord.API.Client.Rest;
|
||||
using Discord.Net;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
internal sealed class GlobalUsers : AsyncCollection<ulong, GlobalUser>
|
||||
{
|
||||
public GlobalUsers(DiscordClient client, object writerLock)
|
||||
: base(client, writerLock) { }
|
||||
|
||||
public GlobalUser GetOrAdd(ulong id) => GetOrAdd(id, () => new GlobalUser(_client, id));
|
||||
}
|
||||
internal sealed class Users : AsyncCollection<User.CompositeKey, User>
|
||||
{
|
||||
public Users(DiscordClient client, object writerLock)
|
||||
: base(client, writerLock)
|
||||
{ }
|
||||
|
||||
public User this[ulong userId, ulong? serverId]
|
||||
=> base[new User.CompositeKey(userId, serverId)];
|
||||
public User GetOrAdd(ulong userId, ulong? serverId)
|
||||
=> GetOrAdd(new User.CompositeKey(userId, serverId), () => new User(_client, userId, serverId));
|
||||
public User TryRemove(ulong userId, ulong? serverId)
|
||||
=> TryRemove(new User.CompositeKey(userId, serverId));
|
||||
}
|
||||
|
||||
public class UserEventArgs : EventArgs
|
||||
{
|
||||
public User User { get; }
|
||||
public Server Server => User.Server;
|
||||
|
||||
public UserEventArgs(User user) { User = user; }
|
||||
}
|
||||
public class UserChannelEventArgs : UserEventArgs
|
||||
{
|
||||
public Channel Channel { get; }
|
||||
|
||||
public UserChannelEventArgs(User user, Channel channel)
|
||||
: base(user)
|
||||
{
|
||||
Channel = channel;
|
||||
}
|
||||
}
|
||||
public class BanEventArgs : EventArgs
|
||||
{
|
||||
public ulong UserId { get; }
|
||||
public Server Server { get; }
|
||||
|
||||
public BanEventArgs(ulong userId, Server server)
|
||||
{
|
||||
UserId = userId;
|
||||
Server = server;
|
||||
}
|
||||
}
|
||||
|
||||
public partial class DiscordClient : IDisposable
|
||||
{
|
||||
public event EventHandler<UserEventArgs> UserJoined;
|
||||
private void RaiseUserJoined(User user)
|
||||
{
|
||||
if (UserJoined != null)
|
||||
EventHelper.Raise(_logger, nameof(UserJoined), () => UserJoined(this, new UserEventArgs(user)));
|
||||
}
|
||||
public event EventHandler<UserEventArgs> UserLeft;
|
||||
private void RaiseUserLeft(User user)
|
||||
{
|
||||
if (UserLeft != null)
|
||||
EventHelper.Raise(_logger, nameof(UserLeft), () => UserLeft(this, new UserEventArgs(user)));
|
||||
}
|
||||
public event EventHandler<UserEventArgs> UserUpdated;
|
||||
private void RaiseUserUpdated(User user)
|
||||
{
|
||||
if (UserUpdated != null)
|
||||
EventHelper.Raise(_logger, nameof(UserUpdated), () => UserUpdated(this, new UserEventArgs(user)));
|
||||
}
|
||||
public event EventHandler<UserEventArgs> UserPresenceUpdated;
|
||||
private void RaiseUserPresenceUpdated(User user)
|
||||
{
|
||||
if (UserPresenceUpdated != null)
|
||||
EventHelper.Raise(_logger, nameof(UserPresenceUpdated), () => UserPresenceUpdated(this, new UserEventArgs(user)));
|
||||
}
|
||||
public event EventHandler<UserEventArgs> UserVoiceStateUpdated;
|
||||
private void RaiseUserVoiceStateUpdated(User user)
|
||||
{
|
||||
if (UserVoiceStateUpdated != null)
|
||||
EventHelper.Raise(_logger, nameof(UserVoiceStateUpdated), () => UserVoiceStateUpdated(this, new UserEventArgs(user)));
|
||||
}
|
||||
public event EventHandler<UserChannelEventArgs> UserIsTypingUpdated;
|
||||
private void RaiseUserIsTyping(User user, Channel channel)
|
||||
{
|
||||
if (UserIsTypingUpdated != null)
|
||||
EventHelper.Raise(_logger, nameof(UserIsTypingUpdated), () => UserIsTypingUpdated(this, new UserChannelEventArgs(user, channel)));
|
||||
}
|
||||
public event EventHandler ProfileUpdated;
|
||||
private void RaiseProfileUpdated()
|
||||
{
|
||||
if (ProfileUpdated != null)
|
||||
EventHelper.Raise(_logger, nameof(ProfileUpdated), () => ProfileUpdated(this, EventArgs.Empty));
|
||||
}
|
||||
public event EventHandler<BanEventArgs> UserBanned;
|
||||
private void RaiseUserBanned(ulong userId, Server server)
|
||||
{
|
||||
if (UserBanned != null)
|
||||
EventHelper.Raise(_logger, nameof(UserBanned), () => UserBanned(this, new BanEventArgs(userId, server)));
|
||||
}
|
||||
public event EventHandler<BanEventArgs> UserUnbanned;
|
||||
private void RaiseUserUnbanned(ulong userId, Server server)
|
||||
{
|
||||
if (UserUnbanned != null)
|
||||
EventHelper.Raise(_logger, nameof(UserUnbanned), () => UserUnbanned(this, new BanEventArgs(userId, server)));
|
||||
}
|
||||
|
||||
/// <summary> Returns the current logged-in user used in private channels. </summary>
|
||||
internal User PrivateUser => _privateUser;
|
||||
private User _privateUser;
|
||||
|
||||
/// <summary> Returns information about the currently logged-in account. </summary>
|
||||
public GlobalUser CurrentUser => _currentUser;
|
||||
private GlobalUser _currentUser;
|
||||
|
||||
/// <summary> Returns a collection of all unique users this client can currently see. </summary>
|
||||
public IEnumerable<GlobalUser> AllUsers { get { CheckReady(); return _globalUsers; } }
|
||||
internal GlobalUsers GlobalUsers => _globalUsers;
|
||||
private readonly GlobalUsers _globalUsers;
|
||||
|
||||
internal Users Users => _users;
|
||||
private readonly Users _users;
|
||||
|
||||
public GlobalUser GetUser(ulong userId)
|
||||
{
|
||||
CheckReady();
|
||||
|
||||
return _globalUsers[userId];
|
||||
}
|
||||
/// <summary> Returns the user with the specified id, along with their server-specific data, or null if none was found. </summary>
|
||||
public User GetUser(Server server, ulong userId)
|
||||
{
|
||||
if (server == null) throw new ArgumentNullException(nameof(server));
|
||||
CheckReady();
|
||||
|
||||
return _users[userId, server.Id];
|
||||
}
|
||||
/// <summary> Returns the user with the specified name and discriminator, along withtheir server-specific data, or null if they couldn't be found. </summary>
|
||||
public User GetUser(Server server, string username, ushort discriminator)
|
||||
{
|
||||
if (server == null) throw new ArgumentNullException(nameof(server));
|
||||
if (username == null) throw new ArgumentNullException(nameof(username));
|
||||
CheckReady();
|
||||
|
||||
return FindUsers(server.Members, server.Id, username, discriminator, true).FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary> Returns all users with the specified server and name, along with their server-specific data. </summary>
|
||||
/// <remarks> Name formats supported: Name, @Name and <@Id>. Search is case-insensitive if exactMatch is false.</remarks>
|
||||
public IEnumerable<User> FindUsers(Server server, string name, bool exactMatch = false)
|
||||
{
|
||||
if (server == null) throw new ArgumentNullException(nameof(server));
|
||||
if (name == null) throw new ArgumentNullException(nameof(name));
|
||||
CheckReady();
|
||||
|
||||
return FindUsers(server.Members, server.Id, name, exactMatch: exactMatch);
|
||||
}
|
||||
/// <summary> Returns all users with the specified channel and name, along with their server-specific data. </summary>
|
||||
/// <remarks> Name formats supported: Name, @Name and <@Id>. Search is case-insensitive if exactMatch is false.</remarks>
|
||||
public IEnumerable<User> FindUsers(Channel channel, string name, bool exactMatch = false)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
if (name == null) throw new ArgumentNullException(nameof(name));
|
||||
CheckReady();
|
||||
|
||||
return FindUsers(channel.Members, channel.IsPrivate ? (ulong?)null : channel.Server.Id, name, exactMatch: exactMatch);
|
||||
}
|
||||
|
||||
private IEnumerable<User> FindUsers(IEnumerable<User> users, ulong? serverId, string name, ushort? discriminator = null, bool exactMatch = false)
|
||||
{
|
||||
var query = users.Where(x => string.Equals(x.Name, name, exactMatch ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (!exactMatch && name.Length >= 2)
|
||||
{
|
||||
if (name[0] == '<' && name[1] == '@' && name[name.Length - 1] == '>') //Parse mention
|
||||
{
|
||||
ulong id = IdConvert.ToLong(name.Substring(2, name.Length - 3));
|
||||
var user = _users[id, serverId];
|
||||
if (user != null)
|
||||
query = query.Concat(new User[] { user });
|
||||
}
|
||||
else if (name[0] == '@') //If we somehow get text starting with @ but isn't a 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 Task EditUser(User user, bool? isMuted = null, bool? isDeafened = null, Channel voiceChannel = null, IEnumerable<Role> roles = null)
|
||||
{
|
||||
if (user == null) throw new ArgumentNullException(nameof(user));
|
||||
if (user.IsPrivate) throw new InvalidOperationException("Unable to edit users in a private channel");
|
||||
CheckReady();
|
||||
|
||||
//Modify the roles collection and filter out the everyone role
|
||||
var roleIds = roles == null ? null : user.Roles.Where(x => !x.IsEveryone) .Select(x => x.Id);
|
||||
|
||||
var request = new UpdateMemberRequest(user.Server.Id, user.Id)
|
||||
{
|
||||
IsMuted = isMuted ?? user.IsServerMuted,
|
||||
IsDeafened = isDeafened ?? user.IsServerDeafened,
|
||||
VoiceChannelId = voiceChannel?.Id,
|
||||
RoleIds = roleIds.ToArray()
|
||||
};
|
||||
return _clientRest.Send(request);
|
||||
}
|
||||
|
||||
public Task KickUser(User user)
|
||||
{
|
||||
if (user == null) throw new ArgumentNullException(nameof(user));
|
||||
if (user.IsPrivate) throw new InvalidOperationException("Unable to kick users from a private channel");
|
||||
CheckReady();
|
||||
|
||||
var request = new KickMemberRequest(user.Server.Id, user.Id);
|
||||
return _clientRest.Send(request);
|
||||
}
|
||||
public Task BanUser(User user, int pruneDays = 0)
|
||||
{
|
||||
if (user == null) throw new ArgumentNullException(nameof(user));
|
||||
if (user.IsPrivate) throw new InvalidOperationException("Unable to ban users from a private channel");
|
||||
CheckReady();
|
||||
|
||||
var request = new AddGuildBanRequest(user.Server.Id, user.Id);
|
||||
request.PruneDays = pruneDays;
|
||||
return _clientRest.Send(request);
|
||||
}
|
||||
public async Task UnbanUser(Server server, ulong userId)
|
||||
{
|
||||
if (server == null) throw new ArgumentNullException(nameof(server));
|
||||
if (userId <= 0) throw new ArgumentOutOfRangeException(nameof(userId));
|
||||
CheckReady();
|
||||
|
||||
try { await _clientRest.Send(new RemoveGuildBanRequest(server.Id, userId)).ConfigureAwait(false); }
|
||||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
|
||||
}
|
||||
|
||||
public async Task<int> PruneUsers(Server server, int days, bool simulate = false)
|
||||
{
|
||||
if (server == null) throw new ArgumentNullException(nameof(server));
|
||||
if (days <= 0) throw new ArgumentOutOfRangeException(nameof(days));
|
||||
CheckReady();
|
||||
|
||||
var request = new PruneMembersRequest(server.Id)
|
||||
{
|
||||
Days = days,
|
||||
IsSimulation = simulate
|
||||
};
|
||||
var response = await _clientRest.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 a particular server.</summary>
|
||||
public void RequestOfflineUsers(Server server)
|
||||
{
|
||||
if (server == null) throw new ArgumentNullException(nameof(server));
|
||||
|
||||
_webSocket.SendRequestMembers(server.Id, "", 0);
|
||||
}
|
||||
|
||||
public async Task EditProfile(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));
|
||||
CheckReady();
|
||||
|
||||
var request = new UpdateProfileRequest()
|
||||
{
|
||||
CurrentPassword = currentPassword,
|
||||
Email = email ?? _currentUser?.Email,
|
||||
Password = password,
|
||||
Username = username ?? _privateUser?.Name,
|
||||
AvatarBase64 = Base64Image(avatarType, avatar, _privateUser?.AvatarId)
|
||||
};
|
||||
|
||||
await _clientRest.Send(request).ConfigureAwait(false);
|
||||
|
||||
if (password != null)
|
||||
{
|
||||
var loginRequest = new LoginRequest()
|
||||
{
|
||||
Email = _currentUser.Email,
|
||||
Password = password
|
||||
};
|
||||
var loginResponse = await _clientRest.Send(loginRequest).ConfigureAwait(false);
|
||||
_clientRest.SetToken(loginResponse.Token);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
_status = status;
|
||||
return SendStatus();
|
||||
}
|
||||
public Task SetGame(int? gameId)
|
||||
{
|
||||
CheckReady();
|
||||
|
||||
_gameId = gameId;
|
||||
return SendStatus();
|
||||
}
|
||||
private Task SendStatus()
|
||||
{
|
||||
_webSocket.SendUpdateStatus(_status == UserStatus.Idle ? EpochTime.GetMilliseconds() - (10 * 60 * 1000) : (long?)null, _gameId);
|
||||
return TaskHelper.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,8 +14,8 @@ namespace Discord
|
||||
Debug = 5
|
||||
}
|
||||
|
||||
public abstract class BaseConfig<T>
|
||||
where T : BaseConfig<T>
|
||||
public abstract class Config<T>
|
||||
where T : Config<T>
|
||||
{
|
||||
protected bool _isLocked;
|
||||
protected internal void Lock() { _isLocked = true; }
|
||||
@@ -34,20 +34,22 @@ namespace Discord
|
||||
}
|
||||
}
|
||||
|
||||
public class DiscordConfig : BaseConfig<DiscordConfig>
|
||||
{
|
||||
public static string LibName => "Discord.Net";
|
||||
public static string LibVersion => typeof(DiscordClient).GetTypeInfo().Assembly.GetName().Version.ToString(3);
|
||||
public static string LibUrl => "https://github.com/RogueException/Discord.Net";
|
||||
public class DiscordConfig : Config<DiscordConfig>
|
||||
{
|
||||
public const int MaxMessageSize = 2000;
|
||||
|
||||
public static string ClientAPIUrl => "https://discordapp.com/api/";
|
||||
public static string StatusAPIUrl => "https://status.discordapp.com/api/v2/";
|
||||
public static string CDNUrl => "https://cdn.discordapp.com/";
|
||||
public static string InviteUrl => "https://discord.gg/";
|
||||
public const string LibName = "Discord.Net";
|
||||
public static string LibVersion => typeof(DiscordClient).GetTypeInfo().Assembly.GetName().Version.ToString(3);
|
||||
public const string LibUrl = "https://github.com/RogueException/Discord.Net";
|
||||
|
||||
public const string ClientAPIUrl = "https://discordapp.com/api/";
|
||||
public const string StatusAPIUrl = "https://status.discordapp.com/api/v2/";
|
||||
public const string CDNUrl = "https://cdn.discordapp.com/";
|
||||
public const string InviteUrl = "https://discord.gg/";
|
||||
|
||||
//Global
|
||||
|
||||
/// <summary> Name of your application. </summary>
|
||||
/// <summary> Name of your application. This is used both for the token cache directory and user agent. </summary>
|
||||
public string AppName { get { return _appName; } set { SetValue(ref _appName, value); UpdateUserAgent(); } }
|
||||
private string _appName = null;
|
||||
/// <summary> Version of your application. </summary>
|
||||
|
||||
10
src/Discord.Net/Enums/ConnectionState.cs
Normal file
10
src/Discord.Net/Enums/ConnectionState.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Discord
|
||||
{
|
||||
public enum ConnectionState : byte
|
||||
{
|
||||
Disconnected,
|
||||
Connecting,
|
||||
Connected,
|
||||
Disconnecting
|
||||
}
|
||||
}
|
||||
12
src/Discord.Net/Events/ChannelEventArgs.cs
Normal file
12
src/Discord.Net/Events/ChannelEventArgs.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public class ChannelEventArgs : EventArgs
|
||||
{
|
||||
public Channel Channel { get; }
|
||||
public Server Server => Channel.Server;
|
||||
|
||||
public ChannelEventArgs(Channel channel) { Channel = channel; }
|
||||
}
|
||||
}
|
||||
14
src/Discord.Net/Events/ChannelUserEventArgs.cs
Normal file
14
src/Discord.Net/Events/ChannelUserEventArgs.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace Discord
|
||||
{
|
||||
public class ChannelUserEventArgs
|
||||
{
|
||||
public Channel Channel { get; }
|
||||
public User User { get; }
|
||||
|
||||
public ChannelUserEventArgs(Channel channel, User user)
|
||||
{
|
||||
Channel = channel;
|
||||
User = user;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/Discord.Net/Events/DisconnectedEventArgs.cs
Normal file
16
src/Discord.Net/Events/DisconnectedEventArgs.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public class DisconnectedEventArgs : EventArgs
|
||||
{
|
||||
public bool WasUnexpected { get; }
|
||||
public Exception Exception { get; }
|
||||
|
||||
public DisconnectedEventArgs(bool wasUnexpected, Exception ex)
|
||||
{
|
||||
WasUnexpected = wasUnexpected;
|
||||
Exception = ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
src/Discord.Net/Events/LogMessageEventArgs.cs
Normal file
20
src/Discord.Net/Events/LogMessageEventArgs.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public sealed class LogMessageEventArgs : EventArgs
|
||||
{
|
||||
public LogSeverity Severity { get; }
|
||||
public string Source { get; }
|
||||
public string Message { get; }
|
||||
public Exception Exception { get; }
|
||||
|
||||
public LogMessageEventArgs(LogSeverity severity, string source, string msg, Exception exception)
|
||||
{
|
||||
Severity = severity;
|
||||
Source = source;
|
||||
Message = msg;
|
||||
Exception = exception;
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/Discord.Net/Events/MessageEventArgs.cs
Normal file
14
src/Discord.Net/Events/MessageEventArgs.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public class MessageEventArgs : EventArgs
|
||||
{
|
||||
public Message Message { get; }
|
||||
public User User => Message.User;
|
||||
public Channel Channel => Message.Channel;
|
||||
public Server Server => Message.Server;
|
||||
|
||||
public MessageEventArgs(Message msg) { Message = msg; }
|
||||
}
|
||||
}
|
||||
14
src/Discord.Net/Events/ProfileEventArgs.cs
Normal file
14
src/Discord.Net/Events/ProfileEventArgs.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public class ProfileEventArgs : EventArgs
|
||||
{
|
||||
public Profile Profile { get; }
|
||||
|
||||
public ProfileEventArgs(Profile profile)
|
||||
{
|
||||
Profile = profile;
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/Discord.Net/Events/RoleEventArgs.cs
Normal file
12
src/Discord.Net/Events/RoleEventArgs.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public class RoleEventArgs : EventArgs
|
||||
{
|
||||
public Role Role { get; }
|
||||
public Server Server => Role.Server;
|
||||
|
||||
public RoleEventArgs(Role role) { Role = role; }
|
||||
}
|
||||
}
|
||||
11
src/Discord.Net/Events/ServerEventArgs.cs
Normal file
11
src/Discord.Net/Events/ServerEventArgs.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public class ServerEventArgs : EventArgs
|
||||
{
|
||||
public Server Server { get; }
|
||||
|
||||
public ServerEventArgs(Server server) { Server = server; }
|
||||
}
|
||||
}
|
||||
11
src/Discord.Net/Events/UserEventArgs.cs
Normal file
11
src/Discord.Net/Events/UserEventArgs.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
namespace Discord
|
||||
{
|
||||
public class UserEventArgs : EventArgs
|
||||
{
|
||||
public User User { get; }
|
||||
public Server Server => User.Server;
|
||||
|
||||
public UserEventArgs(User user) { User = user; }
|
||||
}
|
||||
}
|
||||
@@ -10,11 +10,11 @@ namespace Discord
|
||||
static Format()
|
||||
{
|
||||
_patterns = new string[] { "__", "_", "**", "*", "~~", "```", "`"};
|
||||
_builder = new StringBuilder(DiscordClient.MaxMessageSize);
|
||||
_builder = new StringBuilder(DiscordConfig.MaxMessageSize);
|
||||
}
|
||||
|
||||
/// <summary> Removes all special formatting characters from the provided text. </summary>
|
||||
private static string Escape(string text)
|
||||
public static string Escape(string text)
|
||||
{
|
||||
lock (_builder)
|
||||
{
|
||||
@@ -84,10 +84,7 @@ namespace Discord
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary> Returns a markdown-formatted string with no formatting, optionally escaping the contents. </summary>
|
||||
public static string Normal(string text, bool escape = true)
|
||||
=> escape ? Escape(text) : text;
|
||||
|
||||
/// <summary> Returns a markdown-formatted string with bold formatting, optionally escaping the contents. </summary>
|
||||
public static string Bold(string text, bool escape = true)
|
||||
=> escape ? $"**{Escape(text)}**" : $"**{text}**";
|
||||
@@ -109,20 +106,5 @@ namespace Discord
|
||||
else
|
||||
return $"`{text}`";
|
||||
}
|
||||
|
||||
/// <summary> Returns a markdown-formatted string with multiple formatting, optionally escaping the contents. </summary>
|
||||
public static string Multiple(string text, bool escape = true,
|
||||
bool bold = false, bool italics = false, bool underline = false, bool strikeout = false,
|
||||
bool code = false, string codeLanguage = null)
|
||||
{
|
||||
string result = text;
|
||||
if (escape) result = Escape(result);
|
||||
if (bold) result = Bold(result, false);
|
||||
if (italics) result = Italics(result, false);
|
||||
if (underline) result = Underline(result, false);
|
||||
if (strikeout) result = Strikeout(result, false);
|
||||
if (code) result = Code(result, codeLanguage);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
internal abstract class AsyncCollection<TKey, TValue> : IEnumerable<TValue>
|
||||
where TKey : struct, IEquatable<TKey>
|
||||
where TValue : CachedObject
|
||||
{
|
||||
private readonly object _writerLock;
|
||||
|
||||
public class CollectionItemEventArgs : EventArgs
|
||||
{
|
||||
public TValue Item { get; }
|
||||
public CollectionItemEventArgs(TValue item) { Item = item; }
|
||||
}
|
||||
public class CollectionItemRemappedEventArgs : EventArgs
|
||||
{
|
||||
public TValue Item { get; }
|
||||
public TKey OldId { get; }
|
||||
public TKey NewId { get; }
|
||||
public CollectionItemRemappedEventArgs(TValue item, TKey oldId, TKey newId) { Item = item; OldId = oldId; NewId = newId; }
|
||||
}
|
||||
|
||||
public EventHandler<CollectionItemEventArgs> ItemCreated;
|
||||
private void RaiseItemCreated(TValue item)
|
||||
{
|
||||
if (ItemCreated != null)
|
||||
ItemCreated(this, new CollectionItemEventArgs(item));
|
||||
}
|
||||
public EventHandler<CollectionItemEventArgs> ItemDestroyed;
|
||||
private void RaiseItemDestroyed(TValue item)
|
||||
{
|
||||
if (ItemDestroyed != null)
|
||||
ItemDestroyed(this, new CollectionItemEventArgs(item));
|
||||
}
|
||||
public EventHandler<CollectionItemRemappedEventArgs> ItemRemapped;
|
||||
private void RaiseItemRemapped(TValue item, TKey oldId, TKey newId)
|
||||
{
|
||||
if (ItemRemapped != null)
|
||||
ItemRemapped(this, new CollectionItemRemappedEventArgs(item, oldId, newId));
|
||||
}
|
||||
|
||||
public EventHandler Cleared;
|
||||
private void RaiseCleared()
|
||||
{
|
||||
if (Cleared != null)
|
||||
Cleared(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
protected readonly DiscordClient _client;
|
||||
protected readonly ConcurrentDictionary<TKey, TValue> _dictionary;
|
||||
|
||||
public int Count => _dictionary.Count;
|
||||
|
||||
protected AsyncCollection(DiscordClient client, object writerLock)
|
||||
{
|
||||
_client = client;
|
||||
_writerLock = writerLock;
|
||||
_dictionary = new ConcurrentDictionary<TKey, TValue>();
|
||||
}
|
||||
|
||||
public TValue this[TKey? key]
|
||||
=> key == null ? null : this[key.Value];
|
||||
public TValue this[TKey key]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (key.Equals(default(TKey)))
|
||||
return null;
|
||||
|
||||
TValue result;
|
||||
if (!_dictionary.TryGetValue(key, out result))
|
||||
return null;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
protected TValue GetOrAdd(TKey key, Func<TValue> createFunc)
|
||||
{
|
||||
TValue result;
|
||||
if (_dictionary.TryGetValue(key, out result))
|
||||
return result;
|
||||
|
||||
lock (_writerLock)
|
||||
{
|
||||
if (!_dictionary.ContainsKey(key))
|
||||
{
|
||||
result = createFunc();
|
||||
if (result.Cache())
|
||||
{
|
||||
_dictionary.TryAdd(key, result);
|
||||
RaiseItemCreated(result);
|
||||
}
|
||||
else
|
||||
result.Uncache();
|
||||
return result;
|
||||
}
|
||||
else
|
||||
return _dictionary[key];
|
||||
}
|
||||
}
|
||||
protected void Import(IEnumerable<KeyValuePair<TKey, TValue>> items)
|
||||
{
|
||||
lock (_writerLock)
|
||||
{
|
||||
foreach (var pair in items)
|
||||
{
|
||||
var value = pair.Value;
|
||||
if (value.Cache())
|
||||
{
|
||||
_dictionary.TryAdd(pair.Key, value);
|
||||
RaiseItemCreated(value);
|
||||
}
|
||||
else
|
||||
value.Uncache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TValue TryRemove(TKey key)
|
||||
{
|
||||
if (_dictionary.ContainsKey(key))
|
||||
{
|
||||
lock (_writerLock)
|
||||
{
|
||||
TValue result;
|
||||
if (_dictionary.TryRemove(key, out result))
|
||||
{
|
||||
result.Uncache(); //TODO: If this object is accessed before OnRemoved finished firing, properties such as Server.Channels will have null elements
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public void Clear()
|
||||
{
|
||||
lock (_writerLock)
|
||||
{
|
||||
_dictionary.Clear();
|
||||
RaiseCleared();
|
||||
}
|
||||
}
|
||||
|
||||
public TValue Remap(TKey oldKey, TKey newKey)
|
||||
{
|
||||
if (_dictionary.ContainsKey(oldKey))
|
||||
{
|
||||
lock (_writerLock)
|
||||
{
|
||||
TValue result;
|
||||
if (_dictionary.TryRemove(oldKey, out result))
|
||||
_dictionary[newKey] = result;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public IEnumerator<TValue> GetEnumerator() => _dictionary.Select(x => x.Value).GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
namespace Discord
|
||||
{
|
||||
internal static class BitHelper
|
||||
{
|
||||
public static bool GetBit(uint value, int pos) => ((value >> (byte)pos) & 1U) == 1;
|
||||
public static void SetBit(ref uint value, int pos, bool bitValue)
|
||||
{
|
||||
if (bitValue)
|
||||
value |= (1U << pos);
|
||||
else
|
||||
value &= ~(1U << pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public abstract class CachedObject<TKey> : CachedObject
|
||||
{
|
||||
private TKey _id;
|
||||
|
||||
internal CachedObject(DiscordClient client, TKey id)
|
||||
: base(client)
|
||||
{
|
||||
_id = id;
|
||||
}
|
||||
|
||||
/// <summary> Returns the unique identifier for this object. </summary>
|
||||
public TKey Id { get { return _id; } internal set { _id = value; } }
|
||||
|
||||
public override string ToString() => $"{this.GetType().Name} {Id}";
|
||||
}
|
||||
|
||||
public abstract class CachedObject
|
||||
{
|
||||
protected readonly DiscordClient _client;
|
||||
private bool _isCached;
|
||||
|
||||
internal DiscordClient Client => _client;
|
||||
internal bool IsCached => _isCached;
|
||||
|
||||
internal CachedObject(DiscordClient client)
|
||||
{
|
||||
_client = client;
|
||||
}
|
||||
|
||||
internal bool Cache()
|
||||
{
|
||||
if (LoadReferences())
|
||||
{
|
||||
_isCached = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
internal void Uncache()
|
||||
{
|
||||
if (_isCached)
|
||||
{
|
||||
UnloadReferences();
|
||||
_isCached = false;
|
||||
}
|
||||
}
|
||||
internal abstract bool LoadReferences();
|
||||
internal abstract void UnloadReferences();
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public enum EditMode : byte
|
||||
{
|
||||
Set,
|
||||
Add,
|
||||
Remove
|
||||
}
|
||||
|
||||
internal static class Extensions
|
||||
{
|
||||
public static IEnumerable<T> Modify<T>(this IEnumerable<T> original, IEnumerable<T> modified, EditMode mode)
|
||||
{
|
||||
if (original == null) return null;
|
||||
switch (mode)
|
||||
{
|
||||
case EditMode.Set:
|
||||
default:
|
||||
return modified;
|
||||
case EditMode.Add:
|
||||
return original.Concat(modified);
|
||||
case EditMode.Remove:
|
||||
return original.Except(modified);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
internal class Reference<T>
|
||||
where T : CachedObject<ulong>
|
||||
{
|
||||
private Action<T> _onCache, _onUncache;
|
||||
private Func<ulong, T> _getItem;
|
||||
private ulong? _id;
|
||||
public ulong? Id
|
||||
{
|
||||
get { return _id; }
|
||||
set
|
||||
{
|
||||
_id = value;
|
||||
_value = null;
|
||||
}
|
||||
}
|
||||
|
||||
private T _value;
|
||||
public T Value
|
||||
{
|
||||
get
|
||||
{
|
||||
var v = _value; //A little trickery to make this threadsafe
|
||||
var id = _id;
|
||||
if (v != null && !_value.IsCached)
|
||||
{
|
||||
v = null;
|
||||
_value = null;
|
||||
}
|
||||
if (v == null && id != null)
|
||||
{
|
||||
v = _getItem(id.Value);
|
||||
if (v != null && _onCache != null)
|
||||
_onCache(v);
|
||||
_value = v;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Load()
|
||||
{
|
||||
return Value != null; //Used for precaching
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
if (_onUncache != null)
|
||||
{
|
||||
var v = _value;
|
||||
if (v != null && _onUncache != null)
|
||||
_onUncache(v);
|
||||
}
|
||||
}
|
||||
|
||||
public Reference(Func<ulong, T> onUpdate, Action<T> onCache = null, Action<T> onUncache = null)
|
||||
: this(null, onUpdate, onCache, onUncache) { }
|
||||
public Reference(ulong? id, Func<ulong, T> getItem, Action<T> onCache = null, Action<T> onUncache = null)
|
||||
{
|
||||
_id = id;
|
||||
_getItem = getItem;
|
||||
_onCache = onCache;
|
||||
_onUncache = onUncache;
|
||||
_value = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
59
src/Discord.Net/Logging/LogManager.cs
Normal file
59
src/Discord.Net/Logging/LogManager.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
|
||||
namespace Discord.Logging
|
||||
{
|
||||
public class LogManager
|
||||
{
|
||||
private readonly DiscordClient _client;
|
||||
|
||||
public LogSeverity Level { get; }
|
||||
|
||||
public event EventHandler<LogMessageEventArgs> Message = delegate { };
|
||||
|
||||
internal LogManager(DiscordClient client)
|
||||
{
|
||||
_client = client;
|
||||
Level = client.Config.LogLevel;
|
||||
}
|
||||
|
||||
public void Log(LogSeverity severity, string source, FormattableString message, Exception exception = null)
|
||||
{
|
||||
if (severity <= Level)
|
||||
{
|
||||
try { Message(this, new LogMessageEventArgs(severity, source, message.ToString(), exception)); }
|
||||
catch { } //We dont want to log on log errors
|
||||
}
|
||||
}
|
||||
public void Log(LogSeverity severity, string source, string message, Exception exception = null)
|
||||
{
|
||||
if (severity <= Level)
|
||||
{
|
||||
try { Message(this, new LogMessageEventArgs(severity, source, message, exception)); }
|
||||
catch { } //We dont want to log on log errors
|
||||
}
|
||||
}
|
||||
|
||||
public void Error(string source, string message, Exception ex = null)
|
||||
=> Log(LogSeverity.Error, source, message, ex);
|
||||
public void Error(string source, FormattableString message, Exception ex = null)
|
||||
=> Log(LogSeverity.Error, source, message, ex);
|
||||
public void Warning(string source, string message, Exception ex = null)
|
||||
=> Log(LogSeverity.Warning, source, message, ex);
|
||||
public void Warning(string source, FormattableString message, Exception ex = null)
|
||||
=> Log(LogSeverity.Warning, source, message, ex);
|
||||
public void Info(string source, string message, Exception ex = null)
|
||||
=> Log(LogSeverity.Info, source, message, ex);
|
||||
public void Info(string source, FormattableString message, Exception ex = null)
|
||||
=> Log(LogSeverity.Info, source, message, ex);
|
||||
public void Verbose(string source, string message, Exception ex = null)
|
||||
=> Log(LogSeverity.Verbose, source, message, ex);
|
||||
public void Verbose(string source, FormattableString message, Exception ex = null)
|
||||
=> Log(LogSeverity.Verbose, source, message, ex);
|
||||
public void Debug(string source, string message, Exception ex = null)
|
||||
=> Log(LogSeverity.Debug, source, message, ex);
|
||||
public void Debug(string source, FormattableString message, Exception ex = null)
|
||||
=> Log(LogSeverity.Debug, source, message, ex);
|
||||
|
||||
public Logger CreateLogger(string name) => new Logger(this, name);
|
||||
}
|
||||
}
|
||||
45
src/Discord.Net/Logging/Logger.cs
Normal file
45
src/Discord.Net/Logging/Logger.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using Discord.Net.WebSockets;
|
||||
using System;
|
||||
|
||||
namespace Discord.Logging
|
||||
{
|
||||
public class Logger
|
||||
{
|
||||
private readonly LogManager _manager;
|
||||
|
||||
public string Name { get; }
|
||||
public LogSeverity Level => _manager.Level;
|
||||
|
||||
internal Logger(LogManager manager, string name)
|
||||
{
|
||||
_manager = manager;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public void Log(LogSeverity severity, string message, Exception exception = null)
|
||||
=> _manager.Log(severity, Name, message, exception);
|
||||
public void Log(LogSeverity severity, FormattableString message, Exception exception = null)
|
||||
=> _manager.Log(severity, Name, message, exception);
|
||||
|
||||
public void Error(string message, Exception exception = null)
|
||||
=> _manager.Error(Name, message, exception);
|
||||
public void Error(FormattableString message, Exception exception = null)
|
||||
=> _manager.Error(Name, message, exception);
|
||||
public void Warning(string message, Exception exception = null)
|
||||
=> _manager.Warning(Name, message, exception);
|
||||
public void Warning(FormattableString message, Exception exception = null)
|
||||
=> _manager.Warning(Name, message, exception);
|
||||
public void Info(string message, Exception exception = null)
|
||||
=> _manager.Info(Name, message, exception);
|
||||
public void Info(FormattableString message, Exception exception = null)
|
||||
=> _manager.Info(Name, message, exception);
|
||||
public void Verbose(string message, Exception exception = null)
|
||||
=> _manager.Verbose(Name, message, exception);
|
||||
public void Verbose(FormattableString message, Exception exception = null)
|
||||
=> _manager.Verbose(Name, message, exception);
|
||||
public void Debug(string message, Exception exception = null)
|
||||
=> _manager.Debug(Name, message, exception);
|
||||
public void Debug(FormattableString message, Exception exception = null)
|
||||
=> _manager.Debug(Name, message, exception);
|
||||
}
|
||||
}
|
||||
@@ -14,10 +14,6 @@ namespace Discord
|
||||
[Obsolete("Use User.Mention instead")]
|
||||
public static string User(User user)
|
||||
=> $"<@{user.Id}>";
|
||||
/// <summary> Returns the string used to create a user mention. </summary>
|
||||
[Obsolete("Use GlobalUser.Mention instead")]
|
||||
public static string User(GlobalUser 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)
|
||||
@@ -27,12 +23,12 @@ namespace Discord
|
||||
public static string Everyone()
|
||||
=> $"@everyone";
|
||||
|
||||
internal static string CleanUserMentions(DiscordClient client, Server server, string text, List<User> users = null)
|
||||
internal static string CleanUserMentions(DiscordClient client, Channel channel, string text, List<User> users = null)
|
||||
{
|
||||
return _userRegex.Replace(text, new MatchEvaluator(e =>
|
||||
{
|
||||
var id = IdConvert.ToLong(e.Value.Substring(2, e.Value.Length - 3));
|
||||
var user = client.Users[id, server?.Id];
|
||||
var id = e.Value.Substring(2, e.Value.Length - 3).ToId();
|
||||
var user = channel.GetUser(id);
|
||||
if (user != null)
|
||||
{
|
||||
if (users != null)
|
||||
@@ -43,54 +39,57 @@ namespace Discord
|
||||
return '@' + e.Value;
|
||||
}));
|
||||
}
|
||||
internal static string CleanChannelMentions(DiscordClient client, Server server, string text, List<Channel> channels = null)
|
||||
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 = IdConvert.ToLong(e.Value.Substring(2, e.Value.Length - 3));
|
||||
var channel = client.Channels[id];
|
||||
if (channel != null && channel.Server.Id == server.Id)
|
||||
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(channel);
|
||||
return '#' + channel.Name;
|
||||
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)
|
||||
/*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(channel.Server.EveryoneRole);
|
||||
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)
|
||||
/// <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.Server, 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(Server server, string text)
|
||||
public static string Resolve(Channel channel, string text)
|
||||
{
|
||||
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||
|
||||
var client = server?.Client;
|
||||
text = CleanUserMentions(client, server, text);
|
||||
if (server != null)
|
||||
{
|
||||
text = CleanChannelMentions(client, server, text);
|
||||
//text = CleanRoleMentions(_client, User, channel, text);
|
||||
}
|
||||
return text;
|
||||
var client = channel.Client;
|
||||
text = CleanUserMentions(client, channel, text);
|
||||
text = CleanChannelMentions(client, channel, text);
|
||||
//text = CleanRoleMentions(_client, channel, text);
|
||||
return text;
|
||||
}
|
||||
}
|
||||
}
|
||||
102
src/Discord.Net/MessageQueue.cs
Normal file
102
src/Discord.Net/MessageQueue.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using Discord.API.Client.Rest;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord.Net
|
||||
{
|
||||
public class MessageQueue
|
||||
{
|
||||
private class MessageQueueItem
|
||||
{
|
||||
public readonly ulong Id, ChannelId;
|
||||
public readonly string Text;
|
||||
public readonly ulong[] MentionedUsers;
|
||||
public readonly bool IsTTS;
|
||||
|
||||
public MessageQueueItem(ulong id, ulong channelId, string text, ulong[] userIds, bool isTTS)
|
||||
{
|
||||
Id = id;
|
||||
ChannelId = channelId;
|
||||
Text = text;
|
||||
MentionedUsers = userIds;
|
||||
IsTTS = isTTS;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Random _nonceRand;
|
||||
private readonly DiscordClient _client;
|
||||
private readonly ConcurrentQueue<MessageQueueItem> _pending;
|
||||
|
||||
internal MessageQueue(DiscordClient client)
|
||||
{
|
||||
_client = client;
|
||||
_nonceRand = new Random();
|
||||
_pending = new ConcurrentQueue<MessageQueueItem>();
|
||||
}
|
||||
|
||||
public void QueueSend(ulong channelId, string text, ulong[] userIds, bool isTTS)
|
||||
{
|
||||
_pending.Enqueue(new MessageQueueItem(0, channelId, text, userIds, isTTS));
|
||||
}
|
||||
public void QueueEdit(ulong channelId, ulong messageId, string text, ulong[] userIds)
|
||||
{
|
||||
_pending.Enqueue(new MessageQueueItem(channelId, messageId, text, userIds, false));
|
||||
}
|
||||
|
||||
internal Task Run(CancellationToken cancelToken, int interval)
|
||||
{
|
||||
return Task.Run(async () =>
|
||||
{
|
||||
MessageQueueItem queuedMessage;
|
||||
|
||||
while (!cancelToken.IsCancellationRequested)
|
||||
{
|
||||
await Task.Delay(interval).ConfigureAwait(false);
|
||||
while (_pending.TryDequeue(out queuedMessage))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (queuedMessage.Id == 0)
|
||||
{
|
||||
var request = new SendMessageRequest(queuedMessage.ChannelId)
|
||||
{
|
||||
Content = queuedMessage.Text,
|
||||
MentionedUserIds = queuedMessage.MentionedUsers,
|
||||
Nonce = GenerateNonce().ToIdString(),
|
||||
IsTTS = queuedMessage.IsTTS
|
||||
};
|
||||
await _client.ClientAPI.Send(request).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
var request = new UpdateMessageRequest(queuedMessage.ChannelId, queuedMessage.Id)
|
||||
{
|
||||
Content = queuedMessage.Text,
|
||||
MentionedUserIds = queuedMessage.MentionedUsers
|
||||
};
|
||||
await _client.ClientAPI.Send(request).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (WebException) { break; }
|
||||
catch (HttpException) { /*msg.State = MessageState.Failed;*/ }
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
MessageQueueItem ignored;
|
||||
while (_pending.TryDequeue(out ignored)) { }
|
||||
}
|
||||
|
||||
private ulong GenerateNonce()
|
||||
{
|
||||
lock (_nonceRand)
|
||||
return (ulong)_nonceRand.Next(1, int.MaxValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
using Discord.API.Client;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -7,238 +7,278 @@ using APIChannel = Discord.API.Client.Channel;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public sealed class Channel : CachedObject<ulong>
|
||||
{
|
||||
private struct ChannelMember
|
||||
{
|
||||
public readonly User User;
|
||||
public readonly ChannelPermissions Permissions;
|
||||
public sealed class Channel
|
||||
{
|
||||
private struct Member
|
||||
{
|
||||
public readonly User User;
|
||||
public readonly ChannelPermissions Permissions;
|
||||
public Member(User user)
|
||||
{
|
||||
User = user;
|
||||
Permissions = new ChannelPermissions();
|
||||
Permissions.Lock();
|
||||
}
|
||||
}
|
||||
|
||||
public ChannelMember(User user)
|
||||
{
|
||||
User = user;
|
||||
Permissions = new ChannelPermissions();
|
||||
Permissions.Lock();
|
||||
}
|
||||
}
|
||||
public sealed class PermissionOverwrite
|
||||
{
|
||||
public PermissionTarget TargetType { get; }
|
||||
public ulong TargetId { get; }
|
||||
public DualChannelPermissions Permissions { get; }
|
||||
internal PermissionOverwrite(PermissionTarget targetType, ulong targetId, uint allow, uint deny)
|
||||
{
|
||||
TargetType = targetType;
|
||||
TargetId = targetId;
|
||||
Permissions = new DualChannelPermissions(allow, deny);
|
||||
Permissions.Lock();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ConcurrentDictionary<ulong, Member> _users;
|
||||
private readonly ConcurrentDictionary<ulong, Message> _messages;
|
||||
private Dictionary<ulong, PermissionOverwrite> _permissionOverwrites;
|
||||
|
||||
public sealed class PermissionOverwrite
|
||||
{
|
||||
public PermissionTarget TargetType { get; }
|
||||
public ulong TargetId { get; }
|
||||
public DualChannelPermissions Permissions { get; }
|
||||
/// <summary> Gets the client that generated this channel object. </summary>
|
||||
internal DiscordClient Client { get; }
|
||||
/// <summary> Gets the unique identifier for this channel. </summary>
|
||||
public ulong Id { get; }
|
||||
/// <summary> Gets the server owning this channel, if this is a public chat. </summary>
|
||||
public Server Server { get; }
|
||||
/// <summary> Gets the target user, if this is a private chat. </summary>
|
||||
public User Recipient { get; }
|
||||
|
||||
internal PermissionOverwrite(PermissionTarget targetType, ulong targetId, uint allow, uint deny)
|
||||
{
|
||||
TargetType = targetType;
|
||||
TargetId = targetId;
|
||||
Permissions = new DualChannelPermissions(allow, deny);
|
||||
Permissions.Lock();
|
||||
}
|
||||
}
|
||||
/// <summary> Gets the name of this channel. </summary>
|
||||
public string Name { get; private set; }
|
||||
/// <summary> Gets the topic of this channel. </summary>
|
||||
public string Topic { get; private set; }
|
||||
/// <summary> Gets the position of this channel relative to other channels in this server. </summary>
|
||||
public int Position { get; private set; }
|
||||
/// <summary> Gets the type of this channel (see ChannelTypes). </summary>
|
||||
public string Type { get; private set; }
|
||||
|
||||
/// <summary> Returns the name of this channel. </summary>
|
||||
public string Name { get; private set; }
|
||||
/// <summary> Returns the topic associated with this channel. </summary>
|
||||
public string Topic { get; private set; }
|
||||
/// <summary> Returns the position of this channel in the channel list for this server. </summary>
|
||||
public int Position { get; private set; }
|
||||
/// <summary> Returns false is this is a public chat and true if this is a private chat with another user (see Recipient). </summary>
|
||||
public bool IsPrivate => _recipient.Id != null;
|
||||
/// <summary> Returns the type of this channel (see ChannelTypes). </summary>
|
||||
public string Type { get; private set; }
|
||||
/// <summary> Gets true if this is a private chat with another user. </summary>
|
||||
public bool IsPrivate => Recipient != null;
|
||||
/// <summary> Gets the string used to mention this channel. </summary>
|
||||
public string Mention => $"<#{Id}>";
|
||||
/// <summary> Gets a collection of all messages the client has seen posted in this channel. This collection does not guarantee any ordering. </summary>
|
||||
public IEnumerable<Message> Messages => _messages?.Values ?? Enumerable.Empty<Message>();
|
||||
/// <summary> Gets a collection of all custom permissions used for this channel. </summary>
|
||||
public IEnumerable<PermissionOverwrite> PermissionOverwrites => _permissionOverwrites.Select(x => x.Value);
|
||||
|
||||
/// <summary> Returns the server containing this channel. </summary>
|
||||
[JsonIgnore]
|
||||
public Server Server => _server.Value;
|
||||
[JsonProperty]
|
||||
private ulong? ServerId { get { return _server.Id; } set { _server.Id = value; } }
|
||||
private readonly Reference<Server> _server;
|
||||
|
||||
/// For private chats, returns the target user, otherwise null.
|
||||
[JsonIgnore]
|
||||
public User Recipient => _recipient.Value;
|
||||
[JsonProperty]
|
||||
private ulong? RecipientId { get { return _recipient.Id; } set { _recipient.Id = value; } }
|
||||
private readonly Reference<User> _recipient;
|
||||
|
||||
//Collections
|
||||
/// <summary> Returns a collection of all users with read access to this channel. </summary>
|
||||
[JsonIgnore]
|
||||
public IEnumerable<User> Members
|
||||
{
|
||||
get
|
||||
{
|
||||
/// <summary> Gets a collection of all users with read access to this channel. </summary>
|
||||
public IEnumerable<User> Users
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsPrivate)
|
||||
return _members.Values.Select(x => x.User);
|
||||
if (_client.Config.UsePermissionsCache)
|
||||
return _users.Values.Select(x => x.User);
|
||||
if (Client.Config.UsePermissionsCache)
|
||||
{
|
||||
if (Type == ChannelType.Text)
|
||||
return _members.Values.Where(x => x.Permissions.ReadMessages == true).Select(x => x.User);
|
||||
return _users.Values.Where(x => x.Permissions.ReadMessages == true).Select(x => x.User);
|
||||
else if (Type == ChannelType.Voice)
|
||||
return _members.Values.Select(x => x.User).Where(x => x.VoiceChannel == this);
|
||||
return _users.Values.Select(x => x.User).Where(x => x.VoiceChannel == this);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Type == ChannelType.Text)
|
||||
{
|
||||
ChannelPermissions perms = new ChannelPermissions();
|
||||
return Server.Members.Where(x =>
|
||||
return Server.Users.Where(x =>
|
||||
{
|
||||
UpdatePermissions(x, perms);
|
||||
return perms.ReadMessages == true;
|
||||
});
|
||||
}
|
||||
else if (Type == ChannelType.Voice)
|
||||
return Server.Members.Where(x => x.VoiceChannel == this);
|
||||
return Server.Users.Where(x => x.VoiceChannel == this);
|
||||
}
|
||||
return Enumerable.Empty<User>();
|
||||
}
|
||||
}
|
||||
[JsonProperty]
|
||||
private IEnumerable<ulong> MemberIds => Members.Select(x => x.Id);
|
||||
private ConcurrentDictionary<ulong, ChannelMember> _members;
|
||||
|
||||
/// <summary> Returns a collection of all messages the client has seen posted in this channel. This collection does not guarantee any ordering. </summary>
|
||||
[JsonIgnore]
|
||||
public IEnumerable<Message> Messages => _messages?.Values ?? Enumerable.Empty<Message>();
|
||||
[JsonProperty]
|
||||
private IEnumerable<ulong> MessageIds => Messages.Select(x => x.Id);
|
||||
private readonly ConcurrentDictionary<ulong, Message> _messages;
|
||||
|
||||
/// <summary> Returns a collection of all custom permissions used for this channel. </summary>
|
||||
private PermissionOverwrite[] _permissionOverwrites;
|
||||
public IEnumerable<PermissionOverwrite> PermissionOverwrites { get { return _permissionOverwrites; } internal set { _permissionOverwrites = value.ToArray(); } }
|
||||
|
||||
/// <summary> Returns the string used to mention this channel. </summary>
|
||||
public string Mention => $"<#{Id}>";
|
||||
|
||||
internal Channel(DiscordClient client, ulong id, ulong? serverId, ulong? recipientId)
|
||||
: base(client, id)
|
||||
{
|
||||
_server = new Reference<Server>(serverId,
|
||||
x => _client.Servers[x],
|
||||
x => x.AddChannel(this),
|
||||
x => x.RemoveChannel(this));
|
||||
_recipient = new Reference<User>(recipientId,
|
||||
x => _client.Users.GetOrAdd(x, _server.Id),
|
||||
x =>
|
||||
{
|
||||
Name = $"@{x}";
|
||||
if (_server.Id == null)
|
||||
x.Global.PrivateChannel = this;
|
||||
},
|
||||
x =>
|
||||
{
|
||||
if (_server.Id == null)
|
||||
x.Global.PrivateChannel = null;
|
||||
});
|
||||
_permissionOverwrites = new PermissionOverwrite[0];
|
||||
_members = new ConcurrentDictionary<ulong, ChannelMember>();
|
||||
|
||||
if (recipientId != null)
|
||||
{
|
||||
AddMember(client.PrivateUser);
|
||||
AddMember(Recipient);
|
||||
}
|
||||
|
||||
//Local Cache
|
||||
if (client.Config.MessageCacheSize > 0)
|
||||
_messages = new ConcurrentDictionary<ulong, Message>();
|
||||
}
|
||||
internal override bool LoadReferences()
|
||||
{
|
||||
if (IsPrivate)
|
||||
return _recipient.Load();
|
||||
else
|
||||
return _server.Load();
|
||||
}
|
||||
internal override void UnloadReferences()
|
||||
{
|
||||
_server.Unload();
|
||||
_recipient.Unload();
|
||||
|
||||
var globalMessages = _client.Messages;
|
||||
if (_client.Config.MessageCacheSize > 0)
|
||||
{
|
||||
var messages = _messages;
|
||||
foreach (var message in messages)
|
||||
globalMessages.TryRemove(message.Key);
|
||||
messages.Clear();
|
||||
return Enumerable.Empty<User>();
|
||||
}
|
||||
}
|
||||
|
||||
internal void Update(ChannelReference model)
|
||||
{
|
||||
if (!IsPrivate && model.Name != null)
|
||||
Name = model.Name;
|
||||
if (model.Type != null)
|
||||
Type = model.Type;
|
||||
}
|
||||
internal void Update(APIChannel model)
|
||||
{
|
||||
Update(model as ChannelReference);
|
||||
|
||||
if (model.Position != null)
|
||||
Position = model.Position;
|
||||
if (model.Topic != null)
|
||||
Topic = model.Topic;
|
||||
|
||||
if (model.PermissionOverwrites != null)
|
||||
{
|
||||
_permissionOverwrites = model.PermissionOverwrites
|
||||
.Select(x => new PermissionOverwrite(PermissionTarget.FromString(x.Type), x.Id, x.Allow, x.Deny))
|
||||
.ToArray();
|
||||
UpdatePermissions();
|
||||
}
|
||||
}
|
||||
|
||||
internal void AddMessage(Message message)
|
||||
{
|
||||
//Race conditions are okay here - it just means the queue will occasionally go higher than the requested cache size, and fixed later.
|
||||
var cacheLength = _client.Config.MessageCacheSize;
|
||||
if (cacheLength > 0)
|
||||
{
|
||||
var oldestIds = _messages.Where(x => x.Value.Timestamp < message.Timestamp).Select(x => x.Key).OrderBy(x => x).Take(_messages.Count - cacheLength);
|
||||
foreach (var id in oldestIds)
|
||||
{
|
||||
Message removed;
|
||||
if (_messages.TryRemove(id, out removed))
|
||||
_client.Messages.TryRemove(id);
|
||||
}
|
||||
_messages.TryAdd(message.Id, message);
|
||||
}
|
||||
}
|
||||
internal void RemoveMessage(Message message)
|
||||
internal Channel(DiscordClient client, ulong id, Server server)
|
||||
: this(client, id)
|
||||
{
|
||||
if (_client.Config.MessageCacheSize > 0)
|
||||
_messages.TryRemove(message.Id, out message);
|
||||
Server = server;
|
||||
}
|
||||
|
||||
internal void AddMember(User user)
|
||||
internal Channel(DiscordClient client, ulong id, User recipient)
|
||||
: this(client, id)
|
||||
{
|
||||
if (!_client.Config.UsePermissionsCache)
|
||||
Recipient = recipient;
|
||||
Name = $"@{recipient}";
|
||||
AddUser(client.PrivateUser);
|
||||
AddUser(recipient);
|
||||
}
|
||||
private Channel(DiscordClient client, ulong id)
|
||||
{
|
||||
Client = client;
|
||||
Id = id;
|
||||
|
||||
_permissionOverwrites = new Dictionary<ulong, PermissionOverwrite>();
|
||||
_users = new ConcurrentDictionary<ulong, Member>();
|
||||
if (client.Config.MessageCacheSize > 0)
|
||||
_messages = new ConcurrentDictionary<ulong, Message>();
|
||||
}
|
||||
|
||||
internal void Update(ChannelReference model)
|
||||
{
|
||||
if (!IsPrivate && model.Name != null)
|
||||
Name = model.Name;
|
||||
if (model.Type != null)
|
||||
Type = model.Type;
|
||||
}
|
||||
internal void Update(APIChannel model)
|
||||
{
|
||||
Update(model as ChannelReference);
|
||||
|
||||
if (model.Position != null)
|
||||
Position = model.Position.Value;
|
||||
if (model.Topic != null)
|
||||
Topic = model.Topic;
|
||||
if (model.Recipient != null)
|
||||
Recipient.Update(model.Recipient);
|
||||
|
||||
if (model.PermissionOverwrites != null)
|
||||
{
|
||||
_permissionOverwrites = model.PermissionOverwrites
|
||||
.Select(x => new PermissionOverwrite(PermissionTarget.FromString(x.Type), x.Id, x.Allow, x.Deny))
|
||||
.ToDictionary(x => x.TargetId);
|
||||
UpdatePermissions();
|
||||
}
|
||||
}
|
||||
|
||||
//Members
|
||||
internal void AddUser(User user)
|
||||
{
|
||||
if (!Client.Config.UsePermissionsCache)
|
||||
return;
|
||||
|
||||
var member = new ChannelMember(user);
|
||||
if (_members.TryAdd(user.Id, member))
|
||||
UpdatePermissions(user, member.Permissions);
|
||||
var member = new Member(user);
|
||||
if (_users.TryAdd(user.Id, member))
|
||||
UpdatePermissions(user, member.Permissions);
|
||||
}
|
||||
internal void RemoveMember(User user)
|
||||
internal void RemoveUser(ulong id)
|
||||
{
|
||||
if (!_client.Config.UsePermissionsCache)
|
||||
if (!Client.Config.UsePermissionsCache)
|
||||
return;
|
||||
|
||||
ChannelMember ignored;
|
||||
_members.TryRemove(user.Id, out ignored);
|
||||
}
|
||||
Member ignored;
|
||||
_users.TryRemove(id, out ignored);
|
||||
}
|
||||
public User GetUser(ulong id)
|
||||
{
|
||||
Member result;
|
||||
_users.TryGetValue(id, out result);
|
||||
return result.User;
|
||||
}
|
||||
|
||||
//Messages
|
||||
internal Message AddMessage(ulong id, ulong userId, DateTime timestamp)
|
||||
{
|
||||
Message message = new Message(id, this, userId);
|
||||
var cacheLength = Client.Config.MessageCacheSize;
|
||||
if (cacheLength > 0)
|
||||
{
|
||||
var oldestIds = _messages
|
||||
.Where(x => x.Value.Timestamp < timestamp)
|
||||
.Select(x => x.Key).OrderBy(x => x)
|
||||
.Take(_messages.Count - cacheLength);
|
||||
Message removed;
|
||||
foreach (var removeId in oldestIds)
|
||||
_messages.TryRemove(removeId, out removed);
|
||||
return _messages.GetOrAdd(message.Id, message);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
internal Message RemoveMessage(ulong id)
|
||||
{
|
||||
if (Client.Config.MessageCacheSize > 0)
|
||||
{
|
||||
Message msg;
|
||||
_messages.TryRemove(id, out msg);
|
||||
return msg;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public Message GetMessage(ulong id)
|
||||
{
|
||||
Message result;
|
||||
_messages.TryGetValue(id, out result);
|
||||
return result;
|
||||
}
|
||||
|
||||
//Permissions
|
||||
internal void UpdatePermissions()
|
||||
{
|
||||
if (!Client.Config.UsePermissionsCache)
|
||||
return;
|
||||
|
||||
foreach (var pair in _users)
|
||||
{
|
||||
Member member = pair.Value;
|
||||
UpdatePermissions(member.User, member.Permissions);
|
||||
}
|
||||
}
|
||||
internal void UpdatePermissions(User user)
|
||||
{
|
||||
if (!Client.Config.UsePermissionsCache)
|
||||
return;
|
||||
|
||||
Member member;
|
||||
if (_users.TryGetValue(user.Id, out member))
|
||||
UpdatePermissions(member.User, member.Permissions);
|
||||
}
|
||||
internal void UpdatePermissions(User user, ChannelPermissions permissions)
|
||||
{
|
||||
uint newPermissions = 0;
|
||||
var server = Server;
|
||||
|
||||
//Load the mask of all permissions supported by this channel type
|
||||
var mask = ChannelPermissions.All(this).RawValue;
|
||||
|
||||
if (server != null)
|
||||
{
|
||||
//Start with this user's server permissions
|
||||
newPermissions = server.GetPermissions(user).RawValue;
|
||||
|
||||
if (IsPrivate || user == Server.Owner)
|
||||
newPermissions = mask; //Owners always have all permissions
|
||||
else
|
||||
{
|
||||
var channelOverwrites = PermissionOverwrites;
|
||||
|
||||
var roles = user.Roles;
|
||||
foreach (var denyRole in channelOverwrites.Where(x => x.TargetType == PermissionTarget.Role && x.Permissions.Deny.RawValue != 0 && roles.Any(y => y.Id == x.TargetId)))
|
||||
newPermissions &= ~denyRole.Permissions.Deny.RawValue;
|
||||
foreach (var allowRole in channelOverwrites.Where(x => x.TargetType == PermissionTarget.Role && x.Permissions.Allow.RawValue != 0 && roles.Any(y => y.Id == x.TargetId)))
|
||||
newPermissions |= allowRole.Permissions.Allow.RawValue;
|
||||
foreach (var denyUser in channelOverwrites.Where(x => x.TargetType == PermissionTarget.User && x.TargetId == Id && x.Permissions.Deny.RawValue != 0))
|
||||
newPermissions &= ~denyUser.Permissions.Deny.RawValue;
|
||||
foreach (var allowUser in channelOverwrites.Where(x => x.TargetType == PermissionTarget.User && x.TargetId == Id && x.Permissions.Allow.RawValue != 0))
|
||||
newPermissions |= allowUser.Permissions.Allow.RawValue;
|
||||
|
||||
if (newPermissions.HasBit((byte)PermissionsBits.ManageRolesOrPermissions))
|
||||
newPermissions = mask; //ManageRolesOrPermissions gives all permisions
|
||||
else if (Type == ChannelType.Text && !newPermissions.HasBit((byte)PermissionsBits.ReadMessages))
|
||||
newPermissions = 0; //No read permission on a text channel removes all other permissions
|
||||
else
|
||||
newPermissions &= mask; //Ensure we didnt get any permissions this channel doesnt support (from serverPerms, for example)
|
||||
}
|
||||
}
|
||||
else
|
||||
newPermissions = mask; //Private messages always have all permissions
|
||||
|
||||
if (newPermissions != permissions.RawValue)
|
||||
permissions.SetRawValueInternal(newPermissions);
|
||||
}
|
||||
internal ChannelPermissions GetPermissions(User user)
|
||||
{
|
||||
if (_client.Config.UsePermissionsCache)
|
||||
if (Client.Config.UsePermissionsCache)
|
||||
{
|
||||
ChannelMember member;
|
||||
if (_members.TryGetValue(user.Id, out member))
|
||||
Member member;
|
||||
if (_users.TryGetValue(user.Id, out member))
|
||||
return member.Permissions;
|
||||
else
|
||||
return null;
|
||||
@@ -249,73 +289,10 @@ namespace Discord
|
||||
UpdatePermissions(user, perms);
|
||||
return perms;
|
||||
}
|
||||
}
|
||||
internal void UpdatePermissions()
|
||||
{
|
||||
if (!_client.Config.UsePermissionsCache)
|
||||
return;
|
||||
|
||||
foreach (var pair in _members)
|
||||
{
|
||||
ChannelMember member = pair.Value;
|
||||
UpdatePermissions(member.User, member.Permissions);
|
||||
}
|
||||
}
|
||||
internal void UpdatePermissions(User user)
|
||||
{
|
||||
if (!_client.Config.UsePermissionsCache)
|
||||
return;
|
||||
|
||||
ChannelMember member;
|
||||
if (_members.TryGetValue(user.Id, out member))
|
||||
UpdatePermissions(member.User, member.Permissions);
|
||||
}
|
||||
internal void UpdatePermissions(User user, ChannelPermissions permissions)
|
||||
{
|
||||
uint newPermissions = 0;
|
||||
var server = Server;
|
||||
|
||||
//Load the mask of all permissions supported by this channel type
|
||||
var mask = ChannelPermissions.All(this).RawValue;
|
||||
|
||||
if (server != null)
|
||||
{
|
||||
//Start with this user's server permissions
|
||||
newPermissions = server.GetPermissions(user).RawValue;
|
||||
|
||||
if (IsPrivate || user.IsOwner)
|
||||
newPermissions = mask; //Owners always have all permissions
|
||||
else
|
||||
{
|
||||
var channelOverwrites = PermissionOverwrites;
|
||||
|
||||
var roles = user.Roles;
|
||||
foreach (var denyRole in channelOverwrites.Where(x => x.TargetType == PermissionTarget.Role && x.Permissions.Deny.RawValue != 0 && roles.Any(y => y.Id == x.TargetId)))
|
||||
newPermissions &= ~denyRole.Permissions.Deny.RawValue;
|
||||
foreach (var allowRole in channelOverwrites.Where(x => x.TargetType == PermissionTarget.Role && x.Permissions.Allow.RawValue != 0 && roles.Any(y => y.Id == x.TargetId)))
|
||||
newPermissions |= allowRole.Permissions.Allow.RawValue;
|
||||
foreach (var denyUser in channelOverwrites.Where(x => x.TargetType == PermissionTarget.User && x.TargetId == Id && x.Permissions.Deny.RawValue != 0))
|
||||
newPermissions &= ~denyUser.Permissions.Deny.RawValue;
|
||||
foreach (var allowUser in channelOverwrites.Where(x => x.TargetType == PermissionTarget.User && x.TargetId == Id && x.Permissions.Allow.RawValue != 0))
|
||||
newPermissions |= allowUser.Permissions.Allow.RawValue;
|
||||
|
||||
if (BitHelper.GetBit(newPermissions, (int)PermissionsBits.ManageRolesOrPermissions))
|
||||
newPermissions = mask; //ManageRolesOrPermissions gives all permisions
|
||||
else if (Type == ChannelType.Text && !BitHelper.GetBit(newPermissions, (int)PermissionsBits.ReadMessages))
|
||||
newPermissions = 0; //No read permission on a text channel removes all other permissions
|
||||
else
|
||||
newPermissions &= mask; //Ensure we didnt get any permissions this channel doesnt support (from serverPerms, for example)
|
||||
}
|
||||
}
|
||||
else
|
||||
newPermissions = mask; //Private messages always have all permissions
|
||||
|
||||
if (newPermissions != permissions.RawValue)
|
||||
permissions.SetRawValueInternal(newPermissions);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj) => obj is Channel && (obj as Channel).Id == Id;
|
||||
public override int GetHashCode() => unchecked(Id.GetHashCode() + 5658);
|
||||
public override string ToString() => Name ?? IdConvert.ToString(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 string ToString() => Name ?? Id.ToIdString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using APIUser = Discord.API.Client.User;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public sealed class GlobalUser : CachedObject<ulong>
|
||||
/*public sealed class GlobalUser : CachedObject<ulong>
|
||||
{
|
||||
/// <summary> Returns the email for this user. Note: this field is only ever populated for the current logged in user. </summary>
|
||||
[JsonIgnore]
|
||||
@@ -75,5 +75,5 @@ namespace Discord
|
||||
public override bool Equals(object obj) => obj is GlobalUser && (obj as GlobalUser).Id == Id;
|
||||
public override int GetHashCode() => unchecked(Id.GetHashCode() + 7891);
|
||||
public override string ToString() => IdConvert.ToString(Id);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ namespace Discord
|
||||
public ushort Discriminator { get; }
|
||||
/// <summary> Returns the unique identifier for this user's avatar. </summary>
|
||||
public string AvatarId { get; }
|
||||
|
||||
/// <summary> Returns the full path to this user's avatar. </summary>
|
||||
public string AvatarUrl => User.GetAvatarUrl(Id, AvatarId);
|
||||
|
||||
@@ -54,24 +55,26 @@ namespace Discord
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Returns information about the server this invite is attached to. </summary>
|
||||
public ServerInfo Server { get; private set; }
|
||||
/// <summary> Returns information about the channel this invite is attached to. </summary>
|
||||
public ChannelInfo Channel { get; private set; }
|
||||
|
||||
/// <summary> Gets the unique code for this invite. </summary>
|
||||
public string Code { get; }
|
||||
/// <summary> Returns, if enabled, an alternative human-readable code for URLs. </summary>
|
||||
/// <summary> Gets, if enabled, an alternative human-readable invite code. </summary>
|
||||
public string XkcdCode { get; }
|
||||
/// <summary> Time (in seconds) until the invite expires. Set to 0 to never expire. </summary>
|
||||
public int MaxAge { get; private set; }
|
||||
/// <summary> The amount of times this invite has been used. </summary>
|
||||
|
||||
/// <summary> Gets information about the server this invite is attached to. </summary>
|
||||
public ServerInfo Server { get; private set; }
|
||||
/// <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; }
|
||||
/// <summary> Gets the amount of times this invite has been used. </summary>
|
||||
public int Uses { get; private set; }
|
||||
/// <summary> The max amount of times this invite may be used. </summary>
|
||||
public int MaxUses { get; private set; }
|
||||
/// <summary> Returns true if this invite has been destroyed, or you are banned from that server. </summary>
|
||||
/// <summary> Gets the max amount of times this invite may be used. </summary>
|
||||
public int? MaxUses { get; private set; }
|
||||
/// <summary> Returns true if this invite has expired, been destroyed, or you are banned from that server. </summary>
|
||||
public bool IsRevoked { get; private set; }
|
||||
/// <summary> If true, a user accepting this invite will be kicked from the server after closing their client. </summary>
|
||||
public bool IsTemporary { get; private set; }
|
||||
/// <summary> Gets when this invite was created. </summary>
|
||||
public DateTime CreatedAt { get; private set; }
|
||||
|
||||
/// <summary> Returns a URL for this invite using XkcdCode if available or Id if not. </summary>
|
||||
@@ -99,7 +102,7 @@ namespace Discord
|
||||
if (model.IsTemporary != null)
|
||||
IsTemporary = model.IsTemporary.Value;
|
||||
if (model.MaxAge != null)
|
||||
MaxAge = model.MaxAge.Value;
|
||||
MaxAge = model.MaxAge.Value != 0 ? model.MaxAge.Value : (int?)null;
|
||||
if (model.MaxUses != null)
|
||||
MaxUses = model.MaxUses.Value;
|
||||
if (model.Uses != null)
|
||||
|
||||
@@ -15,9 +15,9 @@ namespace Discord
|
||||
Failed
|
||||
}
|
||||
|
||||
public sealed class Message : CachedObject<ulong>
|
||||
public sealed class Message
|
||||
{
|
||||
internal class ImportResolver : DefaultContractResolver
|
||||
/*internal class ImportResolver : DefaultContractResolver
|
||||
{
|
||||
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
|
||||
{
|
||||
@@ -31,7 +31,7 @@ namespace Discord
|
||||
}
|
||||
return property;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
public sealed class Attachment : File
|
||||
{
|
||||
@@ -89,17 +89,24 @@ namespace Discord
|
||||
internal File() { }
|
||||
}
|
||||
|
||||
/// <summary> Returns true if the logged-in user was mentioned. </summary>
|
||||
public bool IsMentioningMe { get; private set; }
|
||||
/// <summary> Returns true if the current user created this message. </summary>
|
||||
public bool IsAuthor => _client.CurrentUser.Id == _user.Id;
|
||||
private static readonly Attachment[] _initialAttachments = new Attachment[0];
|
||||
private static readonly Embed[] _initialEmbeds = new Embed[0];
|
||||
|
||||
private readonly ulong _userId;
|
||||
|
||||
/// <summary> Returns the unique identifier for this message. </summary>
|
||||
public ulong Id { get; }
|
||||
/// <summary> Returns the channel this message was sent to. </summary>
|
||||
public Channel Channel { get; }
|
||||
|
||||
/// <summary> Returns true if the logged-in user was mentioned. </summary>
|
||||
public bool IsMentioningMe { get; private set; }
|
||||
/// <summary> Returns true if the message was sent as text-to-speech by someone with permissions to do so. </summary>
|
||||
public bool IsTTS { get; private set; }
|
||||
/// <summary> Returns the state of this message. Only useful if UseMessageQueue is true. </summary>
|
||||
public MessageState State { get; internal set; }
|
||||
/// <summary> Returns the raw content of this message as it was received from the server. </summary>
|
||||
public string RawText { get; private set; }
|
||||
[JsonIgnore]
|
||||
/// <summary> Returns the content of this message with any special references such as mentions converted. </summary>
|
||||
public string Text { get; internal set; }
|
||||
/// <summary> Returns the timestamp for when this message was sent. </summary>
|
||||
@@ -108,89 +115,26 @@ namespace Discord
|
||||
public DateTime? EditedTimestamp { get; private set; }
|
||||
/// <summary> Returns the attachments included in this message. </summary>
|
||||
public Attachment[] Attachments { get; private set; }
|
||||
private static readonly Attachment[] _initialAttachments = new Attachment[0];
|
||||
/// <summary> Returns a collection of all embeded content in this message. </summary>
|
||||
public Embed[] Embeds { get; private set; }
|
||||
private static readonly Embed[] _initialEmbeds = new Embed[0];
|
||||
|
||||
/// <summary> Returns a collection of all users mentioned in this message. </summary>
|
||||
[JsonIgnore]
|
||||
public IEnumerable<User> MentionedUsers { get; internal set; }
|
||||
[JsonProperty]
|
||||
private IEnumerable<ulong> MentionedUserIds
|
||||
{
|
||||
get { return MentionedUsers?.Select(x => x.Id); }
|
||||
set { MentionedUsers = value.Select(x => _client.GetUser(Server, x)).Where(x => x != null); }
|
||||
}
|
||||
|
||||
/// <summary> Returns a collection of all channels mentioned in this message. </summary>
|
||||
[JsonIgnore]
|
||||
public IEnumerable<Channel> MentionedChannels { get; internal set; }
|
||||
[JsonProperty]
|
||||
private IEnumerable<ulong> MentionedChannelIds
|
||||
{
|
||||
get { return MentionedChannels?.Select(x => x.Id); }
|
||||
set { MentionedChannels = value.Select(x => _client.GetChannel(x)).Where(x => x != null); }
|
||||
}
|
||||
|
||||
/// <summary> Returns a collection of all roles mentioned in this message. </summary>
|
||||
[JsonIgnore]
|
||||
public IEnumerable<Role> MentionedRoles { get; internal set; }
|
||||
[JsonProperty]
|
||||
private IEnumerable<ulong> MentionedRoleIds
|
||||
{
|
||||
get { return MentionedRoles?.Select(x => x.Id); }
|
||||
set { MentionedRoles = value.Select(x => _client.GetRole(x)).Where(x => x != null); }
|
||||
}
|
||||
|
||||
/// <summary> Returns the server containing the channel this message was sent to. </summary>
|
||||
[JsonIgnore]
|
||||
public Server Server => _channel.Value.Server;
|
||||
public Server Server => Channel.Server;
|
||||
/// <summary> Returns the author of this message. </summary>
|
||||
public User User => Channel.GetUser(_userId);
|
||||
|
||||
/// <summary> Returns the channel this message was sent to. </summary>
|
||||
[JsonIgnore]
|
||||
public Channel Channel => _channel.Value;
|
||||
[JsonProperty]
|
||||
private ulong? ChannelId => _channel.Id;
|
||||
private readonly Reference<Channel> _channel;
|
||||
|
||||
/// <summary> Returns the author of this message. </summary>
|
||||
[JsonIgnore]
|
||||
public User User => _user.Value;
|
||||
[JsonProperty]
|
||||
private ulong? UserId => _user.Id;
|
||||
private readonly Reference<User> _user;
|
||||
|
||||
internal Message(DiscordClient client, ulong id, ulong channelId, ulong userId)
|
||||
: base(client, id)
|
||||
internal Message(ulong id, Channel channel, ulong userId)
|
||||
{
|
||||
_channel = new Reference<Channel>(channelId,
|
||||
x => _client.Channels[x],
|
||||
x => x.AddMessage(this),
|
||||
x => x.RemoveMessage(this));
|
||||
_user = new Reference<User>(userId,
|
||||
x =>
|
||||
{
|
||||
var channel = Channel;
|
||||
if (channel == null) return null;
|
||||
|
||||
if (!channel.IsPrivate)
|
||||
return _client.Users[x, channel.Server.Id];
|
||||
else
|
||||
return _client.Users[x, null];
|
||||
});
|
||||
Attachments = _initialAttachments;
|
||||
Embeds = _initialEmbeds;
|
||||
}
|
||||
internal override bool LoadReferences()
|
||||
{
|
||||
return _channel.Load() && _user.Load();
|
||||
}
|
||||
internal override void UnloadReferences()
|
||||
{
|
||||
_channel.Unload();
|
||||
_user.Unload();
|
||||
}
|
||||
|
||||
internal void Update(APIMessage model)
|
||||
{
|
||||
@@ -247,7 +191,7 @@ namespace Discord
|
||||
if (model.Mentions != null)
|
||||
{
|
||||
MentionedUsers = model.Mentions
|
||||
.Select(x => _client.Users[x.Id, Channel.Server?.Id])
|
||||
.Select(x => Channel.GetUser(x.Id))
|
||||
.Where(x => x != null)
|
||||
.ToArray();
|
||||
}
|
||||
@@ -266,10 +210,10 @@ namespace Discord
|
||||
//var mentionedUsers = new List<User>();
|
||||
var mentionedChannels = new List<Channel>();
|
||||
//var mentionedRoles = new List<Role>();
|
||||
text = Mention.CleanUserMentions(_client, server, text/*, mentionedUsers*/);
|
||||
text = Mention.CleanUserMentions(Channel.Client, channel, text/*, mentionedUsers*/);
|
||||
if (server != null)
|
||||
{
|
||||
text = Mention.CleanChannelMentions(_client, server, text, mentionedChannels);
|
||||
text = Mention.CleanChannelMentions(Channel.Client, channel, text, mentionedChannels);
|
||||
//text = Mention.CleanRoleMentions(_client, User, channel, text, mentionedRoles);
|
||||
}
|
||||
Text = text;
|
||||
@@ -287,7 +231,7 @@ namespace Discord
|
||||
}
|
||||
else
|
||||
{
|
||||
var me = _client.PrivateUser;
|
||||
var me = Channel.Client.PrivateUser;
|
||||
IsMentioningMe = MentionedUsers?.Contains(me) ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,9 +127,15 @@ namespace Discord
|
||||
_rawValue = rawValue;
|
||||
}
|
||||
|
||||
internal bool GetBit(PermissionsBits pos) => BitHelper.GetBit(_rawValue, (int)pos);
|
||||
internal void SetBit(PermissionsBits pos, bool value) { CheckLock(); SetBitInternal((byte)pos, value); }
|
||||
internal void SetBitInternal(int pos, bool value) => BitHelper.SetBit(ref _rawValue, pos, value);
|
||||
internal bool GetBit(PermissionsBits bit) => _rawValue.HasBit((byte)bit);
|
||||
internal void SetBit(PermissionsBits bit, bool value) { CheckLock(); SetBitInternal((byte)bit, value); }
|
||||
internal void SetBitInternal(int pos, bool value)
|
||||
{
|
||||
if (value)
|
||||
_rawValue |= (1U << pos);
|
||||
else
|
||||
_rawValue &= ~(1U << pos);
|
||||
}
|
||||
|
||||
internal void Lock() => _isLocked = true;
|
||||
protected void CheckLock()
|
||||
@@ -140,7 +146,7 @@ namespace Discord
|
||||
|
||||
public override bool Equals(object obj) => obj is Permissions && (obj as Permissions)._rawValue == _rawValue;
|
||||
public override int GetHashCode() => unchecked(_rawValue.GetHashCode() + 393);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class DualChannelPermissions
|
||||
{
|
||||
|
||||
29
src/Discord.Net/Models/Profile.cs
Normal file
29
src/Discord.Net/Models/Profile.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using Newtonsoft.Json;
|
||||
using APIUser = Discord.API.Client.User;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public sealed class Profile
|
||||
{
|
||||
/// <summary> Gets the unique identifier for this user. </summary>
|
||||
public ulong Id { get; private set; }
|
||||
/// <summary> Gets the email for this user. </summary>
|
||||
public string Email { get; private set; }
|
||||
/// <summary> Gets if the email for this user has been verified. </summary>
|
||||
public bool? IsVerified { get; private set; }
|
||||
|
||||
internal Profile() { }
|
||||
|
||||
internal void Update(APIUser model)
|
||||
{
|
||||
Id = model.Id;
|
||||
Email = model.Email;
|
||||
IsVerified = model.IsVerified;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
=> (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 string ToString() => Id.ToIdString();
|
||||
}
|
||||
}
|
||||
@@ -1,64 +1,58 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using APIRole = Discord.API.Client.Role;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public sealed class Role : CachedObject<ulong>
|
||||
{
|
||||
/// <summary> Returns the name of this role. </summary>
|
||||
public string Name { get; private set; }
|
||||
public sealed class Role
|
||||
{
|
||||
private readonly DiscordClient _client;
|
||||
|
||||
/// <summary> Gets the unique identifier for this role. </summary>
|
||||
public ulong Id { get; }
|
||||
/// <summary> Gets the server this role is a member of. </summary>
|
||||
public Server Server { get; }
|
||||
/// <summary> Gets the the permissions contained by this role. </summary>
|
||||
public ServerPermissions Permissions { get; }
|
||||
/// <summary> Gets the color of this role. </summary>
|
||||
public Color Color { get; }
|
||||
|
||||
/// <summary> Gets the name of this role. </summary>
|
||||
public string Name { get; private set; }
|
||||
/// <summary> If true, this role is displayed isolated from other users. </summary>
|
||||
public bool IsHoisted { get; private set; }
|
||||
/// <summary> Returns the position of this channel in the role list for this server. </summary>
|
||||
public int Position { get; private set; }
|
||||
/// <summary> Returns the color of this role. </summary>
|
||||
public Color Color { get; private set; }
|
||||
/// <summary> Returns whether this role is managed by server (e.g. for Twitch integration) </summary>
|
||||
public bool IsManaged { get; private set; }
|
||||
/// <summary> Gets the position of this channel relative to other channels in this server. </summary>
|
||||
public int Position { get; private set; }
|
||||
/// <summary> Gets whether this role is managed by server (e.g. for Twitch integration) </summary>
|
||||
public bool IsManaged { get; private set; }
|
||||
|
||||
/// <summary> Returns the the permissions contained by this role. </summary>
|
||||
public ServerPermissions Permissions { get; }
|
||||
/// <summary> Gets true if this is the role representing all users in a server. </summary>
|
||||
public bool IsEveryone => Id == Server.Id;
|
||||
/// <summary> Gets a list of all members in this role. </summary>
|
||||
public IEnumerable<User> Members => IsEveryone ? Server.Users : Server.Users.Where(x => x.HasRole(this));
|
||||
|
||||
/// <summary> Returns the server this role is a member of. </summary>
|
||||
[JsonIgnore]
|
||||
public Server Server => _server.Value;
|
||||
[JsonProperty]
|
||||
private ulong? ServerId { get { return _server.Id; } set { _server.Id = value; } }
|
||||
private readonly Reference<Server> _server;
|
||||
/// <summary> Gets the string used to mention this role. </summary>
|
||||
public string Mention
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsEveryone)
|
||||
return "@everyone";
|
||||
else
|
||||
throw new InvalidOperationException("Roles may only be mentioned if IsEveryone is true");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Returns true if this is the role representing all users in a server. </summary>
|
||||
public bool IsEveryone => _server.Id == null || Id == _server.Id;
|
||||
|
||||
/// <summary> Returns a list of all members in this role. </summary>
|
||||
[JsonIgnore]
|
||||
public IEnumerable<User> Members => _server.Id != null ? (IsEveryone ? Server.Members : Server.Members.Where(x => x.HasRole(this))) : new User[0];
|
||||
[JsonProperty]
|
||||
private IEnumerable<ulong> MemberIds => Members.Select(x => x.Id);
|
||||
//TODO: Add local members cache
|
||||
|
||||
/// <summary> Returns the string used to mention this role. </summary>
|
||||
public string Mention { get { if (IsEveryone) return "@everyone"; else throw new InvalidOperationException("Discord currently only supports mentioning the everyone role"); } }
|
||||
|
||||
internal Role(DiscordClient client, ulong id, ulong serverId)
|
||||
: base(client, id)
|
||||
internal Role(ulong id, Server server)
|
||||
{
|
||||
_server = new Reference<Server>(serverId, x => _client.Servers[x], x => x.AddRole(this), x => x.RemoveRole(this));
|
||||
Id = id;
|
||||
Server = server;
|
||||
Permissions = new ServerPermissions(0);
|
||||
Permissions.Lock();
|
||||
Color = new Color(0);
|
||||
Color.Lock();
|
||||
}
|
||||
internal override bool LoadReferences()
|
||||
{
|
||||
return _server.Load();
|
||||
}
|
||||
internal override void UnloadReferences()
|
||||
{
|
||||
_server.Unload();
|
||||
}
|
||||
|
||||
internal void Update(APIRole model)
|
||||
{
|
||||
@@ -81,6 +75,6 @@ namespace Discord
|
||||
|
||||
public override bool Equals(object obj) => obj is Role && (obj as Role).Id == Id;
|
||||
public override int GetHashCode() => unchecked(Id.GetHashCode() + 6653);
|
||||
public override string ToString() => Name ?? IdConvert.ToString(Id);
|
||||
public override string ToString() => Name ?? Id.ToIdString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,283 +1,229 @@
|
||||
using Discord.API.Client;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using APIGuild = Discord.API.Client.Guild;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public sealed class Server : CachedObject<ulong>
|
||||
{
|
||||
private struct ServerMember
|
||||
{
|
||||
public readonly User User;
|
||||
public readonly ServerPermissions Permissions;
|
||||
|
||||
public ServerMember(User user)
|
||||
{
|
||||
User = user;
|
||||
Permissions = new ServerPermissions();
|
||||
Permissions.Lock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Returns the name of this channel. </summary>
|
||||
public string Name { get; private set; }
|
||||
/// <summary> Returns the current logged-in user's data for this server. </summary>
|
||||
public User CurrentUser { get; internal set; }
|
||||
|
||||
/// <summary> Returns the amount of time (in seconds) a user must be inactive for until they are automatically moved to the AFK channel (see AFKChannel). </summary>
|
||||
public int AFKTimeout { get; private set; }
|
||||
/// <summary> Returns the date and time your joined this server. </summary>
|
||||
public DateTime JoinedAt { get; private set; }
|
||||
/// <summary> Returns the region for this server (see Regions). </summary>
|
||||
public string Region { get; private set; }
|
||||
/// <summary> Returns the unique identifier for this user's current avatar. </summary>
|
||||
public string IconId { get; private set; }
|
||||
/// <summary> Returns the URL to this user's current avatar. </summary>
|
||||
public string IconUrl => IconId != null ? $"{DiscordConfig.CDNUrl}/icons/{Id}/{IconId}.jpg" : null;
|
||||
|
||||
/// <summary> Returns the user that first created this server. </summary>
|
||||
[JsonIgnore]
|
||||
public User Owner => _owner.Value;
|
||||
[JsonProperty]
|
||||
internal ulong? OwnerId => _owner.Id;
|
||||
private Reference<User> _owner;
|
||||
|
||||
/// <summary> Returns the AFK voice channel for this server (see AFKTimeout). </summary>
|
||||
[JsonIgnore]
|
||||
public Channel AFKChannel => _afkChannel.Value;
|
||||
[JsonProperty]
|
||||
private ulong? AFKChannelId => _afkChannel.Id;
|
||||
private Reference<Channel> _afkChannel;
|
||||
|
||||
/// <summary> Returns the default channel for this server. </summary>
|
||||
[JsonIgnore]
|
||||
public Channel DefaultChannel { get; private set; }
|
||||
|
||||
/// <summary> Returns a collection of the ids of all users banned on this server. </summary>
|
||||
public IEnumerable<ulong> BannedUserIds => _bans.Select(x => x.Key);
|
||||
private ConcurrentDictionary<ulong, bool> _bans;
|
||||
|
||||
/// <summary> Returns a collection of all channels within this server. </summary>
|
||||
[JsonIgnore]
|
||||
public IEnumerable<Channel> Channels => _channels.Select(x => x.Value);
|
||||
/// <summary> Returns a collection of all text channels within this server. </summary>
|
||||
[JsonIgnore]
|
||||
public IEnumerable<Channel> TextChannels => _channels.Select(x => x.Value).Where(x => x.Type == ChannelType.Text);
|
||||
/// <summary> Returns a collection of all voice channels within this server. </summary>
|
||||
[JsonIgnore]
|
||||
public IEnumerable<Channel> VoiceChannels => _channels.Select(x => x.Value).Where(x => x.Type == ChannelType.Voice);
|
||||
[JsonProperty]
|
||||
private IEnumerable<ulong> ChannelIds => Channels.Select(x => x.Id);
|
||||
private ConcurrentDictionary<ulong, Channel> _channels;
|
||||
|
||||
/// <summary> Returns a collection of all users within this server with their server-specific data. </summary>
|
||||
[JsonIgnore]
|
||||
public IEnumerable<User> Members => _members.Select(x => x.Value.User);
|
||||
[JsonProperty]
|
||||
private IEnumerable<ulong> MemberIds => Members.Select(x => x.Id);
|
||||
private ConcurrentDictionary<ulong, ServerMember> _members;
|
||||
|
||||
/// <summary> Return the the role representing all users in a server. </summary>
|
||||
[JsonIgnore]
|
||||
public Role EveryoneRole { get; private set; }
|
||||
/// <summary> Returns a collection of all roles within this server. </summary>
|
||||
[JsonIgnore]
|
||||
public IEnumerable<Role> Roles => _roles.Select(x => x.Value);
|
||||
[JsonProperty]
|
||||
private IEnumerable<ulong> RoleIds => Roles.Select(x => x.Id);
|
||||
private ConcurrentDictionary<ulong, Role> _roles;
|
||||
|
||||
internal Server(DiscordClient client, ulong id)
|
||||
: base(client, id)
|
||||
{
|
||||
_owner = new Reference<User>(x => _client.Users[x, Id]);
|
||||
_afkChannel = new Reference<Channel>(x => _client.Channels[x]);
|
||||
|
||||
//Global Cache
|
||||
_channels = new ConcurrentDictionary<ulong, Channel>();
|
||||
_roles = new ConcurrentDictionary<ulong, Role>();
|
||||
_members = new ConcurrentDictionary<ulong, ServerMember>();
|
||||
|
||||
//Local Cache
|
||||
_bans = new ConcurrentDictionary<ulong, bool>();
|
||||
EveryoneRole = _client.Roles.GetOrAdd(id, id);
|
||||
}
|
||||
internal override bool LoadReferences()
|
||||
{
|
||||
_afkChannel.Load();
|
||||
_owner.Load();
|
||||
return true;
|
||||
}
|
||||
internal override void UnloadReferences()
|
||||
{
|
||||
//Global Cache
|
||||
var globalChannels = _client.Channels;
|
||||
var channels = _channels;
|
||||
foreach (var channel in channels)
|
||||
globalChannels.TryRemove(channel.Key);
|
||||
channels.Clear();
|
||||
|
||||
var globalUsers = _client.Users;
|
||||
var members = _members;
|
||||
foreach (var member in members)
|
||||
globalUsers.TryRemove(member.Key, Id);
|
||||
members.Clear();
|
||||
|
||||
var globalRoles = _client.Roles;
|
||||
var roles = _roles;
|
||||
foreach (var role in roles)
|
||||
globalRoles.TryRemove(role.Key);
|
||||
roles.Clear();
|
||||
|
||||
//Local Cache
|
||||
_bans.Clear();
|
||||
|
||||
_afkChannel.Unload();
|
||||
}
|
||||
|
||||
internal void Update(GuildReference model)
|
||||
{
|
||||
if (model.Name != null)
|
||||
Name = model.Name;
|
||||
}
|
||||
|
||||
internal void Update(Guild model)
|
||||
{
|
||||
Update(model as GuildReference);
|
||||
|
||||
if (model.AFKTimeout != null)
|
||||
AFKTimeout = model.AFKTimeout.Value;
|
||||
if (model.AFKChannelId != null)
|
||||
if (model.JoinedAt != null)
|
||||
JoinedAt = model.JoinedAt.Value;
|
||||
if (model.OwnerId != null)
|
||||
_owner.Id = model.OwnerId.Value;
|
||||
if (model.Region != null)
|
||||
Region = model.Region;
|
||||
if (model.Icon != null)
|
||||
IconId = model.Icon;
|
||||
|
||||
if (model.Roles != null)
|
||||
{
|
||||
var roleCache = _client.Roles;
|
||||
foreach (var x in model.Roles)
|
||||
{
|
||||
var role = roleCache.GetOrAdd(x.Id, Id);
|
||||
role.Update(x);
|
||||
}
|
||||
/// <summary> Represents a Discord server (also known as a guild). </summary>
|
||||
public sealed class Server
|
||||
{
|
||||
private struct Member
|
||||
{
|
||||
public readonly User User;
|
||||
public readonly ServerPermissions Permissions;
|
||||
public Member(User user)
|
||||
{
|
||||
User = user;
|
||||
Permissions = new ServerPermissions();
|
||||
Permissions.Lock();
|
||||
}
|
||||
|
||||
_afkChannel.Id = model.AFKChannelId; //Can be null
|
||||
}
|
||||
internal void Update(ExtendedGuild model)
|
||||
{
|
||||
Update(model as APIGuild);
|
||||
|
||||
var channels = _client.Channels;
|
||||
foreach (var subModel in model.Channels)
|
||||
{
|
||||
var channel = channels.GetOrAdd(subModel.Id, Id);
|
||||
channel.Update(subModel);
|
||||
}
|
||||
|
||||
var usersCache = _client.Users;
|
||||
foreach (var subModel in model.Members)
|
||||
{
|
||||
var user = usersCache.GetOrAdd(subModel.User.Id, Id);
|
||||
user.Update(subModel);
|
||||
}
|
||||
foreach (var subModel in model.VoiceStates)
|
||||
{
|
||||
var user = usersCache[subModel.UserId, Id];
|
||||
if (user != null)
|
||||
user.Update(subModel);
|
||||
}
|
||||
foreach (var subModel in model.Presences)
|
||||
{
|
||||
var user = usersCache[subModel.User.Id, Id];
|
||||
if (user != null)
|
||||
user.Update(subModel);
|
||||
}
|
||||
}
|
||||
|
||||
internal void AddBan(ulong banId)
|
||||
{
|
||||
_bans.TryAdd(banId, true);
|
||||
}
|
||||
internal bool RemoveBan(ulong banId)
|
||||
{
|
||||
bool ignored;
|
||||
return _bans.TryRemove(banId, out ignored);
|
||||
}
|
||||
|
||||
internal void AddChannel(Channel channel)
|
||||
{
|
||||
if (_channels.TryAdd(channel.Id, channel))
|
||||
{
|
||||
if (channel.Id == Id)
|
||||
DefaultChannel = channel;
|
||||
}
|
||||
}
|
||||
internal void RemoveChannel(Channel channel)
|
||||
{
|
||||
_channels.TryRemove(channel.Id, out channel);
|
||||
}
|
||||
|
||||
internal void AddMember(User user)
|
||||
{
|
||||
if (_members.TryAdd(user.Id, new ServerMember(user)))
|
||||
{
|
||||
foreach (var channel in Channels)
|
||||
channel.AddMember(user);
|
||||
}
|
||||
}
|
||||
internal void RemoveMember(User user)
|
||||
{
|
||||
ServerMember ignored;
|
||||
if (_members.TryRemove(user.Id, out ignored))
|
||||
|
||||
private readonly ConcurrentDictionary<ulong, Role> _roles;
|
||||
private readonly ConcurrentDictionary<ulong, Member> _users;
|
||||
private readonly ConcurrentDictionary<ulong, Channel> _channels;
|
||||
private readonly ConcurrentDictionary<ulong, bool> _bans;
|
||||
private ulong _ownerId;
|
||||
private ulong? _afkChannelId;
|
||||
|
||||
/// <summary> Gets the client that generated this server object. </summary>
|
||||
internal DiscordClient Client { get; }
|
||||
/// <summary> Gets the unique identifier for this server. </summary>
|
||||
public ulong Id { get; }
|
||||
/// <summary> Gets the default channel for this server. </summary>
|
||||
public Channel DefaultChannel { get; }
|
||||
/// <summary> Gets the the role representing all users in a server. </summary>
|
||||
public Role EveryoneRole { get; }
|
||||
|
||||
/// <summary> Gets the name of this server. </summary>
|
||||
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>
|
||||
public Region Region { get; private set; }
|
||||
/// <summary> Gets the unique identifier for this user's current avatar. </summary>
|
||||
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 user that created this server. </summary>
|
||||
public User Owner => GetUser(_ownerId);
|
||||
/// <summary> Gets the AFK voice channel for this server. </summary>
|
||||
public Channel AFKChannel => _afkChannelId != null ? GetChannel(_afkChannelId.Value) : null;
|
||||
/// <summary> Gets the current user in this server. </summary>
|
||||
public User CurrentUser => GetUser(Client.CurrentUser.Id);
|
||||
|
||||
/// <summary> Gets a collection of the ids of all users banned on this server. </summary>
|
||||
public IEnumerable<ulong> BannedUserIds => _bans.Select(x => x.Key);
|
||||
/// <summary> Gets a collection of all channels within this server. </summary>
|
||||
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>
|
||||
public IEnumerable<User> Users => _users.Select(x => x.Value.User);
|
||||
/// <summary> Gets a collection of all roles within this server. </summary>
|
||||
public IEnumerable<Role> Roles => _roles.Select(x => x.Value);
|
||||
|
||||
internal Server(DiscordClient client, ulong id)
|
||||
{
|
||||
Client = client;
|
||||
Id = id;
|
||||
_channels = new ConcurrentDictionary<ulong, Channel>();
|
||||
_roles = new ConcurrentDictionary<ulong, Role>();
|
||||
_users = new ConcurrentDictionary<ulong, Member>();
|
||||
_bans = new ConcurrentDictionary<ulong, bool>();
|
||||
DefaultChannel = AddChannel(id);
|
||||
EveryoneRole = AddRole(id);
|
||||
}
|
||||
|
||||
internal void Update(GuildReference model)
|
||||
{
|
||||
if (model.Name != null)
|
||||
Name = model.Name;
|
||||
}
|
||||
internal void Update(Guild model)
|
||||
{
|
||||
Update(model as GuildReference);
|
||||
|
||||
if (model.AFKTimeout != null)
|
||||
AFKTimeout = model.AFKTimeout.Value;
|
||||
_afkChannelId = model.AFKChannelId.Value; //Can be null
|
||||
if (model.JoinedAt != null)
|
||||
JoinedAt = model.JoinedAt.Value;
|
||||
if (model.OwnerId != null)
|
||||
_ownerId = model.OwnerId.Value;
|
||||
if (model.Region != null)
|
||||
Region = Client.GetRegion(model.Region);
|
||||
if (model.Icon != null)
|
||||
IconId = model.Icon;
|
||||
|
||||
if (model.Roles != null)
|
||||
{
|
||||
foreach (var x in model.Roles)
|
||||
AddRole(x.Id).Update(x);
|
||||
}
|
||||
}
|
||||
internal void Update(ExtendedGuild model)
|
||||
{
|
||||
Update(model as Guild);
|
||||
|
||||
if (model.Channels != null)
|
||||
{
|
||||
foreach (var subModel in model.Channels)
|
||||
AddChannel(subModel.Id).Update(subModel);
|
||||
}
|
||||
if (model.Members != null)
|
||||
{
|
||||
foreach (var subModel in model.Members)
|
||||
AddMember(subModel.User.Id).Update(subModel);
|
||||
}
|
||||
if (model.VoiceStates != null)
|
||||
{
|
||||
foreach (var subModel in model.VoiceStates)
|
||||
GetUser(subModel.UserId)?.Update(subModel);
|
||||
}
|
||||
if (model.Presences != null)
|
||||
{
|
||||
foreach (var subModel in model.Presences)
|
||||
GetUser(subModel.User.Id)?.Update(subModel);
|
||||
}
|
||||
}
|
||||
|
||||
//Bans
|
||||
internal void AddBan(ulong banId)
|
||||
=> _bans.TryAdd(banId, true);
|
||||
internal bool RemoveBan(ulong banId)
|
||||
{
|
||||
bool ignored;
|
||||
return _bans.TryRemove(banId, out ignored);
|
||||
}
|
||||
|
||||
//Channels
|
||||
internal Channel AddChannel(ulong id)
|
||||
=> _channels.GetOrAdd(id, x => new Channel(Client, x, this));
|
||||
internal Channel RemoveChannel(ulong id)
|
||||
{
|
||||
Channel channel;
|
||||
_channels.TryRemove(id, out channel);
|
||||
return channel;
|
||||
}
|
||||
public Channel GetChannel(ulong id)
|
||||
{
|
||||
Channel result;
|
||||
_channels.TryGetValue(id, out result);
|
||||
return result;
|
||||
}
|
||||
|
||||
//Members
|
||||
internal User AddMember(ulong id)
|
||||
{
|
||||
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.RemoveMember(user);
|
||||
channel.AddUser(newUser);
|
||||
}
|
||||
}
|
||||
internal void HasMember(User user) => _members.ContainsKey(user.Id);
|
||||
|
||||
internal void AddRole(Role role)
|
||||
return user.User;
|
||||
}
|
||||
internal User RemoveMember(ulong id)
|
||||
{
|
||||
if (_roles.TryAdd(role.Id, role))
|
||||
Member member;
|
||||
if (_users.TryRemove(id, out member))
|
||||
{
|
||||
if (role.Id == Id)
|
||||
EveryoneRole = role;
|
||||
foreach (var channel in Channels)
|
||||
channel.RemoveUser(id);
|
||||
}
|
||||
}
|
||||
internal void RemoveRole(Role role)
|
||||
{
|
||||
_roles.TryRemove(role.Id, out role);
|
||||
}
|
||||
return member.User;
|
||||
}
|
||||
public User GetUser(ulong id)
|
||||
{
|
||||
Member result;
|
||||
_users.TryGetValue(id, out result);
|
||||
return result.User;
|
||||
}
|
||||
|
||||
internal ServerPermissions GetPermissions(User user)
|
||||
//Roles
|
||||
internal Role AddRole(ulong id)
|
||||
=> _roles.GetOrAdd(id, x => new Role(x, this));
|
||||
internal Role RemoveRole(ulong id)
|
||||
{
|
||||
Role role;
|
||||
_roles.TryRemove(id, out role);
|
||||
return role;
|
||||
}
|
||||
public Role GetRole(ulong id)
|
||||
{
|
||||
Role result;
|
||||
_roles.TryGetValue(id, out result);
|
||||
return result;
|
||||
}
|
||||
|
||||
//Permissions
|
||||
internal ServerPermissions GetPermissions(User user)
|
||||
{
|
||||
ServerMember member;
|
||||
if (_members.TryGetValue(user.Id, out member))
|
||||
Member member;
|
||||
if (_users.TryGetValue(user.Id, out member))
|
||||
return member.Permissions;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
internal void UpdatePermissions(User user)
|
||||
{
|
||||
ServerMember member;
|
||||
if (_members.TryGetValue(user.Id, out member))
|
||||
Member member;
|
||||
if (_users.TryGetValue(user.Id, out member))
|
||||
UpdatePermissions(member.User, member.Permissions);
|
||||
}
|
||||
private void UpdatePermissions(User user, ServerPermissions permissions)
|
||||
{
|
||||
uint newPermissions = 0;
|
||||
|
||||
if (user.IsOwner)
|
||||
if (user.Id == _ownerId)
|
||||
newPermissions = ServerPermissions.All.RawValue;
|
||||
else
|
||||
{
|
||||
@@ -285,7 +231,7 @@ namespace Discord
|
||||
newPermissions |= serverRole.Permissions.RawValue;
|
||||
}
|
||||
|
||||
if (BitHelper.GetBit(newPermissions, (int)PermissionsBits.ManageRolesOrPermissions))
|
||||
if (newPermissions.HasBit((byte)PermissionsBits.ManageRolesOrPermissions))
|
||||
newPermissions = ServerPermissions.All.RawValue;
|
||||
|
||||
if (newPermissions != permissions.RawValue)
|
||||
@@ -298,6 +244,6 @@ namespace Discord
|
||||
|
||||
public override bool Equals(object obj) => obj is Server && (obj as Server).Id == Id;
|
||||
public override int GetHashCode() => unchecked(Id.GetHashCode() + 5175);
|
||||
public override string ToString() => Name ?? IdConvert.ToString(Id);
|
||||
public override string ToString() => Name ?? Id.ToIdString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Discord.API.Client;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -7,8 +6,19 @@ using APIMember = Discord.API.Client.Member;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public class User : CachedObject<ulong>
|
||||
public class User
|
||||
{
|
||||
[Flags]
|
||||
private enum VoiceState : byte
|
||||
{
|
||||
None = 0x0,
|
||||
SelfMuted = 0x01,
|
||||
SelfDeafened = 0x02,
|
||||
ServerMuted = 0x04,
|
||||
ServerDeafened = 0x08,
|
||||
ServerSuppressed = 0x10,
|
||||
}
|
||||
|
||||
internal struct CompositeKey : IEquatable<CompositeKey>
|
||||
{
|
||||
public ulong ServerId, UserId;
|
||||
@@ -24,92 +34,71 @@ namespace Discord
|
||||
=> unchecked(ServerId.GetHashCode() + UserId.GetHashCode() + 23);
|
||||
}
|
||||
|
||||
public static string GetAvatarUrl(ulong userId, string avatarId) => avatarId != null ? $"{DiscordConfig.CDNUrl}/avatars/{userId}/{avatarId}.jpg" : null;
|
||||
internal static string GetAvatarUrl(ulong userId, string avatarId) => avatarId != null ? $"{DiscordConfig.CDNUrl}/avatars/{userId}/{avatarId}.jpg" : null;
|
||||
|
||||
/// <summary> Returns a unique identifier combining this user's id with its server's. </summary>
|
||||
internal CompositeKey UniqueId => new CompositeKey(_server.Id ?? 0, Id);
|
||||
/// <summary> Returns the name of this user on this server. </summary>
|
||||
public string Name { get; private set; }
|
||||
/// <summary> Returns a by-name unique identifier separating this user from others with the same name. </summary>
|
||||
public ushort Discriminator { get; private set; }
|
||||
/// <summary> Returns the unique identifier for this user's current avatar. </summary>
|
||||
public string AvatarId { get; private set; }
|
||||
/// <summary> Returns the URL to this user's current avatar. </summary>
|
||||
public string AvatarUrl => GetAvatarUrl(Id, AvatarId);
|
||||
/// <summary> Returns the datetime that this user joined this server. </summary>
|
||||
private VoiceState _voiceState;
|
||||
private DateTime? _lastOnline;
|
||||
private ulong? _voiceChannelId;
|
||||
private Dictionary<ulong, Role> _roles;
|
||||
|
||||
/// <summary> Gets the client that generated this user object. </summary>
|
||||
internal DiscordClient Client { get; }
|
||||
/// <summary> Gets the unique identifier for this user. </summary>
|
||||
public ulong Id { get; }
|
||||
/// <summary> Gets the server this user is a member of. </summary>
|
||||
public Server Server { get; }
|
||||
|
||||
/// <summary> Gets the name of this user. </summary>
|
||||
public string Name { get; private set; }
|
||||
/// <summary> Gets an id uniquely identifying from others with the same name. </summary>
|
||||
public ushort Discriminator { get; private set; }
|
||||
/// <summary> Gets the unique identifier for this user's current avatar. </summary>
|
||||
public string AvatarId { get; private set; }
|
||||
/// <summary> Gets the id for the game this user is currently playing. </summary>
|
||||
public string GameId { get; private set; }
|
||||
/// <summary> Gets the current status for this user. </summary>
|
||||
public UserStatus Status { get; private set; }
|
||||
/// <summary> Gets the datetime that this user joined this server. </summary>
|
||||
public DateTime JoinedAt { get; private set; }
|
||||
/// <summary> Returns the time this user last sent/edited a message, started typing or sent voice data in this server. </summary>
|
||||
public DateTime? LastActivityAt { get; private set; }
|
||||
// /// <summary> Gets this user's voice session id. </summary>
|
||||
// public string SessionId { get; private set; }
|
||||
// /// <summary> Gets this user's voice token. </summary>
|
||||
// public string Token { get; private set; }
|
||||
|
||||
|
||||
public bool IsSelfMuted { get; private set; }
|
||||
public bool IsSelfDeafened { get; private set; }
|
||||
public bool IsServerMuted { get; private set; }
|
||||
public bool IsServerDeafened { get; private set; }
|
||||
public bool IsServerSuppressed { get; private set; }
|
||||
public bool IsPrivate => _server.Id == null;
|
||||
public bool IsOwner => _server.Value.OwnerId == Id;
|
||||
|
||||
public string SessionId { get; private set; }
|
||||
public string Token { get; private set; }
|
||||
|
||||
/// <summary> Returns the id for the game this user is currently playing. </summary>
|
||||
public int? GameId { get; private set; }
|
||||
/// <summary> Returns the current status for this user. </summary>
|
||||
public UserStatus Status { get; private set; }
|
||||
/// <summary> Returns the time this user last sent/edited a message, started typing or sent voice data in this server. </summary>
|
||||
public DateTime? LastActivityAt { get; private set; }
|
||||
/// <summary> Returns the string used to mention this user. </summary>
|
||||
public string Mention => $"<@{Id}>";
|
||||
/// <summary> Returns true if this user has marked themselves as muted. </summary>
|
||||
public bool IsSelfMuted => (_voiceState & VoiceState.SelfMuted) != 0;
|
||||
/// <summary> Returns true if this user has marked themselves as deafened. </summary>
|
||||
public bool IsSelfDeafened => (_voiceState & VoiceState.SelfDeafened) != 0;
|
||||
/// <summary> Returns true if the server is blocking audio from this user. </summary>
|
||||
public bool IsServerMuted => (_voiceState & VoiceState.ServerMuted) != 0;
|
||||
/// <summary> Returns true if the server is blocking audio to this user. </summary>
|
||||
public bool IsServerDeafened => (_voiceState & VoiceState.ServerDeafened) != 0;
|
||||
/// <summary> Returns true if the server is temporarily blocking audio to/from this user. </summary>
|
||||
public bool IsServerSuppressed => (_voiceState & VoiceState.ServerSuppressed) != 0;
|
||||
/// <summary> Returns the time this user was last seen online in this server. </summary>
|
||||
public DateTime? LastOnlineAt => Status != UserStatus.Offline ? DateTime.UtcNow : _lastOnline;
|
||||
private DateTime? _lastOnline;
|
||||
public DateTime? LastOnlineAt => Status != UserStatus.Offline ? DateTime.UtcNow : _lastOnline;
|
||||
/// <summary> Gets this user's </summary>
|
||||
public Channel VoiceChannel => _voiceChannelId != null ? Server.GetChannel(_voiceChannelId.Value) : null;
|
||||
/// <summary> Gets the URL to this user's current avatar. </summary>
|
||||
public string AvatarUrl => GetAvatarUrl(Id, AvatarId);
|
||||
/// <summary> Gets all roles that have been assigned to this user, including the everyone role. </summary>
|
||||
public IEnumerable<Role> Roles => _roles.Select(x => x.Value);
|
||||
|
||||
//References
|
||||
[JsonIgnore]
|
||||
public GlobalUser Global => _globalUser.Value;
|
||||
private readonly Reference<GlobalUser> _globalUser;
|
||||
|
||||
[JsonIgnore]
|
||||
public Server Server => _server.Value;
|
||||
private readonly Reference<Server> _server;
|
||||
[JsonProperty]
|
||||
private ulong? ServerId { get { return _server.Id; } set { _server.Id = value; } }
|
||||
|
||||
[JsonIgnore]
|
||||
public Channel VoiceChannel => _voiceChannel.Value;
|
||||
private Reference<Channel> _voiceChannel;
|
||||
[JsonProperty]
|
||||
private ulong? VoiceChannelId { get { return _voiceChannel.Id; } set { _voiceChannel.Id = value; } }
|
||||
|
||||
//Collections
|
||||
[JsonIgnore]
|
||||
public IEnumerable<Role> Roles => _roles.Select(x => x.Value);
|
||||
private Dictionary<ulong, Role> _roles;
|
||||
[JsonProperty]
|
||||
private IEnumerable<ulong> RoleIds => _roles.Select(x => x.Key);
|
||||
|
||||
/// <summary> Returns a collection of all messages this user has sent on this server that are still in cache. </summary>
|
||||
[JsonIgnore]
|
||||
public IEnumerable<Message> Messages
|
||||
/// <summary> Returns a collection of all channels this user has permissions to join on this server. </summary>
|
||||
public IEnumerable<Channel> Channels
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_server.Id != null)
|
||||
return Server.Channels.SelectMany(x => x.Messages.Where(y => y.User.Id == Id));
|
||||
else
|
||||
return Global.PrivateChannel.Messages.Where(x => x.User.Id == Id);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Returns a collection of all channels this user has permissions to join on this server. </summary>
|
||||
[JsonIgnore]
|
||||
public IEnumerable<Channel> Channels
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_server.Id != null)
|
||||
if (Server != null)
|
||||
{
|
||||
if (_client.Config.UsePermissionsCache)
|
||||
if (Client.Config.UsePermissionsCache)
|
||||
{
|
||||
return Server.Channels
|
||||
.Where(x => (x.Type == ChannelType.Text && x.GetPermissions(this).ReadMessages) ||
|
||||
return Server.Channels.Where(x =>
|
||||
(x.Type == ChannelType.Text && x.GetPermissions(this).ReadMessages) ||
|
||||
(x.Type == ChannelType.Voice && x.GetPermissions(this).Connect));
|
||||
}
|
||||
else
|
||||
@@ -120,63 +109,37 @@ namespace Discord
|
||||
{
|
||||
x.UpdatePermissions(this, perms);
|
||||
return (x.Type == ChannelType.Text && perms.ReadMessages) ||
|
||||
(x.Type == ChannelType.Voice && perms.Connect);
|
||||
(x.Type == ChannelType.Voice && perms.Connect);
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var privateChannel = Global.PrivateChannel;
|
||||
if (privateChannel != null)
|
||||
return new Channel[] { privateChannel };
|
||||
else
|
||||
return new Channel[0];
|
||||
if (this == Client.PrivateUser)
|
||||
return Client.PrivateChannels;
|
||||
else
|
||||
{
|
||||
var privateChannel = Client.GetPrivateChannel(Id);
|
||||
if (privateChannel != null)
|
||||
return new Channel[] { privateChannel };
|
||||
else
|
||||
return new Channel[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Returns the string used to mention this user. </summary>
|
||||
public string Mention => $"<@{Id}>";
|
||||
|
||||
internal User(DiscordClient client, ulong id, ulong? serverId)
|
||||
: base(client, id)
|
||||
internal User(ulong id, Server server)
|
||||
{
|
||||
_globalUser = new Reference<GlobalUser>(id,
|
||||
x => _client.GlobalUsers.GetOrAdd(x),
|
||||
x => x.AddUser(this),
|
||||
x => x.RemoveUser(this));
|
||||
_server = new Reference<Server>(serverId,
|
||||
x => _client.Servers[x],
|
||||
x =>
|
||||
{
|
||||
x.AddMember(this);
|
||||
if (Id == _client.CurrentUser.Id)
|
||||
x.CurrentUser = this;
|
||||
},
|
||||
x =>
|
||||
{
|
||||
x.RemoveMember(this);
|
||||
if (Id == _client.CurrentUser.Id)
|
||||
x.CurrentUser = null;
|
||||
});
|
||||
_voiceChannel = new Reference<Channel>(x => _client.Channels[x]);
|
||||
Server = server;
|
||||
_roles = new Dictionary<ulong, Role>();
|
||||
|
||||
Status = UserStatus.Offline;
|
||||
|
||||
if (serverId == null)
|
||||
if (server == null)
|
||||
UpdateRoles(null);
|
||||
}
|
||||
internal override bool LoadReferences()
|
||||
{
|
||||
return _globalUser.Load() &&
|
||||
(IsPrivate || _server.Load());
|
||||
}
|
||||
internal override void UnloadReferences()
|
||||
{
|
||||
_globalUser.Unload();
|
||||
_server.Unload();
|
||||
}
|
||||
|
||||
internal void Update(UserReference model)
|
||||
{
|
||||
@@ -195,24 +158,29 @@ namespace Discord
|
||||
if (model.JoinedAt.HasValue)
|
||||
JoinedAt = model.JoinedAt.Value;
|
||||
if (model.Roles != null)
|
||||
UpdateRoles(model.Roles.Select(x => _client.Roles[x]));
|
||||
UpdateRoles(model.Roles.Select(x => Server.GetRole(x)));
|
||||
}
|
||||
internal void Update(ExtendedGuild.ExtendedMemberInfo model)
|
||||
{
|
||||
Update(model as APIMember);
|
||||
|
||||
if (model.IsServerMuted == true)
|
||||
_voiceState |= VoiceState.ServerMuted;
|
||||
else if (model.IsServerMuted == false)
|
||||
_voiceState &= ~VoiceState.ServerMuted;
|
||||
|
||||
if (model.IsServerDeafened != null)
|
||||
IsServerDeafened = model.IsServerDeafened.Value;
|
||||
if (model.IsServerMuted != null)
|
||||
IsServerMuted = model.IsServerMuted.Value;
|
||||
}
|
||||
if (model.IsServerDeafened.Value == true)
|
||||
_voiceState |= VoiceState.ServerDeafened;
|
||||
else if (model.IsServerDeafened.Value == false)
|
||||
_voiceState &= ~VoiceState.ServerDeafened;
|
||||
}
|
||||
internal void Update(MemberPresence model)
|
||||
{
|
||||
if (model.User != null)
|
||||
Update(model.User as UserReference);
|
||||
|
||||
if (model.Roles != null)
|
||||
UpdateRoles(model.Roles.Select(x => _client.Roles[x]));
|
||||
if (model.Roles != null)
|
||||
UpdateRoles(model.Roles.Select(x => Server.GetRole(x)));
|
||||
if (model.Status != null && Status != model.Status)
|
||||
{
|
||||
Status = UserStatus.FromString(model.Status);
|
||||
@@ -223,42 +191,55 @@ namespace Discord
|
||||
GameId = model.GameId; //Allows null
|
||||
}
|
||||
internal void Update(MemberVoiceState model)
|
||||
{
|
||||
if (model.IsServerDeafened != null)
|
||||
IsServerDeafened = model.IsServerDeafened.Value;
|
||||
if (model.IsServerMuted != null)
|
||||
IsServerMuted = model.IsServerMuted.Value;
|
||||
if (model.SessionId != null)
|
||||
{
|
||||
if (model.IsSelfMuted.Value == true)
|
||||
_voiceState |= VoiceState.SelfMuted;
|
||||
else if (model.IsSelfMuted.Value == false)
|
||||
_voiceState &= ~VoiceState.SelfMuted;
|
||||
if (model.IsSelfDeafened.Value == true)
|
||||
_voiceState |= VoiceState.SelfDeafened;
|
||||
else if (model.IsSelfDeafened.Value == false)
|
||||
_voiceState &= ~VoiceState.SelfDeafened;
|
||||
if (model.IsServerMuted == true)
|
||||
_voiceState |= VoiceState.ServerMuted;
|
||||
else if (model.IsServerMuted == false)
|
||||
_voiceState &= ~VoiceState.ServerMuted;
|
||||
if (model.IsServerDeafened.Value == true)
|
||||
_voiceState |= VoiceState.ServerDeafened;
|
||||
else if (model.IsServerDeafened.Value == false)
|
||||
_voiceState &= ~VoiceState.ServerDeafened;
|
||||
if (model.IsServerSuppressed.Value == true)
|
||||
_voiceState |= VoiceState.ServerSuppressed;
|
||||
else if (model.IsServerSuppressed.Value == false)
|
||||
_voiceState &= ~VoiceState.ServerSuppressed;
|
||||
|
||||
/*if (model.SessionId != null)
|
||||
SessionId = model.SessionId;
|
||||
if (model.Token != null)
|
||||
Token = model.Token;
|
||||
Token = model.Token;*/
|
||||
|
||||
if (model.IsSelfDeafened != null)
|
||||
IsSelfDeafened = model.IsSelfDeafened.Value;
|
||||
if (model.IsSelfMuted != null)
|
||||
IsSelfMuted = model.IsSelfMuted.Value;
|
||||
if (model.IsServerSuppressed != null)
|
||||
IsServerSuppressed = model.IsServerSuppressed.Value;
|
||||
|
||||
_voiceChannel.Id = 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)
|
||||
newRoles[r.Id] = r;
|
||||
foreach (var r in roles)
|
||||
{
|
||||
if (r != null)
|
||||
newRoles[r.Id] = r;
|
||||
}
|
||||
}
|
||||
|
||||
if (_server.Id != null)
|
||||
if (Server != null)
|
||||
{
|
||||
var everyone = Server.EveryoneRole;
|
||||
newRoles.Add(everyone.Id, everyone);
|
||||
newRoles[everyone.Id] = everyone;
|
||||
}
|
||||
_roles = newRoles;
|
||||
|
||||
if (!IsPrivate)
|
||||
if (Server != null)
|
||||
Server.UpdatePermissions(this);
|
||||
}
|
||||
|
||||
@@ -285,6 +266,6 @@ namespace Discord
|
||||
|
||||
public override bool Equals(object obj) => obj is User && (obj as User).Id == Id;
|
||||
public override int GetHashCode() => unchecked(Id.GetHashCode() + 7230);
|
||||
public override string ToString() => Name != null ? $"{Name}#{Discriminator}" : IdConvert.ToString(Id);
|
||||
public override string ToString() => Name != null ? $"{Name}#{Discriminator}" : Id.ToIdString();
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using System.Runtime.Serialization;
|
||||
|
||||
namespace Discord.Net
|
||||
{
|
||||
#if NET45
|
||||
#if NET46
|
||||
[Serializable]
|
||||
#endif
|
||||
public class HttpException : Exception
|
||||
@@ -16,7 +16,7 @@ namespace Discord.Net
|
||||
{
|
||||
StatusCode = statusCode;
|
||||
}
|
||||
#if NET45
|
||||
#if NET46
|
||||
public override void GetObjectData(SerializationInfo info, StreamingContext context)
|
||||
=> base.GetObjectData(info, context);
|
||||
#endif
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Discord.Net.Rest
|
||||
{
|
||||
public sealed partial class RestClient
|
||||
{
|
||||
public class RequestEventArgs : EventArgs
|
||||
{
|
||||
public string Method { get; }
|
||||
public string Path { get; }
|
||||
public string Payload { get; }
|
||||
public double ElapsedMilliseconds { get; }
|
||||
public RequestEventArgs(string method, string path, string payload, double milliseconds)
|
||||
{
|
||||
Method = method;
|
||||
Path = path;
|
||||
Payload = payload;
|
||||
ElapsedMilliseconds = milliseconds;
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler<RequestEventArgs> OnRequest;
|
||||
private void RaiseOnRequest(string method, string path, string payload, double milliseconds)
|
||||
{
|
||||
if (OnRequest != null)
|
||||
OnRequest(this, new RequestEventArgs(method, path, payload, milliseconds));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Discord.API;
|
||||
using Discord.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
@@ -7,24 +8,52 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Discord.Net.Rest
|
||||
{
|
||||
public sealed partial class RestClient
|
||||
public class RequestEventArgs : EventArgs
|
||||
{
|
||||
public string Method { get; }
|
||||
public string Path { get; }
|
||||
public string Payload { get; }
|
||||
public double ElapsedMilliseconds { get; }
|
||||
public RequestEventArgs(string method, string path, string payload, double milliseconds)
|
||||
{
|
||||
Method = method;
|
||||
Path = path;
|
||||
Payload = payload;
|
||||
ElapsedMilliseconds = milliseconds;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed partial class RestClient
|
||||
{
|
||||
private readonly DiscordConfig _config;
|
||||
private readonly IRestEngine _engine;
|
||||
private CancellationToken _cancelToken;
|
||||
private string _token;
|
||||
|
||||
public RestClient(DiscordConfig config, Logger logger, string baseUrl)
|
||||
{
|
||||
_config = config;
|
||||
#if !DOTNET5_4
|
||||
_engine = new RestSharpEngine(config, logger, baseUrl);
|
||||
#else
|
||||
//_engine = new BuiltInRestEngine(config, logger, baseUrl);
|
||||
#endif
|
||||
internal Logger Logger { get; }
|
||||
|
||||
public CancellationToken CancelToken { get; set; }
|
||||
|
||||
public string Token
|
||||
{
|
||||
get { return _token; }
|
||||
set
|
||||
{
|
||||
_token = value;
|
||||
_engine.SetToken(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetToken(string token) => _engine.SetToken(token);
|
||||
public void SetCancelToken(CancellationToken token) => _cancelToken = token;
|
||||
public RestClient(DiscordConfig config, string baseUrl, Logger logger)
|
||||
{
|
||||
_config = config;
|
||||
Logger = logger;
|
||||
|
||||
#if !DOTNET5_4
|
||||
_engine = new RestSharpEngine(config, baseUrl);
|
||||
#else
|
||||
//_engine = new BuiltInRestEngine(config, baseUrl);
|
||||
#endif
|
||||
}
|
||||
|
||||
public async Task<ResponseT> Send<ResponseT>(IRestRequest<ResponseT> request)
|
||||
where ResponseT : class
|
||||
@@ -69,24 +98,26 @@ namespace Discord.Net.Rest
|
||||
requestJson = JsonConvert.SerializeObject(payload);
|
||||
|
||||
Stopwatch stopwatch = null;
|
||||
if (_config.LogLevel >= LogSeverity.Verbose)
|
||||
if (Logger.Level >= LogSeverity.Verbose)
|
||||
stopwatch = Stopwatch.StartNew();
|
||||
|
||||
string responseJson = await _engine.Send(method, path, requestJson, _cancelToken).ConfigureAwait(false);
|
||||
string responseJson = await _engine.Send(method, path, requestJson, CancelToken).ConfigureAwait(false);
|
||||
|
||||
if (_config.LogLevel >= LogSeverity.Verbose)
|
||||
if (Logger.Level >= LogSeverity.Verbose)
|
||||
{
|
||||
stopwatch.Stop();
|
||||
if (payload != null && _config.LogLevel >= LogSeverity.Debug)
|
||||
double milliseconds = Math.Round(stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerMillisecond, 2);
|
||||
|
||||
string log = $"{method} {path}: {milliseconds} ms";
|
||||
if (payload != null && _config.LogLevel >= LogSeverity.Debug)
|
||||
{
|
||||
if (isPrivate)
|
||||
RaiseOnRequest(method, path, "[Hidden]", stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerMillisecond);
|
||||
else
|
||||
RaiseOnRequest(method, path, requestJson, stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerMillisecond);
|
||||
log += $" [Hidden]";
|
||||
else
|
||||
log += $" {requestJson}";
|
||||
}
|
||||
else
|
||||
RaiseOnRequest(method, path, null, stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerMillisecond);
|
||||
}
|
||||
Logger.Verbose(log);
|
||||
}
|
||||
|
||||
return responseJson;
|
||||
}
|
||||
@@ -100,19 +131,21 @@ namespace Discord.Net.Rest
|
||||
var isPrivate = request.IsPrivate;
|
||||
|
||||
Stopwatch stopwatch = null;
|
||||
if (_config.LogLevel >= LogSeverity.Verbose)
|
||||
if (Logger.Level >= LogSeverity.Verbose)
|
||||
stopwatch = Stopwatch.StartNew();
|
||||
|
||||
string responseJson = await _engine.SendFile(method, path, filename, stream, _cancelToken).ConfigureAwait(false);
|
||||
string responseJson = await _engine.SendFile(method, path, filename, stream, CancelToken).ConfigureAwait(false);
|
||||
|
||||
if (_config.LogLevel >= LogSeverity.Verbose)
|
||||
if (Logger.Level >= LogSeverity.Verbose)
|
||||
{
|
||||
stopwatch.Stop();
|
||||
if (_config.LogLevel >= LogSeverity.Debug && !isPrivate)
|
||||
RaiseOnRequest(method, path, filename, stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerMillisecond);
|
||||
else
|
||||
RaiseOnRequest(method, path, null, stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerMillisecond);
|
||||
}
|
||||
double milliseconds = Math.Round(stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerMillisecond, 2);
|
||||
|
||||
string log = $"{method} {path}: {milliseconds} ms";
|
||||
if (_config.LogLevel >= LogSeverity.Debug && !isPrivate)
|
||||
log += $" {filename}";
|
||||
Logger.Verbose(log);
|
||||
}
|
||||
|
||||
return responseJson;
|
||||
}
|
||||
|
||||
@@ -12,15 +12,13 @@ namespace Discord.Net.Rest
|
||||
{
|
||||
private readonly DiscordConfig _config;
|
||||
private readonly RestSharp.RestClient _client;
|
||||
private readonly Logger _logger;
|
||||
|
||||
private readonly object _rateLimitLock;
|
||||
private DateTime _rateLimitTime;
|
||||
|
||||
public RestSharpEngine(DiscordConfig config, Logger logger, string baseUrl)
|
||||
public RestSharpEngine(DiscordConfig config, string baseUrl)
|
||||
{
|
||||
_config = config;
|
||||
_logger = logger;
|
||||
_rateLimitLock = new object();
|
||||
_client = new RestSharp.RestClient(baseUrl)
|
||||
{
|
||||
@@ -28,9 +26,6 @@ namespace Discord.Net.Rest
|
||||
ReadWriteTimeout = _config.RestTimeout,
|
||||
UserAgent = config.UserAgent
|
||||
};
|
||||
/*if (_config.ProxyUrl != null)
|
||||
_client.Proxy = new WebProxy(_config.ProxyUrl, true, new string[0], _config.ProxyCredentials);
|
||||
else*/
|
||||
_client.Proxy = null;
|
||||
_client.RemoveDefaultParameter("Accept");
|
||||
_client.AddDefaultHeader("accept", "*/*");
|
||||
@@ -83,21 +78,18 @@ namespace Discord.Net.Rest
|
||||
int milliseconds;
|
||||
if (retryAfter != null && int.TryParse((string)retryAfter.Value, out milliseconds))
|
||||
{
|
||||
if (_logger != null)
|
||||
/*var now = DateTime.UtcNow;
|
||||
if (now >= _rateLimitTime)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
if (now >= _rateLimitTime)
|
||||
lock (_rateLimitLock)
|
||||
{
|
||||
lock (_rateLimitLock)
|
||||
if (now >= _rateLimitTime)
|
||||
{
|
||||
if (now >= _rateLimitTime)
|
||||
{
|
||||
_rateLimitTime = now.AddMilliseconds(milliseconds);
|
||||
_logger.Warning($"Rate limit hit, waiting {Math.Round(milliseconds / 1000.0f, 2)} seconds");
|
||||
}
|
||||
_rateLimitTime = now.AddMilliseconds(milliseconds);
|
||||
_logger.Warning($"Rate limit hit, waiting {Math.Round(milliseconds / 1000.0f, 2)} seconds");
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
await Task.Delay(milliseconds, cancelToken).ConfigureAwait(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Discord.Net
|
||||
{
|
||||
#if NET45
|
||||
#if NET46
|
||||
[Serializable]
|
||||
#endif
|
||||
public sealed class TimeoutException : OperationCanceledException
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Discord.API.Client;
|
||||
using Discord.API.Client.GatewaySocket;
|
||||
using Discord.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
@@ -10,14 +11,13 @@ namespace Discord.Net.WebSockets
|
||||
{
|
||||
public partial class GatewaySocket : WebSocket
|
||||
{
|
||||
public int LastSequence => _lastSeq;
|
||||
private int _lastSeq;
|
||||
|
||||
public string SessionId => _sessionId;
|
||||
private int _lastSequence;
|
||||
private string _sessionId;
|
||||
|
||||
public GatewaySocket(DiscordClient client, Logger logger)
|
||||
: base(client, logger)
|
||||
public string Token { get; private set; }
|
||||
|
||||
public GatewaySocket(DiscordClient client, JsonSerializer serializer, Logger logger)
|
||||
: base(client, serializer, logger)
|
||||
{
|
||||
Disconnected += async (s, e) =>
|
||||
{
|
||||
@@ -26,10 +26,11 @@ namespace Discord.Net.WebSockets
|
||||
};
|
||||
}
|
||||
|
||||
public async Task Connect()
|
||||
public async Task Connect(string token)
|
||||
{
|
||||
await BeginConnect().ConfigureAwait(false);
|
||||
SendIdentify();
|
||||
Token = token;
|
||||
await BeginConnect().ConfigureAwait(false);
|
||||
SendIdentify(token);
|
||||
}
|
||||
private async Task Redirect()
|
||||
{
|
||||
@@ -46,13 +47,13 @@ namespace Discord.Net.WebSockets
|
||||
{
|
||||
try
|
||||
{
|
||||
await Connect().ConfigureAwait(false);
|
||||
await Connect(Token).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
catch (OperationCanceledException) { throw; }
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Log(LogSeverity.Error, $"Reconnect failed", ex);
|
||||
Logger.Error("Reconnect failed", ex);
|
||||
//Net is down? We can keep trying to reconnect until the user runs Disconnect()
|
||||
await Task.Delay(_client.Config.FailedReconnectDelay, cancelToken).ConfigureAwait(false);
|
||||
}
|
||||
@@ -60,13 +61,13 @@ namespace Discord.Net.WebSockets
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
}
|
||||
public Task Disconnect() => TaskManager.Stop();
|
||||
public Task Disconnect() => _taskManager.Stop();
|
||||
|
||||
protected override async Task Run()
|
||||
{
|
||||
List<Task> tasks = new List<Task>();
|
||||
tasks.AddRange(_engine.GetTasks(_cancelToken));
|
||||
tasks.Add(HeartbeatAsync(_cancelToken));
|
||||
tasks.AddRange(_engine.GetTasks(CancelToken));
|
||||
tasks.Add(HeartbeatAsync(CancelToken));
|
||||
await _taskManager.Start(tasks, _cancelTokenSource).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -75,7 +76,7 @@ namespace Discord.Net.WebSockets
|
||||
await base.ProcessMessage(json).ConfigureAwait(false);
|
||||
var msg = JsonConvert.DeserializeObject<WebSocketMessage>(json);
|
||||
if (msg.Sequence.HasValue)
|
||||
_lastSeq = msg.Sequence.Value;
|
||||
_lastSequence = msg.Sequence.Value;
|
||||
|
||||
var opCode = (OpCodes)msg.Operation;
|
||||
switch (opCode)
|
||||
@@ -105,20 +106,18 @@ namespace Discord.Net.WebSockets
|
||||
if (payload.Url != null)
|
||||
{
|
||||
Host = payload.Url;
|
||||
if (_logger.Level >= LogSeverity.Info)
|
||||
_logger.Info("Redirected to " + payload.Url);
|
||||
Logger.Info("Redirected to " + payload.Url);
|
||||
await Redirect().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (_logger.Level >= LogSeverity.Warning)
|
||||
_logger.Log(LogSeverity.Warning, $"Unknown Opcode: {opCode}");
|
||||
Logger.Warning($"Unknown Opcode: {opCode}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void SendIdentify()
|
||||
public void SendIdentify(string token)
|
||||
{
|
||||
var props = new Dictionary<string, string>
|
||||
{
|
||||
@@ -127,7 +126,7 @@ namespace Discord.Net.WebSockets
|
||||
var msg = new IdentifyCommand()
|
||||
{
|
||||
Version = 3,
|
||||
Token = _client.Token,
|
||||
Token = token,
|
||||
Properties = props,
|
||||
LargeThreshold = _client.Config.UseLargeThreshold ? 100 : (int?)null,
|
||||
UseCompression = true
|
||||
@@ -136,7 +135,7 @@ namespace Discord.Net.WebSockets
|
||||
}
|
||||
|
||||
public void SendResume()
|
||||
=> QueueMessage(new ResumeCommand { SessionId = _sessionId, Sequence = _lastSeq });
|
||||
=> QueueMessage(new ResumeCommand { SessionId = _sessionId, Sequence = _lastSequence });
|
||||
public override void SendHeartbeat()
|
||||
=> QueueMessage(new HeartbeatCommand());
|
||||
public void SendUpdateStatus(long? idleSince, int? gameId)
|
||||
|
||||
@@ -13,30 +13,22 @@ namespace Discord.Net.WebSockets
|
||||
internal class WS4NetEngine : IWebSocketEngine
|
||||
{
|
||||
private readonly DiscordConfig _config;
|
||||
private readonly Logger _logger;
|
||||
private readonly ConcurrentQueue<string> _sendQueue;
|
||||
private readonly WebSocket _parent;
|
||||
private readonly TaskManager _taskManager;
|
||||
private WS4NetWebSocket _webSocket;
|
||||
private ManualResetEventSlim _waitUntilConnect;
|
||||
|
||||
public event EventHandler<WebSocketBinaryMessageEventArgs> BinaryMessage;
|
||||
public event EventHandler<WebSocketTextMessageEventArgs> TextMessage;
|
||||
private void RaiseBinaryMessage(byte[] data)
|
||||
{
|
||||
if (BinaryMessage != null)
|
||||
BinaryMessage(this, new WebSocketBinaryMessageEventArgs(data));
|
||||
}
|
||||
private void RaiseTextMessage(string msg)
|
||||
{
|
||||
if (TextMessage != null)
|
||||
TextMessage(this, new WebSocketTextMessageEventArgs(msg));
|
||||
}
|
||||
public event EventHandler<WebSocketBinaryMessageEventArgs> BinaryMessage = delegate { };
|
||||
public event EventHandler<WebSocketTextMessageEventArgs> TextMessage = delegate { };
|
||||
private void OnBinaryMessage(byte[] data)
|
||||
=> BinaryMessage(this, new WebSocketBinaryMessageEventArgs(data));
|
||||
private void OnTextMessage(string msg)
|
||||
=> TextMessage(this, new WebSocketTextMessageEventArgs(msg));
|
||||
|
||||
internal WS4NetEngine(WebSocket parent, DiscordConfig config, Logger logger)
|
||||
internal WS4NetEngine(DiscordConfig config, TaskManager taskManager)
|
||||
{
|
||||
_parent = parent;
|
||||
_config = config;
|
||||
_logger = logger;
|
||||
_taskManager = taskManager;
|
||||
_sendQueue = new ConcurrentQueue<string>();
|
||||
_waitUntilConnect = new ManualResetEventSlim();
|
||||
}
|
||||
@@ -57,7 +49,7 @@ namespace Discord.Net.WebSockets
|
||||
_waitUntilConnect.Reset();
|
||||
_webSocket.Open();
|
||||
_waitUntilConnect.Wait(cancelToken);
|
||||
_parent.TaskManager.ThrowException(); //In case our connection failed
|
||||
_taskManager.ThrowException(); //In case our connection failed
|
||||
return TaskHelper.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -84,27 +76,25 @@ namespace Discord.Net.WebSockets
|
||||
|
||||
private void OnWebSocketError(object sender, ErrorEventArgs e)
|
||||
{
|
||||
_parent.TaskManager.SignalError(e.Exception);
|
||||
_taskManager.SignalError(e.Exception);
|
||||
_waitUntilConnect.Set();
|
||||
}
|
||||
private void OnWebSocketClosed(object sender, EventArgs e)
|
||||
{
|
||||
var ex = new Exception($"Connection lost or close message received.");
|
||||
_parent.TaskManager.SignalError(ex, isUnexpected: true);
|
||||
Exception ex;
|
||||
if (e is ClosedEventArgs)
|
||||
ex = new Exception($"Received close code {(e as ClosedEventArgs).Code}: {(e as ClosedEventArgs).Reason ?? "No reason"}");
|
||||
else
|
||||
ex = new Exception($"Connection lost");
|
||||
_taskManager.SignalError(ex, isUnexpected: true);
|
||||
_waitUntilConnect.Set();
|
||||
}
|
||||
private void OnWebSocketOpened(object sender, EventArgs e)
|
||||
{
|
||||
_waitUntilConnect.Set();
|
||||
}
|
||||
=> _waitUntilConnect.Set();
|
||||
private void OnWebSocketText(object sender, MessageReceivedEventArgs e)
|
||||
{
|
||||
RaiseTextMessage(e.Message);
|
||||
}
|
||||
=> OnTextMessage(e.Message);
|
||||
private void OnWebSocketBinary(object sender, DataReceivedEventArgs e)
|
||||
{
|
||||
RaiseBinaryMessage(e.Data);
|
||||
}
|
||||
=> OnBinaryMessage(e.Data);
|
||||
|
||||
public IEnumerable<Task> GetTasks(CancellationToken cancelToken) => new Task[] { SendAsync(cancelToken) };
|
||||
|
||||
@@ -128,9 +118,7 @@ namespace Discord.Net.WebSockets
|
||||
}
|
||||
|
||||
public void QueueMessage(string message)
|
||||
{
|
||||
_sendQueue.Enqueue(message);
|
||||
}
|
||||
=> _sendQueue.Enqueue(message);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,4 +1,5 @@
|
||||
using Discord.API.Client;
|
||||
using Discord.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.IO;
|
||||
@@ -14,88 +15,59 @@ namespace Discord.Net.WebSockets
|
||||
protected readonly IWebSocketEngine _engine;
|
||||
protected readonly DiscordClient _client;
|
||||
protected readonly ManualResetEventSlim _connectedEvent;
|
||||
|
||||
protected int _heartbeatInterval;
|
||||
private DateTime _lastHeartbeat;
|
||||
|
||||
public CancellationToken? ParentCancelToken { get; set; }
|
||||
public CancellationToken CancelToken => _cancelToken;
|
||||
protected CancellationTokenSource _cancelTokenSource;
|
||||
protected CancellationToken _cancelToken;
|
||||
|
||||
public JsonSerializer Serializer => _serializer;
|
||||
protected JsonSerializer _serializer;
|
||||
|
||||
internal TaskManager TaskManager => _taskManager;
|
||||
protected readonly TaskManager _taskManager;
|
||||
protected readonly JsonSerializer _serializer;
|
||||
protected CancellationTokenSource _cancelTokenSource;
|
||||
protected int _heartbeatInterval;
|
||||
private DateTime _lastHeartbeat;
|
||||
|
||||
/// <summary> Gets the logger used for this client. </summary>
|
||||
internal Logger Logger { get; }
|
||||
|
||||
public Logger Logger => _logger;
|
||||
protected readonly Logger _logger;
|
||||
public CancellationToken CancelToken { get; private set; }
|
||||
|
||||
public string Host { get { return _host; } set { _host = value; } }
|
||||
private string _host;
|
||||
public CancellationToken? ParentCancelToken { get; set; }
|
||||
|
||||
public ConnectionState State => _state;
|
||||
protected ConnectionState _state;
|
||||
public string Host { get; set; }
|
||||
/// <summary> Gets the current connection state of this client. </summary>
|
||||
public ConnectionState State { get; private set; }
|
||||
|
||||
public event EventHandler Connected;
|
||||
private void RaiseConnected()
|
||||
{
|
||||
if (_logger.Level >= LogSeverity.Info)
|
||||
_logger.Info( "Connected");
|
||||
if (Connected != null)
|
||||
Connected(this, EventArgs.Empty);
|
||||
}
|
||||
public event EventHandler<DisconnectedEventArgs> Disconnected;
|
||||
private void RaiseDisconnected(bool wasUnexpected, Exception error)
|
||||
{
|
||||
if (_logger.Level >= LogSeverity.Info)
|
||||
_logger.Info( "Disconnected");
|
||||
if (Disconnected != null)
|
||||
Disconnected(this, new DisconnectedEventArgs(wasUnexpected, error));
|
||||
}
|
||||
public event EventHandler Connected = delegate { };
|
||||
private void OnConnected()
|
||||
=> Connected(this, EventArgs.Empty);
|
||||
public event EventHandler<DisconnectedEventArgs> Disconnected = delegate { };
|
||||
private void OnDisconnected(bool wasUnexpected, Exception error)
|
||||
=> Disconnected(this, new DisconnectedEventArgs(wasUnexpected, error));
|
||||
|
||||
public WebSocket(DiscordClient client, Logger logger)
|
||||
public WebSocket(DiscordClient client, JsonSerializer serializer, Logger logger)
|
||||
{
|
||||
_client = client;
|
||||
_logger = logger;
|
||||
Logger = logger;
|
||||
_serializer = serializer;
|
||||
|
||||
_lock = new Semaphore(1, 1);
|
||||
_taskManager = new TaskManager(Cleanup);
|
||||
_cancelToken = new CancellationToken(true);
|
||||
CancelToken = new CancellationToken(true);
|
||||
_connectedEvent = new ManualResetEventSlim(false);
|
||||
|
||||
#if !DOTNET5_4
|
||||
_engine = new WS4NetEngine(this, client.Config, _logger);
|
||||
_engine = new WS4NetEngine(client.Config, _taskManager);
|
||||
#else
|
||||
//_engine = new BuiltInWebSocketEngine(this, client.Config, _logger);
|
||||
//_engine = new BuiltInWebSocketEngine(this, client.Config);
|
||||
#endif
|
||||
_engine.BinaryMessage += (s, e) =>
|
||||
{
|
||||
using (var compressed = new MemoryStream(e.Data, 2, e.Data.Length - 2))
|
||||
using (var decompressed = new MemoryStream())
|
||||
{
|
||||
using (var zlib = new DeflateStream(compressed, CompressionMode.Decompress))
|
||||
zlib.CopyTo(decompressed);
|
||||
decompressed.Position = 0;
|
||||
{
|
||||
using (var compressed = new MemoryStream(e.Data, 2, e.Data.Length - 2))
|
||||
using (var decompressed = new MemoryStream())
|
||||
{
|
||||
using (var zlib = new DeflateStream(compressed, CompressionMode.Decompress))
|
||||
zlib.CopyTo(decompressed);
|
||||
decompressed.Position = 0;
|
||||
using (var reader = new StreamReader(decompressed))
|
||||
ProcessMessage(reader.ReadToEnd()).Wait();
|
||||
}
|
||||
ProcessMessage(reader.ReadToEnd()).Wait();
|
||||
}
|
||||
};
|
||||
_engine.TextMessage += (s, e) => ProcessMessage(e.Message).Wait();
|
||||
|
||||
_serializer = new JsonSerializer();
|
||||
_serializer.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
|
||||
#if TEST_RESPONSES
|
||||
_serializer.CheckAdditionalContent = true;
|
||||
_serializer.MissingMemberHandling = MissingMemberHandling.Error;
|
||||
#else
|
||||
_serializer.Error += (s, e) =>
|
||||
{
|
||||
e.ErrorContext.Handled = true;
|
||||
_logger.Log(LogSeverity.Error, "Serialization Failed", e.ErrorContext.Error);
|
||||
};
|
||||
#endif
|
||||
}
|
||||
|
||||
protected async Task BeginConnect()
|
||||
@@ -107,13 +79,13 @@ namespace Discord.Net.WebSockets
|
||||
{
|
||||
await _taskManager.Stop().ConfigureAwait(false);
|
||||
_taskManager.ClearException();
|
||||
_state = ConnectionState.Connecting;
|
||||
State = ConnectionState.Connecting;
|
||||
|
||||
_cancelTokenSource = new CancellationTokenSource();
|
||||
_cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelTokenSource.Token, ParentCancelToken.Value).Token;
|
||||
CancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelTokenSource.Token, ParentCancelToken.Value).Token;
|
||||
_lastHeartbeat = DateTime.UtcNow;
|
||||
|
||||
await _engine.Connect(Host, _cancelToken).ConfigureAwait(false);
|
||||
await _engine.Connect(Host, CancelToken).ConfigureAwait(false);
|
||||
await Run().ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
@@ -131,10 +103,11 @@ namespace Discord.Net.WebSockets
|
||||
{
|
||||
try
|
||||
{
|
||||
_state = ConnectionState.Connected;
|
||||
State = ConnectionState.Connected;
|
||||
|
||||
_connectedEvent.Set();
|
||||
RaiseConnected();
|
||||
Logger.Info($"Connected");
|
||||
OnConnected();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -145,29 +118,32 @@ namespace Discord.Net.WebSockets
|
||||
protected abstract Task Run();
|
||||
protected virtual async Task Cleanup()
|
||||
{
|
||||
var oldState = _state;
|
||||
_state = ConnectionState.Disconnecting;
|
||||
var oldState = State;
|
||||
State = ConnectionState.Disconnecting;
|
||||
|
||||
await _engine.Disconnect().ConfigureAwait(false);
|
||||
_cancelTokenSource = null;
|
||||
_connectedEvent.Reset();
|
||||
|
||||
if (oldState == ConnectionState.Connected)
|
||||
RaiseDisconnected(_taskManager.WasUnexpected, _taskManager.Exception);
|
||||
_state = ConnectionState.Disconnected;
|
||||
{
|
||||
Logger.Info("Disconnected");
|
||||
OnDisconnected(_taskManager.WasUnexpected, _taskManager.Exception);
|
||||
}
|
||||
State = ConnectionState.Disconnected;
|
||||
}
|
||||
|
||||
protected virtual Task ProcessMessage(string json)
|
||||
{
|
||||
if (_logger.Level >= LogSeverity.Debug)
|
||||
_logger.Debug( $"In: {json}");
|
||||
if (Logger.Level >= LogSeverity.Debug)
|
||||
Logger.Debug( $"In: {json}");
|
||||
return TaskHelper.CompletedTask;
|
||||
}
|
||||
protected void QueueMessage(IWebSocketMessage message)
|
||||
{
|
||||
string json = JsonConvert.SerializeObject(new WebSocketMessage(message));
|
||||
if (_logger.Level >= LogSeverity.Debug)
|
||||
_logger.Debug( $"Out: " + json);
|
||||
if (Logger.Level >= LogSeverity.Debug)
|
||||
Logger.Debug( $"Out: {json}");
|
||||
_engine.QueueMessage(json);
|
||||
}
|
||||
|
||||
@@ -179,9 +155,9 @@ namespace Discord.Net.WebSockets
|
||||
{
|
||||
while (!cancelToken.IsCancellationRequested)
|
||||
{
|
||||
if (_state == ConnectionState.Connected)
|
||||
if (this.State == ConnectionState.Connected)
|
||||
{
|
||||
SendHeartbeat();
|
||||
SendHeartbeat();
|
||||
await Task.Delay(_heartbeatInterval, cancelToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
@@ -192,5 +168,20 @@ namespace Discord.Net.WebSockets
|
||||
});
|
||||
}
|
||||
public abstract void SendHeartbeat();
|
||||
|
||||
public void WaitForConnection(CancellationToken cancelToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
//Cancel if either DiscordClient.Disconnect is called, data socket errors or timeout is reached
|
||||
cancelToken = CancellationTokenSource.CreateLinkedTokenSource(cancelToken, CancelToken).Token;
|
||||
_connectedEvent.Wait(cancelToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
_taskManager.ThrowException(); //Throws data socket's internal error if any occured
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
71
src/Discord.Net/Reference.cs
Normal file
71
src/Discord.Net/Reference.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
/*internal class Reference<T>
|
||||
where T : CachedObject<ulong>
|
||||
{
|
||||
private Action<T> _onCache, _onUncache;
|
||||
private Func<ulong, T> _getItem;
|
||||
private ulong? _id;
|
||||
public ulong? Id
|
||||
{
|
||||
get { return _id; }
|
||||
set
|
||||
{
|
||||
_id = value;
|
||||
_value = null;
|
||||
}
|
||||
}
|
||||
|
||||
private T _value;
|
||||
public T Value
|
||||
{
|
||||
get
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public T Load()
|
||||
{
|
||||
var v = _value; //A little trickery to make this threadsafe
|
||||
var id = _id;
|
||||
if (v != null && !_value.IsCached)
|
||||
{
|
||||
v = null;
|
||||
_value = null;
|
||||
}
|
||||
if (v == null && id != null)
|
||||
{
|
||||
v = _getItem(id.Value);
|
||||
if (v != null && _onCache != null)
|
||||
_onCache(v);
|
||||
_value = v;
|
||||
}
|
||||
return v;
|
||||
return Value != null; //Used for precaching
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
if (_onUncache != null)
|
||||
{
|
||||
var v = _value;
|
||||
if (v != null && _onUncache != null)
|
||||
_onUncache(v);
|
||||
}
|
||||
}
|
||||
|
||||
public Reference(Func<ulong, T> onUpdate, Action<T> onCache = null, Action<T> onUncache = null)
|
||||
: this(null, onUpdate, onCache, onUncache)
|
||||
{ }
|
||||
public Reference(ulong? id, Func<ulong, T> getItem, Action<T> onCache = null, Action<T> onUncache = null)
|
||||
{
|
||||
_id = id;
|
||||
_getItem = getItem;
|
||||
_onCache = onCache;
|
||||
_onUncache = onUncache;
|
||||
_value = null;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace Discord
|
||||
{
|
||||
public interface IService
|
||||
{
|
||||
void Install(DiscordClient client);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace Discord
|
||||
{
|
||||
public static class LogExtensions
|
||||
{
|
||||
public static LogService Log(this DiscordClient client, bool required = true)
|
||||
=> client.GetService<LogService>(required);
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public class LogService : IService
|
||||
{
|
||||
public DiscordClient Client => _client;
|
||||
private DiscordClient _client;
|
||||
|
||||
public LogSeverity Level => _level;
|
||||
private LogSeverity _level;
|
||||
|
||||
public event EventHandler<LogMessageEventArgs> LogMessage;
|
||||
internal void RaiseLogMessage(LogMessageEventArgs e)
|
||||
{
|
||||
if (LogMessage != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
LogMessage(this, e);
|
||||
}
|
||||
catch { } //We dont want to log on log errors
|
||||
}
|
||||
}
|
||||
|
||||
void IService.Install(DiscordClient client)
|
||||
{
|
||||
_client = client;
|
||||
_level = client.Config.LogLevel;
|
||||
}
|
||||
|
||||
public Logger CreateLogger(string source)
|
||||
{
|
||||
return new Logger(this, source);
|
||||
}
|
||||
}
|
||||
|
||||
public class Logger
|
||||
{
|
||||
private LogService _service;
|
||||
|
||||
public LogSeverity Level => _level;
|
||||
private LogSeverity _level;
|
||||
|
||||
public string Source => _source;
|
||||
private string _source;
|
||||
|
||||
internal Logger(LogService service, string source)
|
||||
{
|
||||
_service = service;
|
||||
_level = service.Level;
|
||||
_source = source;
|
||||
}
|
||||
|
||||
public void Log(LogSeverity severity, string message, Exception exception = null)
|
||||
{
|
||||
if (severity <= _service.Level)
|
||||
_service.RaiseLogMessage(new LogMessageEventArgs(severity, _source, message, exception));
|
||||
}
|
||||
public void Error(string message, Exception exception = null)
|
||||
=> Log(LogSeverity.Error, message, exception);
|
||||
public void Warning(string message, Exception exception = null)
|
||||
=> Log(LogSeverity.Warning, message, exception);
|
||||
public void Info(string message, Exception exception = null)
|
||||
=> Log(LogSeverity.Info, message, exception);
|
||||
public void Verbose(string message, Exception exception = null)
|
||||
=> Log(LogSeverity.Verbose, message, exception);
|
||||
public void Debug(string message, Exception exception = null)
|
||||
=> Log(LogSeverity.Debug, message, exception);
|
||||
}
|
||||
}
|
||||
@@ -146,10 +146,7 @@ namespace Discord
|
||||
public void ThrowException()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_stopReason != null)
|
||||
_stopReason.Throw();
|
||||
}
|
||||
_stopReason?.Throw();
|
||||
}
|
||||
public void ClearException()
|
||||
{
|
||||
@@ -29,12 +29,11 @@
|
||||
},
|
||||
|
||||
"dependencies": {
|
||||
"Newtonsoft.Json": "7.0.1",
|
||||
"StyleCop.Analyzers": "1.0.0-rc2"
|
||||
"Newtonsoft.Json": "7.0.1"
|
||||
},
|
||||
|
||||
"frameworks": {
|
||||
"net45": {
|
||||
"net46": {
|
||||
"dependencies": {
|
||||
"WebSocket4Net": "0.14.1",
|
||||
"RestSharp": "105.2.3"
|
||||
|
||||
Reference in New Issue
Block a user