| @@ -39,4 +39,3 @@ jobs: | |||
| steps: | |||
| - template: azure/build.yml | |||
| - template: azure/deploy.yml | |||
| - template: azure/docs.yml | |||
| @@ -10,7 +10,7 @@ namespace Discord | |||
| public interface IMessage : ISnowflakeEntity, IDeletable | |||
| { | |||
| /// <summary> | |||
| /// Gets the type of this system message. | |||
| /// Gets the type of this message. | |||
| /// </summary> | |||
| MessageType Type { get; } | |||
| /// <summary> | |||
| @@ -16,7 +16,7 @@ namespace Discord.Rest | |||
| /// <summary> | |||
| /// Gets the logged-in user. | |||
| /// </summary> | |||
| public new RestSelfUser CurrentUser => base.CurrentUser as RestSelfUser; | |||
| public new RestSelfUser CurrentUser { get => base.CurrentUser as RestSelfUser; internal set => base.CurrentUser = value; } | |||
| /// <inheritdoc /> | |||
| public DiscordRestClient() : this(new DiscordRestConfig()) { } | |||
| @@ -72,6 +72,8 @@ namespace Discord.Rest | |||
| public MessageReference Reference { get; private set; } | |||
| /// <inheritdoc /> | |||
| public MessageFlags? Flags { get; private set; } | |||
| /// <inheritdoc/> | |||
| public MessageType Type { get; private set; } | |||
| /// <inheritdoc/> | |||
| public IReadOnlyCollection<ActionRowComponent> Components { get; private set; } | |||
| @@ -92,6 +94,8 @@ namespace Discord.Rest | |||
| } | |||
| internal virtual void Update(Model model) | |||
| { | |||
| Type = model.Type; | |||
| if (model.Timestamp.IsSpecified) | |||
| _timestampTicks = model.Timestamp.Value.UtcTicks; | |||
| @@ -219,8 +223,6 @@ namespace Discord.Rest | |||
| /// </returns> | |||
| public override string ToString() => Content; | |||
| /// <inheritdoc /> | |||
| MessageType IMessage.Type => MessageType.Default; | |||
| IUser IMessage.Author => Author; | |||
| /// <inheritdoc /> | |||
| IReadOnlyCollection<IAttachment> IMessage.Attachments => Attachments; | |||
| @@ -9,9 +9,6 @@ namespace Discord.Rest | |||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
| public class RestSystemMessage : RestMessage, ISystemMessage | |||
| { | |||
| /// <inheritdoc /> | |||
| public MessageType Type { get; private set; } | |||
| internal RestSystemMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author) | |||
| : base(discord, id, channel, author, MessageSource.System) | |||
| { | |||
| @@ -25,8 +22,6 @@ namespace Discord.Rest | |||
| internal override void Update(Model model) | |||
| { | |||
| base.Update(model); | |||
| Type = model.Type; | |||
| } | |||
| private string DebuggerDisplay => $"{Author}: {Content} ({Id}, {Type})"; | |||
| @@ -75,11 +75,6 @@ namespace Discord | |||
| nextReconnectDelay = 1000; //Reset delay | |||
| await _connectionPromise.Task.ConfigureAwait(false); | |||
| } | |||
| catch (OperationCanceledException ex) | |||
| { | |||
| Cancel(); //In case this exception didn't come from another Error call | |||
| await DisconnectAsync(ex, !reconnectCancelToken.IsCancellationRequested).ConfigureAwait(false); | |||
| } | |||
| catch (Exception ex) | |||
| { | |||
| Error(ex); //In case this exception didn't come from another Error call | |||
| @@ -143,16 +138,7 @@ namespace Discord | |||
| catch (OperationCanceledException) { } | |||
| }); | |||
| try | |||
| { | |||
| await _onConnecting().ConfigureAwait(false); | |||
| } | |||
| catch (TaskCanceledException ex) | |||
| { | |||
| Exception innerEx = ex.InnerException ?? new OperationCanceledException("Failed to connect."); | |||
| Error(innerEx); | |||
| throw innerEx; | |||
| } | |||
| await _onConnecting().ConfigureAwait(false); | |||
| await _logger.InfoAsync("Connected").ConfigureAwait(false); | |||
| State = ConnectionState.Connected; | |||
| @@ -188,9 +188,9 @@ namespace Discord.API | |||
| catch { } | |||
| if (ex is GatewayReconnectException) | |||
| await WebSocketClient.DisconnectAsync(4000); | |||
| await WebSocketClient.DisconnectAsync(4000).ConfigureAwait(false); | |||
| else | |||
| await WebSocketClient.DisconnectAsync().ConfigureAwait(false); | |||
| await WebSocketClient.DisconnectAsync().ConfigureAwait(false); | |||
| ConnectionState = ConnectionState.Disconnected; | |||
| } | |||
| @@ -621,6 +621,7 @@ namespace Discord.WebSocket | |||
| var activities = _activity.IsSpecified ? ImmutableList.Create(_activity.Value) : null; | |||
| currentUser.Presence = new SocketPresence(Status, null, activities); | |||
| ApiClient.CurrentUserId = currentUser.Id; | |||
| Rest.CurrentUser = RestSelfUser.Create(this, data.User); | |||
| int unavailableGuilds = 0; | |||
| for (int i = 0; i < data.Guilds.Length; i++) | |||
| { | |||
| @@ -1,42 +0,0 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Discord.WebSocket | |||
| { | |||
| public interface ISocketInvite | |||
| { | |||
| /// <summary> | |||
| /// Gets the unique identifier for this invite. | |||
| /// </summary> | |||
| /// <returns> | |||
| /// A string containing the invite code (e.g. <c>FTqNnyS</c>). | |||
| /// </returns> | |||
| string Code { get; } | |||
| /// <summary> | |||
| /// Gets the URL used to accept this invite | |||
| /// </summary> | |||
| /// <returns> | |||
| /// A string containing the full invite URL (e.g. <c>https://discord.gg/FTqNnyS</c>). | |||
| /// </returns> | |||
| string Url { get; } | |||
| /// <summary> | |||
| /// Gets the channel this invite is linked to. | |||
| /// </summary> | |||
| /// <returns> | |||
| /// A generic channel that the invite points to. | |||
| /// </returns> | |||
| SocketGuildChannel Channel { get; } | |||
| /// <summary> | |||
| /// Gets the guild this invite is linked to. | |||
| /// </summary> | |||
| /// <returns> | |||
| /// A guild object representing the guild that the invite points to. | |||
| /// </returns> | |||
| SocketGuild Guild { get; } | |||
| } | |||
| } | |||
| @@ -1,47 +0,0 @@ | |||
| using System; | |||
| using System.Collections.Concurrent; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Discord.WebSocket | |||
| { | |||
| internal class InviteCache | |||
| { | |||
| private readonly ConcurrentDictionary<string, SocketGuildInvite> _invites; | |||
| private readonly ConcurrentQueue<string> _queue; | |||
| private static int _size; | |||
| public InviteCache(DiscordSocketClient client) | |||
| { | |||
| //NOTE: | |||
| //This should be an option in the client config. default for now is 20 invites per guild | |||
| _size = client.Guilds.Count * 20; | |||
| _invites = new ConcurrentDictionary<string, SocketGuildInvite>(); | |||
| _queue = new ConcurrentQueue<string>(); | |||
| } | |||
| public void Add(SocketGuildInvite invite) | |||
| { | |||
| if(_invites.TryAdd(invite.Code, invite)) | |||
| { | |||
| _queue.Enqueue(invite.Code); | |||
| while (_queue.Count > _size && _queue.TryDequeue(out string invCode)) | |||
| _invites.TryRemove(invCode, out _); | |||
| } | |||
| } | |||
| public SocketGuildInvite Remove(string inviteCode) | |||
| { | |||
| _invites.TryRemove(inviteCode, out SocketGuildInvite inv); | |||
| return inv; | |||
| } | |||
| public SocketGuildInvite Get(string inviteCode) | |||
| { | |||
| if(_invites.TryGetValue(inviteCode, out SocketGuildInvite inv)) | |||
| return inv; | |||
| return null; | |||
| } | |||
| } | |||
| } | |||
| @@ -1,112 +0,0 @@ | |||
| using Discord.Rest; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Runtime.Serialization.Formatters; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| using InviteUpdate = Discord.API.Gateway.InviteCreatedEvent; | |||
| namespace Discord.WebSocket | |||
| { | |||
| /// <summary> | |||
| /// Represents a guild invite | |||
| /// </summary> | |||
| public class SocketGuildInvite : SocketEntity<string>, ISocketInvite | |||
| { | |||
| public string Code { get; private set; } | |||
| public string Url => $"{DiscordConfig.InviteUrl}{Code}"; | |||
| public SocketGuildChannel Channel { get; private set; } | |||
| public SocketGuild Guild { get; private set; } | |||
| /// <summary> | |||
| /// Gets the unique invite code | |||
| /// <returns> | |||
| /// Returns the unique invite code | |||
| /// </returns> | |||
| /// </summary> | |||
| public string Id => Code; | |||
| /// <summary> | |||
| /// Gets the user who created the invite | |||
| /// <returns> | |||
| /// Returns the user who created the invite | |||
| /// </returns> | |||
| /// </summary> | |||
| public SocketGuildUser Inviter { get; private set; } | |||
| /// <summary> | |||
| /// Gets the maximum number of times the invite can be used, if there is no limit then the value will be 0 | |||
| /// <returns> | |||
| /// Returns the maximum number of times the invite can be used, if there is no limit then the value will be 0 | |||
| /// </returns> | |||
| /// </summary> | |||
| public int? MaxUses { get; private set; } | |||
| /// <summary> | |||
| /// Gets whether or not the invite is temporary (invited users will be kicked on disconnect unless they're assigned a role) | |||
| /// <returns> | |||
| /// Returns whether or not the invite is temporary (invited users will be kicked on disconnect unless they're assigned a role) | |||
| /// </returns> | |||
| /// </summary> | |||
| public bool Temporary { get; private set; } | |||
| /// <summary> | |||
| /// Gets the time at which the invite was created | |||
| /// <returns> | |||
| /// Returns the time at which the invite was created | |||
| /// </returns> | |||
| /// </summary> | |||
| public DateTimeOffset? CreatedAt { get; private set; } | |||
| /// <summary> | |||
| /// Gets how long the invite is valid for | |||
| /// <returns> | |||
| /// Returns how long the invite is valid for (in seconds) | |||
| /// </returns> | |||
| /// </summary> | |||
| public TimeSpan? MaxAge { get; private set; } | |||
| internal SocketGuildInvite(DiscordSocketClient _client, SocketGuild guild, SocketGuildChannel channel, string inviteCode, RestInviteMetadata rest) : base(_client, inviteCode) | |||
| { | |||
| Code = inviteCode; | |||
| Guild = guild; | |||
| Channel = channel; | |||
| CreatedAt = rest.CreatedAt; | |||
| Temporary = rest.IsTemporary; | |||
| MaxUses = rest.MaxUses; | |||
| Inviter = guild.GetUser(rest.Inviter.Id); | |||
| if (rest.MaxAge.HasValue) | |||
| MaxAge = TimeSpan.FromSeconds(rest.MaxAge.Value); | |||
| } | |||
| internal SocketGuildInvite(DiscordSocketClient _client, SocketGuild guild, SocketGuildChannel channel, string inviteCode, InviteUpdate Update) : base(_client, inviteCode) | |||
| { | |||
| Code = inviteCode; | |||
| Guild = guild; | |||
| Channel = channel; | |||
| if (Update.RawTimestamp.IsSpecified) | |||
| CreatedAt = Update.RawTimestamp.Value; | |||
| else | |||
| CreatedAt = DateTimeOffset.Now; | |||
| if (Update.inviter.IsSpecified) | |||
| Inviter = guild.GetUser(Update.inviter.Value.Id); | |||
| Temporary = Update.TempInvite; | |||
| MaxUses = Update.MaxUsers; | |||
| MaxAge = TimeSpan.FromSeconds(Update.RawAge); | |||
| } | |||
| internal static SocketGuildInvite Create(DiscordSocketClient _client, SocketGuild guild, SocketGuildChannel channel, string inviteCode, InviteUpdate Update) | |||
| { | |||
| var invite = new SocketGuildInvite(_client, guild, channel, inviteCode, Update); | |||
| return invite; | |||
| } | |||
| internal static SocketGuildInvite CreateFromRest(DiscordSocketClient _client, SocketGuild guild, SocketGuildChannel channel, string inviteCode, RestInviteMetadata rest) | |||
| { | |||
| var invite = new SocketGuildInvite(_client, guild, channel, inviteCode, rest); | |||
| return invite; | |||
| } | |||
| /// <summary> | |||
| /// Deletes the invite | |||
| /// </summary> | |||
| /// <param name="options"></param> | |||
| /// <returns></returns> | |||
| public Task DeleteAsync(RequestOptions options = null) | |||
| => SocketInviteHelper.DeleteAsync(this, Discord, options); | |||
| } | |||
| } | |||
| @@ -1,17 +0,0 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Discord.WebSocket | |||
| { | |||
| internal class SocketInviteHelper | |||
| { | |||
| public static async Task DeleteAsync(ISocketInvite invite, BaseSocketClient client, | |||
| RequestOptions options) | |||
| { | |||
| await client.ApiClient.DeleteInviteAsync(invite.Code, options).ConfigureAwait(false); | |||
| } | |||
| } | |||
| } | |||
| @@ -65,6 +65,9 @@ namespace Discord.WebSocket | |||
| /// <inheritdoc /> | |||
| public MessageFlags? Flags { get; private set; } | |||
| /// <inheritdoc/> | |||
| public MessageType Type { get; private set; } | |||
| /// <summary> | |||
| /// Returns all attachments included in this message. | |||
| /// </summary> | |||
| @@ -126,6 +129,8 @@ namespace Discord.WebSocket | |||
| } | |||
| internal virtual void Update(ClientState state, Model model) | |||
| { | |||
| Type = model.Type; | |||
| if (model.Timestamp.IsSpecified) | |||
| _timestampTicks = model.Timestamp.Value.UtcTicks; | |||
| @@ -238,8 +243,6 @@ namespace Discord.WebSocket | |||
| /// <inheritdoc /> | |||
| IMessageChannel IMessage.Channel => Channel; | |||
| /// <inheritdoc /> | |||
| MessageType IMessage.Type => MessageType.Default; | |||
| /// <inheritdoc /> | |||
| IReadOnlyCollection<IAttachment> IMessage.Attachments => Attachments; | |||
| /// <inheritdoc /> | |||
| IReadOnlyCollection<IEmbed> IMessage.Embeds => Embeds; | |||
| @@ -9,9 +9,6 @@ namespace Discord.WebSocket | |||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
| public class SocketSystemMessage : SocketMessage, ISystemMessage | |||
| { | |||
| /// <inheritdoc /> | |||
| public MessageType Type { get; private set; } | |||
| internal SocketSystemMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author) | |||
| : base(discord, id, channel, author, MessageSource.System) | |||
| { | |||
| @@ -25,8 +22,6 @@ namespace Discord.WebSocket | |||
| internal override void Update(ClientState state, Model model) | |||
| { | |||
| base.Update(state, model); | |||
| Type = model.Type; | |||
| } | |||
| private string DebuggerDisplay => $"{Author}: {Content} ({Id}, {Type})"; | |||
| @@ -125,12 +125,16 @@ namespace Discord.WebSocket | |||
| { | |||
| var entity = new SocketGuildUser(guild, guild.Discord.GetOrCreateUser(state, model.User)); | |||
| entity.Update(state, model); | |||
| if (!model.Roles.IsSpecified) | |||
| entity.UpdateRoles(new ulong[0]); | |||
| return entity; | |||
| } | |||
| internal static SocketGuildUser Create(SocketGuild guild, ClientState state, PresenceModel model) | |||
| { | |||
| var entity = new SocketGuildUser(guild, guild.Discord.GetOrCreateUser(state, model.User)); | |||
| entity.Update(state, model, false); | |||
| if (!model.Roles.IsSpecified) | |||
| entity.UpdateRoles(new ulong[0]); | |||
| return entity; | |||
| } | |||
| internal void Update(ClientState state, MemberModel model) | |||
| @@ -108,11 +108,11 @@ namespace Discord.Net.WebSockets | |||
| } | |||
| private async Task DisconnectInternalAsync(int closeCode = 1000, bool isDisposing = false) | |||
| { | |||
| _isDisconnecting = true; | |||
| try { _disconnectTokenSource.Cancel(false); } | |||
| catch { } | |||
| _isDisconnecting = true; | |||
| if (_client != null) | |||
| { | |||
| if (!isDisposing) | |||
| @@ -166,7 +166,14 @@ namespace Discord.Net.WebSockets | |||
| public async Task SendAsync(byte[] data, int index, int count, bool isText) | |||
| { | |||
| await _lock.WaitAsync().ConfigureAwait(false); | |||
| try | |||
| { | |||
| await _lock.WaitAsync(_cancelToken).ConfigureAwait(false); | |||
| } | |||
| catch (TaskCanceledException) | |||
| { | |||
| return; | |||
| } | |||
| try | |||
| { | |||
| if (_client == null) return; | |||
| @@ -201,7 +208,7 @@ namespace Discord.Net.WebSockets | |||
| { | |||
| while (!cancelToken.IsCancellationRequested) | |||
| { | |||
| WebSocketReceiveResult socketResult = await _client.ReceiveAsync(buffer, CancellationToken.None).ConfigureAwait(false); | |||
| WebSocketReceiveResult socketResult = await _client.ReceiveAsync(buffer, cancelToken).ConfigureAwait(false); | |||
| byte[] result; | |||
| int resultCount; | |||