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