@@ -28,7 +28,7 @@ namespace Discord | |||
MessageCreated += async (s, e) => | |||
{ | |||
//Ignore messages from ourselves | |||
if (e.Message.UserId == UserId) | |||
if (e.Message.UserId == _myId) | |||
return; | |||
//Check for the command character | |||
@@ -80,6 +80,12 @@ | |||
<Compile Include="..\Discord.Net\ChannelTypes.cs"> | |||
<Link>ChannelTypes.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\DiscordClient.API.cs"> | |||
<Link>DiscordClient.API.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\DiscordClient.Cache.cs"> | |||
<Link>DiscordClient.Cache.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\DiscordClient.cs"> | |||
<Link>DiscordClient.cs</Link> | |||
</Compile> | |||
@@ -107,6 +113,9 @@ | |||
<Compile Include="..\Discord.Net\Helpers\AsyncCache.cs"> | |||
<Link>Helpers\AsyncCache.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Helpers\Extensions.cs"> | |||
<Link>Helpers\Extensions.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Helpers\JsonHttpClient.cs"> | |||
<Link>Helpers\JsonHttpClient.cs</Link> | |||
</Compile> | |||
@@ -141,10 +141,10 @@ namespace Discord.API | |||
var request = new APIRequests.ChangePassword { NewPassword = newPassword, CurrentEmail = currentEmail, CurrentPassword = currentPassword }; | |||
return _http.Patch<SelfUserInfo>(Endpoints.UserMe, request); | |||
} | |||
public Task<SelfUserInfo> ChangeAvatar(DiscordClient.AvatarImageType imageType, byte[] bytes, string currentEmail, string currentPassword) | |||
public Task<SelfUserInfo> ChangeAvatar(AvatarImageType imageType, byte[] bytes, string currentEmail, string currentPassword) | |||
{ | |||
string base64 = Convert.ToBase64String(bytes); | |||
string type = imageType == DiscordClient.AvatarImageType.Jpeg ? "image/jpeg;base64" : "image/png;base64"; | |||
string type = imageType == AvatarImageType.Jpeg ? "image/jpeg;base64" : "image/png;base64"; | |||
var request = new APIRequests.ChangeAvatar { Avatar = $"data:{type},/9j/{base64}", CurrentEmail = currentEmail, CurrentPassword = currentPassword }; | |||
return _http.Patch<SelfUserInfo>(Endpoints.UserMe, request); | |||
} | |||
@@ -0,0 +1,559 @@ | |||
using Discord.API; | |||
using Discord.API.Models; | |||
using System; | |||
using System.IO; | |||
using System.Linq; | |||
using System.Net; | |||
using System.Threading.Tasks; | |||
namespace Discord | |||
{ | |||
public enum AvatarImageType | |||
{ | |||
Jpeg, | |||
Png | |||
} | |||
public partial class DiscordClient | |||
{ | |||
//Servers | |||
/// <summary> Creates a new server with the provided name and region (see Regions). </summary> | |||
public async Task<Server> CreateServer(string name, string region) | |||
{ | |||
CheckReady(); | |||
if (name == null) throw new ArgumentNullException(nameof(name)); | |||
if (region == null) throw new ArgumentNullException(nameof(region)); | |||
var response = await _api.CreateServer(name, region); | |||
return _servers.Update(response.Id, response); | |||
} | |||
/// <summary> Leaves the provided server, destroying it if you are the owner. </summary> | |||
public Task<Server> LeaveServer(Server server) | |||
=> LeaveServer(server?.Id); | |||
/// <summary> Leaves the provided server, destroying it if you are the owner. </summary> | |||
public async Task<Server> LeaveServer(string serverId) | |||
{ | |||
CheckReady(); | |||
if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
try { await _api.LeaveServer(serverId); } | |||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||
return _servers.Remove(serverId); | |||
} | |||
//Channels | |||
/// <summary> Creates a new channel with the provided name and type (see ChannelTypes). </summary> | |||
public Task<Channel> CreateChannel(Server server, string name, string type) | |||
=> CreateChannel(server?.Id, name, type); | |||
/// <summary> Creates a new channel with the provided name and type (see ChannelTypes). </summary> | |||
public async Task<Channel> CreateChannel(string serverId, string name, string type) | |||
{ | |||
CheckReady(); | |||
if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
if (name == null) throw new ArgumentNullException(nameof(name)); | |||
if (type == null) throw new ArgumentNullException(nameof(type)); | |||
var response = await _api.CreateChannel(serverId, name, type); | |||
return _channels.Update(response.Id, serverId, response); | |||
} | |||
/// <summary> Creates a new private channel with the provided user. </summary> | |||
public Task<Channel> CreatePMChannel(User user) | |||
=> CreatePMChannel(user?.Id); | |||
/// <summary> Creates a new private channel with the provided user. </summary> | |||
public async Task<Channel> CreatePMChannel(string userId) | |||
{ | |||
CheckReady(); | |||
if (userId == null) throw new ArgumentNullException(nameof(userId)); | |||
var response = await _api.CreatePMChannel(_myId, userId); | |||
return _channels.Update(response.Id, response); | |||
} | |||
/// <summary> Destroys the provided channel. </summary> | |||
public Task<Channel> DestroyChannel(Channel channel) | |||
=> DestroyChannel(channel?.Id); | |||
/// <summary> Destroys the provided channel. </summary> | |||
public async Task<Channel> DestroyChannel(string channelId) | |||
{ | |||
CheckReady(); | |||
if (channelId == null) throw new ArgumentNullException(nameof(channelId)); | |||
try { await _api.DestroyChannel(channelId); } | |||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||
return _channels.Remove(channelId); | |||
} | |||
//Bans | |||
/// <summary> Bans a user from the provided server. </summary> | |||
public Task Ban(Server server, User user) | |||
=> Ban(server?.Id, user?.Id); | |||
/// <summary> Bans a user from the provided server. </summary> | |||
public Task Ban(Server server, string userId) | |||
=> Ban(server?.Id, userId); | |||
/// <summary> Bans a user from the provided server. </summary> | |||
public Task Ban(string server, User user) | |||
=> Ban(server, user?.Id); | |||
/// <summary> Bans a user from the provided server. </summary> | |||
public Task Ban(string serverId, string userId) | |||
{ | |||
CheckReady(); | |||
if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
if (userId == null) throw new ArgumentNullException(nameof(userId)); | |||
return _api.Ban(serverId, userId); | |||
} | |||
/// <summary> Unbans a user from the provided server. </summary> | |||
public Task Unban(Server server, User user) | |||
=> Unban(server?.Id, user?.Id); | |||
/// <summary> Unbans a user from the provided server. </summary> | |||
public Task Unban(Server server, string userId) | |||
=> Unban(server?.Id, userId); | |||
/// <summary> Unbans a user from the provided server. </summary> | |||
public Task Unban(string server, User user) | |||
=> Unban(server, user?.Id); | |||
/// <summary> Unbans a user from the provided server. </summary> | |||
public async Task Unban(string serverId, string userId) | |||
{ | |||
CheckReady(); | |||
if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
if (userId == null) throw new ArgumentNullException(nameof(userId)); | |||
try { await _api.Unban(serverId, userId); } | |||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||
} | |||
//Invites | |||
/// <summary> Creates a new invite to the default channel of the provided server. </summary> | |||
/// <param name="maxAge"> Time (in seconds) until the invite expires. Set to 0 to never expire. </param> | |||
/// <param name="isTemporary"> If true, a user accepting this invite will be kicked from the server after closing their client. </param> | |||
/// <param name="hasXkcdPass"> 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. </param> | |||
public Task<Invite> CreateInvite(Server server, int maxAge, int maxUses, bool isTemporary, bool hasXkcdPass) | |||
=> CreateInvite(server?.DefaultChannelId, maxAge, maxUses, isTemporary, hasXkcdPass); | |||
/// <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="isTemporary"> If true, a user accepting this invite will be kicked from the server after closing their client. </param> | |||
/// <param name="hasXkcdPass"> 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. </param> | |||
public Task<Invite> CreateInvite(Channel channel, int maxAge, int maxUses, bool isTemporary, bool hasXkcdPass) | |||
=> CreateInvite(channel?.Id, maxAge, maxUses, isTemporary, hasXkcdPass); | |||
/// <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="isTemporary"> If true, a user accepting this invite will be kicked from the server after closing their client. </param> | |||
/// <param name="hasXkcdPass"> 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. </param> | |||
public async Task<Invite> CreateInvite(string channelId, int maxAge, int maxUses, bool isTemporary, bool hasXkcdPass) | |||
{ | |||
CheckReady(); | |||
if (channelId == null) throw new ArgumentNullException(nameof(channelId)); | |||
if (maxAge <= 0) throw new ArgumentOutOfRangeException(nameof(maxAge)); | |||
if (maxUses <= 0) throw new ArgumentOutOfRangeException(nameof(maxUses)); | |||
var response = await _api.CreateInvite(channelId, maxAge, maxUses, isTemporary, hasXkcdPass); | |||
_channels.Update(response.Channel.Id, response.Server.Id, response.Channel); | |||
_servers.Update(response.Server.Id, response.Server); | |||
_users.Update(response.Inviter.Id, response.Inviter); | |||
return new Invite(response.Code, response.XkcdPass, this) | |||
{ | |||
ChannelId = response.Channel.Id, | |||
InviterId = response.Inviter.Id, | |||
ServerId = response.Server.Id, | |||
IsRevoked = response.IsRevoked, | |||
IsTemporary = response.IsTemporary, | |||
MaxAge = response.MaxAge, | |||
MaxUses = response.MaxUses, | |||
Uses = response.Uses | |||
}; | |||
} | |||
/// <summary> Gets more info about the provided invite. </summary> | |||
/// <remarks> Supported formats: inviteCode, xkcdCode, https://discord.gg/inviteCode, https://discord.gg/xkcdCode </remarks> | |||
public async Task<Invite> GetInvite(string id) | |||
{ | |||
CheckReady(); | |||
if (id == null) throw new ArgumentNullException(nameof(id)); | |||
var response = await _api.GetInvite(id); | |||
return new Invite(response.Code, response.XkcdPass, this) | |||
{ | |||
ChannelId = response.Channel.Id, | |||
InviterId = response.Inviter.Id, | |||
ServerId = response.Server.Id | |||
}; | |||
} | |||
/// <summary> Accepts the provided invite. </summary> | |||
public Task AcceptInvite(Invite invite) | |||
{ | |||
CheckReady(); | |||
if (invite == null) throw new ArgumentNullException(nameof(invite)); | |||
return _api.AcceptInvite(invite.Id); | |||
} | |||
/// <summary> Accepts the provided invite. </summary> | |||
public async Task AcceptInvite(string id) | |||
{ | |||
CheckReady(); | |||
if (id == null) throw new ArgumentNullException(nameof(id)); | |||
//Remove Url Parts | |||
if (id.StartsWith(Endpoints.BaseShortHttps)) | |||
id = id.Substring(Endpoints.BaseShortHttps.Length); | |||
if (id.Length > 0 && id[0] == '/') | |||
id = id.Substring(1); | |||
if (id.Length > 0 && id[id.Length - 1] == '/') | |||
id = id.Substring(0, id.Length - 1); | |||
//Check if this is a human-readable link and get its ID | |||
var response = await _api.GetInvite(id); | |||
await _api.AcceptInvite(response.Code); | |||
} | |||
/// <summary> Deletes the provided invite. </summary> | |||
public async Task DeleteInvite(string id) | |||
{ | |||
CheckReady(); | |||
if (id == null) throw new ArgumentNullException(nameof(id)); | |||
try | |||
{ | |||
//Check if this is a human-readable link and get its ID | |||
var response = await _api.GetInvite(id); | |||
await _api.DeleteInvite(response.Code); | |||
} | |||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||
} | |||
//Chat | |||
/// <summary> Sends a message to the provided channel. </summary> | |||
public Task<Message[]> SendMessage(Channel channel, string text) | |||
=> SendMessage(channel?.Id, text, new string[0]); | |||
/// <summary> Sends a message to the provided channel. </summary> | |||
public Task<Message[]> SendMessage(string channelId, string text) | |||
=> SendMessage(channelId, text, new string[0]); | |||
/// <summary> Sends a message to the provided channel, mentioning certain users. </summary> | |||
/// <remarks> While not required, it is recommended to include a mention reference in the text (see User.Mention). </remarks> | |||
public Task<Message[]> SendMessage(Channel channel, string text, string[] mentions) | |||
=> SendMessage(channel?.Id, text, mentions); | |||
/// <summary> Sends a message to the provided channel, mentioning certain users. </summary> | |||
/// <remarks> While not required, it is recommended to include a mention reference in the text (see User.Mention). </remarks> | |||
public async Task<Message[]> SendMessage(string channelId, string text, string[] mentions) | |||
{ | |||
CheckReady(); | |||
if (channelId == null) throw new ArgumentNullException(nameof(channelId)); | |||
if (text == null) throw new ArgumentNullException(nameof(text)); | |||
if (mentions == null) throw new ArgumentNullException(nameof(mentions)); | |||
int blockCount = (int)Math.Ceiling(text.Length / (double)DiscordAPI.MaxMessageSize); | |||
Message[] result = new Message[blockCount]; | |||
for (int i = 0; i < blockCount; i++) | |||
{ | |||
int index = i * DiscordAPI.MaxMessageSize; | |||
string blockText = text.Substring(index, Math.Min(2000, text.Length - index)); | |||
var nonce = GenerateNonce(); | |||
if (_config.UseMessageQueue) | |||
{ | |||
var msg = _messages.Update("nonce_" + nonce, channelId, new API.Models.Message | |||
{ | |||
Content = blockText, | |||
Timestamp = DateTime.UtcNow, | |||
Author = new UserReference { Avatar = User.AvatarId, Discriminator = User.Discriminator, Id = User.Id, Username = User.Name }, | |||
ChannelId = channelId | |||
}); | |||
msg.IsQueued = true; | |||
msg.Nonce = nonce; | |||
result[i] = msg; | |||
_pendingMessages.Enqueue(msg); | |||
} | |||
else | |||
{ | |||
var msg = await _api.SendMessage(channelId, blockText, mentions, nonce); | |||
result[i] = _messages.Update(msg.Id, channelId, msg); | |||
result[i].Nonce = nonce; | |||
try { RaiseMessageSent(result[i]); } catch { } | |||
} | |||
await Task.Delay(1000); | |||
} | |||
return result; | |||
} | |||
/// <summary> Edits a message the provided message. </summary> | |||
public Task EditMessage(Message message, string text) | |||
=> EditMessage(message?.ChannelId, message?.Id, text, new string[0]); | |||
/// <summary> Edits a message the provided message. </summary> | |||
public Task EditMessage(Channel channel, string messageId, string text) | |||
=> EditMessage(channel?.Id, messageId, text, new string[0]); | |||
/// <summary> Edits a message the provided message. </summary> | |||
public Task EditMessage(string channelId, string messageId, string text) | |||
=> EditMessage(channelId, messageId, text, new string[0]); | |||
/// <summary> Edits a message the provided message, mentioning certain users. </summary> | |||
/// <remarks> While not required, it is recommended to include a mention reference in the text (see User.Mention). </remarks> | |||
public Task EditMessage(Message message, string text, string[] mentions) | |||
=> EditMessage(message?.ChannelId, message?.Id, text, mentions); | |||
/// <summary> Edits a message the provided message, mentioning certain users. </summary> | |||
/// <remarks> While not required, it is recommended to include a mention reference in the text (see User.Mention). </remarks> | |||
public Task EditMessage(Channel channel, string messageId, string text, string[] mentions) | |||
=> EditMessage(channel?.Id, messageId, text, mentions); | |||
/// <summary> Edits a message the provided message, mentioning certain users. </summary> | |||
/// <remarks> While not required, it is recommended to include a mention reference in the text (see User.Mention). </remarks> | |||
public async Task EditMessage(string channelId, string messageId, string text, string[] mentions) | |||
{ | |||
CheckReady(); | |||
if (channelId == null) throw new ArgumentNullException(nameof(channelId)); | |||
if (messageId == null) throw new ArgumentNullException(nameof(messageId)); | |||
if (text == null) throw new ArgumentNullException(nameof(text)); | |||
if (mentions == null) throw new ArgumentNullException(nameof(mentions)); | |||
if (text.Length > DiscordAPI.MaxMessageSize) | |||
text = text.Substring(0, DiscordAPI.MaxMessageSize); | |||
var msg = await _api.EditMessage(channelId, messageId, text, mentions); | |||
_messages.Update(msg.Id, channelId, msg); | |||
} | |||
/// <summary> Deletes the provided message. </summary> | |||
public Task DeleteMessage(Message msg) | |||
=> DeleteMessage(msg?.ChannelId, msg?.Id); | |||
/// <summary> Deletes the provided message. </summary> | |||
public async Task<Message> DeleteMessage(string channelId, string msgId) | |||
{ | |||
CheckReady(); | |||
if (channelId == null) throw new ArgumentNullException(nameof(channelId)); | |||
if (msgId == null) throw new ArgumentNullException(nameof(msgId)); | |||
try | |||
{ | |||
await _api.DeleteMessage(channelId, msgId); | |||
return _messages.Remove(msgId); | |||
} | |||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.InternalServerError) { } //TODO: Remove me - temporary fix for deleting nonexisting messages | |||
return null; | |||
} | |||
/// <summary> Sends a file to the provided channel. </summary> | |||
public Task SendFile(Channel channel, string path) | |||
=> SendFile(channel?.Id, path); | |||
/// <summary> Sends a file to the provided channel. </summary> | |||
public Task SendFile(string channelId, string path) | |||
{ | |||
if (path == null) throw new ArgumentNullException(nameof(path)); | |||
return SendFile(channelId, File.OpenRead(path), Path.GetFileName(path)); | |||
} | |||
/// <summary> Reads a stream and sends it to the provided channel as a file. </summary> | |||
/// <remarks> It is highly recommended that this stream be cached in memory or on disk, or the request may time out. </remarks> | |||
public Task SendFile(Channel channel, Stream stream, string filename = null) | |||
=> SendFile(channel?.Id, stream, filename); | |||
/// <summary> Reads a stream and sends it to the provided channel as a file. </summary> | |||
/// <remarks> It is highly recommended that this stream be cached in memory or on disk, or the request may time out. </remarks> | |||
public Task SendFile(string channelId, Stream stream, string filename = null) | |||
{ | |||
CheckReady(); | |||
if (channelId == null) throw new ArgumentNullException(nameof(channelId)); | |||
if (stream == null) throw new ArgumentNullException(nameof(stream)); | |||
if (filename == null) throw new ArgumentNullException(nameof(filename)); | |||
return _api.SendFile(channelId, stream, filename); | |||
} | |||
/// <summary> Downloads last count messages from the server, starting at beforeMessageId if it's provided. </summary> | |||
public Task<Message[]> DownloadMessages(Channel channel, int count, string beforeMessageId = null) | |||
=> DownloadMessages(channel.Id, count); | |||
/// <summary> Downloads last count messages from the server, starting at beforeMessageId if it's provided. </summary> | |||
public async Task<Message[]> DownloadMessages(string channelId, int count, string beforeMessageId = null) | |||
{ | |||
CheckReady(); | |||
if (channelId == null) throw new NullReferenceException(nameof(channelId)); | |||
if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); | |||
if (count == 0) return new Message[0]; | |||
Channel channel = GetChannel(channelId); | |||
if (channel != null && channel.Type == ChannelTypes.Text) | |||
{ | |||
try | |||
{ | |||
var msgs = await _api.GetMessages(channel.Id, count); | |||
return msgs.OrderBy(x => x.Timestamp) | |||
.Select(x => | |||
{ | |||
var msg = _messages.Update(x.Id, x.ChannelId, x); | |||
var user = msg.User; | |||
if (user != null) | |||
user.UpdateActivity(x.Timestamp); | |||
return msg; | |||
}) | |||
.ToArray(); | |||
} | |||
catch (HttpException) { } //Bad Permissions? | |||
} | |||
return null; | |||
} | |||
//Voice | |||
/// <summary> Mutes a user on the provided server. </summary> | |||
public Task Mute(Server server, User user) | |||
=> Mute(server?.Id, user?.Id); | |||
/// <summary> Mutes a user on the provided server. </summary> | |||
public Task Mute(Server server, string userId) | |||
=> Mute(server?.Id, userId); | |||
/// <summary> Mutes a user on the provided server. </summary> | |||
public Task Mute(string server, User user) | |||
=> Mute(server, user?.Id); | |||
/// <summary> Mutes a user on the provided server. </summary> | |||
public Task Mute(string serverId, string userId) | |||
{ | |||
CheckReady(); | |||
if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
if (userId == null) throw new ArgumentNullException(nameof(userId)); | |||
return _api.Mute(serverId, userId); | |||
} | |||
/// <summary> Unmutes a user on the provided server. </summary> | |||
public Task Unmute(Server server, User user) | |||
=> Unmute(server?.Id, user?.Id); | |||
/// <summary> Unmutes a user on the provided server. </summary> | |||
public Task Unmute(Server server, string userId) | |||
=> Unmute(server?.Id, userId); | |||
/// <summary> Unmutes a user on the provided server. </summary> | |||
public Task Unmute(string server, User user) | |||
=> Unmute(server, user?.Id); | |||
/// <summary> Unmutes a user on the provided server. </summary> | |||
public Task Unmute(string serverId, string userId) | |||
{ | |||
CheckReady(); | |||
if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
if (userId == null) throw new ArgumentNullException(nameof(userId)); | |||
return _api.Unmute(serverId, userId); | |||
} | |||
/// <summary> Deafens a user on the provided server. </summary> | |||
public Task Deafen(Server server, User user) | |||
=> Deafen(server?.Id, user?.Id); | |||
/// <summary> Deafens a user on the provided server. </summary> | |||
public Task Deafen(Server server, string userId) | |||
=> Deafen(server?.Id, userId); | |||
/// <summary> Deafens a user on the provided server. </summary> | |||
public Task Deafen(string server, User user) | |||
=> Deafen(server, user?.Id); | |||
/// <summary> Deafens a user on the provided server. </summary> | |||
public Task Deafen(string serverId, string userId) | |||
{ | |||
CheckReady(); | |||
if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
if (userId == null) throw new ArgumentNullException(nameof(userId)); | |||
return _api.Deafen(serverId, userId); | |||
} | |||
/// <summary> Undeafens a user on the provided server. </summary> | |||
public Task Undeafen(Server server, User user) | |||
=> Undeafen(server?.Id, user?.Id); | |||
/// <summary> Undeafens a user on the provided server. </summary> | |||
public Task Undeafen(Server server, string userId) | |||
=> Undeafen(server?.Id, userId); | |||
/// <summary> Undeafens a user on the provided server. </summary> | |||
public Task Undeafen(string server, User user) | |||
=> Undeafen(server, user?.Id); | |||
/// <summary> Undeafens a user on the provided server. </summary> | |||
public Task Undeafen(string serverId, string userId) | |||
{ | |||
CheckReady(); | |||
if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
if (userId == null) throw new ArgumentNullException(nameof(userId)); | |||
return _api.Undeafen(serverId, userId); | |||
} | |||
#if !DNXCORE50 | |||
public Task JoinVoiceServer(Server server, Channel channel) | |||
=> JoinVoiceServer(server?.Id, channel?.Id); | |||
public Task JoinVoiceServer(Server server, string channelId) | |||
=> JoinVoiceServer(server?.Id, channelId); | |||
public Task JoinVoiceServer(string serverId, Channel channel) | |||
=> JoinVoiceServer(serverId, channel?.Id); | |||
public async Task JoinVoiceServer(string serverId, string channelId) | |||
{ | |||
CheckReady(); | |||
if (!_config.EnableVoice) throw new InvalidOperationException("Voice is not enabled for this client."); | |||
if (serverId == null) throw new ArgumentNullException(nameof(serverId)); | |||
if (channelId == null) throw new ArgumentNullException(nameof(channelId)); | |||
await LeaveVoiceServer(); | |||
_currentVoiceServerId = serverId; | |||
_webSocket.JoinVoice(serverId, channelId); | |||
} | |||
public async Task LeaveVoiceServer() | |||
{ | |||
if (!_config.EnableVoice) throw new InvalidOperationException("Voice is not enabled for this client."); | |||
await _voiceWebSocket.DisconnectAsync(); | |||
if (_currentVoiceServerId != null) | |||
_webSocket.LeaveVoice(); | |||
_currentVoiceServerId = null; | |||
_currentVoiceToken = null; | |||
} | |||
/// <summary> Sends a PCM frame to the voice server. </summary> | |||
/// <param name="data">PCM frame to send.</param> | |||
/// <param name="count">Number of bytes in this frame. </param> | |||
public void SendVoicePCM(byte[] data, int count) | |||
{ | |||
CheckReady(); | |||
if (!_config.EnableVoice) throw new InvalidOperationException("Voice is not enabled for this client."); | |||
if (count == 0) return; | |||
if (_isDebugMode) | |||
RaiseOnDebugMessage(DebugMessageType.VoiceOutput, $"Queued {count} bytes for voice output."); | |||
_voiceWebSocket.SendPCMFrame(data, count); | |||
} | |||
/// <summary> Clears the PCM buffer. </summary> | |||
public void ClearVoicePCM() | |||
{ | |||
CheckReady(); | |||
if (!_config.EnableVoice) throw new InvalidOperationException("Voice is not enabled for this client."); | |||
if (_isDebugMode) | |||
RaiseOnDebugMessage(DebugMessageType.VoiceOutput, $"Cleared the voice buffer."); | |||
_voiceWebSocket.ClearPCMFrames(); | |||
} | |||
#endif | |||
//Profile | |||
/// <summary> Changes your username to newName. </summary> | |||
public async Task ChangeUsername(string newName, string currentEmail, string currentPassword) | |||
{ | |||
CheckReady(); | |||
var response = await _api.ChangeUsername(newName, currentEmail, currentPassword); | |||
_users.Update(response.Id, response); | |||
} | |||
/// <summary> Changes your email to newEmail. </summary> | |||
public async Task ChangeEmail(string newEmail, string currentPassword) | |||
{ | |||
CheckReady(); | |||
var response = await _api.ChangeEmail(newEmail, currentPassword); | |||
_users.Update(response.Id, response); | |||
} | |||
/// <summary> Changes your password to newPassword. </summary> | |||
public async Task ChangePassword(string newPassword, string currentEmail, string currentPassword) | |||
{ | |||
CheckReady(); | |||
var response = await _api.ChangePassword(newPassword, currentEmail, currentPassword); | |||
_users.Update(response.Id, response); | |||
} | |||
/// <summary> Changes your avatar. </summary> | |||
/// <remarks>Only supports PNG and JPEG (see AvatarImageType)</remarks> | |||
public async Task ChangeAvatar(AvatarImageType imageType, byte[] bytes, string currentEmail, string currentPassword) | |||
{ | |||
CheckReady(); | |||
var response = await _api.ChangeAvatar(imageType, bytes, currentEmail, currentPassword); | |||
_users.Update(response.Id, response); | |||
} | |||
} | |||
} |
@@ -0,0 +1,457 @@ | |||
using Discord.API.Models; | |||
using Discord.Helpers; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
namespace Discord | |||
{ | |||
public partial class DiscordClient | |||
{ | |||
/// <summary> Returns a collection of all users the client can see across all servers. </summary> | |||
/// <remarks> This collection does not guarantee any ordering. </remarks> | |||
public IEnumerable<User> Users => _users; | |||
private AsyncCache<User, API.Models.UserReference> _users; | |||
/// <summary> Returns a collection of all servers the client is a member of. </summary> | |||
/// <remarks> This collection does not guarantee any ordering. </remarks> | |||
public IEnumerable<Server> Servers => _servers; | |||
private AsyncCache<Server, API.Models.ServerReference> _servers; | |||
/// <summary> Returns a collection of all channels the client can see across all servers. </summary> | |||
/// <remarks> This collection does not guarantee any ordering. </remarks> | |||
public IEnumerable<Channel> Channels => _channels; | |||
private AsyncCache<Channel, API.Models.ChannelReference> _channels; | |||
/// <summary> Returns a collection of all messages the client has in cache. </summary> | |||
/// <remarks> This collection does not guarantee any ordering. </remarks> | |||
public IEnumerable<Message> Messages => _messages; | |||
private AsyncCache<Message, API.Models.MessageReference> _messages; | |||
/// <summary> Returns a collection of all roles the client can see across all servers. </summary> | |||
/// <remarks> This collection does not guarantee any ordering. </remarks> | |||
public IEnumerable<Role> Roles => _roles; | |||
private AsyncCache<Role, API.Models.Role> _roles; | |||
private void CreateCaches() | |||
{ | |||
_servers = new AsyncCache<Server, API.Models.ServerReference>( | |||
(key, parentKey) => | |||
{ | |||
if (_isDebugMode) | |||
RaiseOnDebugMessage(DebugMessageType.Cache, $"Created server {key}."); | |||
return new Server(key, this); | |||
}, | |||
(server, model) => | |||
{ | |||
server.Name = model.Name; | |||
_channels.Update(server.DefaultChannelId, server.Id, null); | |||
if (model is ExtendedServerInfo) | |||
{ | |||
var extendedModel = model as ExtendedServerInfo; | |||
server.AFKChannelId = extendedModel.AFKChannelId; | |||
server.AFKTimeout = extendedModel.AFKTimeout; | |||
server.JoinedAt = extendedModel.JoinedAt ?? DateTime.MinValue; | |||
server.OwnerId = extendedModel.OwnerId; | |||
server.Region = extendedModel.Region; | |||
foreach (var role in extendedModel.Roles) | |||
_roles.Update(role.Id, model.Id, role); | |||
foreach (var channel in extendedModel.Channels) | |||
_channels.Update(channel.Id, model.Id, channel); | |||
foreach (var membership in extendedModel.Members) | |||
{ | |||
_users.Update(membership.User.Id, membership.User); | |||
server.UpdateMember(membership); | |||
} | |||
foreach (var membership in extendedModel.VoiceStates) | |||
server.UpdateMember(membership); | |||
foreach (var membership in extendedModel.Presences) | |||
server.UpdateMember(membership); | |||
} | |||
if (_isDebugMode) | |||
RaiseOnDebugMessage(DebugMessageType.Cache, $"Updated server {server.Name} ({server.Id})."); | |||
}, | |||
server => | |||
{ | |||
if (_isDebugMode) | |||
RaiseOnDebugMessage(DebugMessageType.Cache, $"Destroyed server {server.Name} ({server.Id})."); | |||
} | |||
); | |||
_channels = new AsyncCache<Channel, API.Models.ChannelReference>( | |||
(key, parentKey) => | |||
{ | |||
if (_isDebugMode) | |||
{ | |||
if (parentKey != null) | |||
RaiseOnDebugMessage(DebugMessageType.Cache, $"Created channel {key} in server {parentKey}."); | |||
else | |||
RaiseOnDebugMessage(DebugMessageType.Cache, $"Created private channel {key}."); | |||
} | |||
return new Channel(key, parentKey, this); | |||
}, | |||
(channel, model) => | |||
{ | |||
channel.Name = model.Name; | |||
channel.Type = model.Type; | |||
if (model is ChannelInfo) | |||
{ | |||
var extendedModel = model as ChannelInfo; | |||
channel.Position = extendedModel.Position; | |||
if (extendedModel.IsPrivate) | |||
{ | |||
var user = _users.Update(extendedModel.Recipient.Id, extendedModel.Recipient); | |||
channel.RecipientId = user.Id; | |||
user.PrivateChannelId = channel.Id; | |||
} | |||
if (extendedModel.PermissionOverwrites != null) | |||
{ | |||
channel.PermissionOverwrites = extendedModel.PermissionOverwrites.Select(x => new Channel.PermissionOverwrite | |||
{ | |||
Type = x.Type, | |||
Id = x.Id, | |||
Deny = new PackedPermissions(x.Deny), | |||
Allow = new PackedPermissions(x.Allow) | |||
}).ToArray(); | |||
} | |||
else | |||
channel.PermissionOverwrites = null; | |||
} | |||
if (_isDebugMode) | |||
{ | |||
if (channel.IsPrivate) | |||
RaiseOnDebugMessage(DebugMessageType.Cache, $"Updated private channel {channel.Name} ({channel.Id})."); | |||
else | |||
RaiseOnDebugMessage(DebugMessageType.Cache, $"Updated channel {channel.Name} ({channel.Id}) in server {channel.Server?.Name} ({channel.ServerId})."); | |||
} | |||
}, | |||
channel => | |||
{ | |||
if (channel.IsPrivate) | |||
{ | |||
var user = channel.Recipient; | |||
if (user.PrivateChannelId == channel.Id) | |||
user.PrivateChannelId = null; | |||
if (_isDebugMode) | |||
RaiseOnDebugMessage(DebugMessageType.Cache, $"Destroyed private channel {channel.Name} ({channel.Id})."); | |||
} | |||
else | |||
{ | |||
if (_isDebugMode) | |||
RaiseOnDebugMessage(DebugMessageType.Cache, $"Destroyed channel {channel.Name} ({channel.Id}) in server {channel.Server?.Name} ({channel.ServerId})."); | |||
} | |||
}); | |||
_messages = new AsyncCache<Message, API.Models.MessageReference>( | |||
(key, parentKey) => | |||
{ | |||
if (_isDebugMode) | |||
RaiseOnDebugMessage(DebugMessageType.Cache, $"Created message {key} in channel {parentKey}."); | |||
return new Message(key, parentKey, this); | |||
}, | |||
(message, model) => | |||
{ | |||
if (model is API.Models.Message) | |||
{ | |||
var extendedModel = model as API.Models.Message; | |||
if (extendedModel.Attachments != null) | |||
{ | |||
message.Attachments = extendedModel.Attachments.Select(x => new Message.Attachment | |||
{ | |||
Id = x.Id, | |||
Url = x.Url, | |||
ProxyUrl = x.ProxyUrl, | |||
Size = x.Size, | |||
Filename = x.Filename, | |||
Width = x.Width, | |||
Height = x.Height | |||
}).ToArray(); | |||
} | |||
else | |||
message.Attachments = new Message.Attachment[0]; | |||
if (extendedModel.Embeds != null) | |||
{ | |||
message.Embeds = extendedModel.Embeds.Select(x => | |||
{ | |||
var embed = new Message.Embed | |||
{ | |||
Url = x.Url, | |||
Type = x.Type, | |||
Description = x.Description, | |||
Title = x.Title | |||
}; | |||
if (x.Provider != null) | |||
{ | |||
embed.Provider = new Message.EmbedReference | |||
{ | |||
Url = x.Provider.Url, | |||
Name = x.Provider.Name | |||
}; | |||
} | |||
if (x.Author != null) | |||
{ | |||
embed.Author = new Message.EmbedReference | |||
{ | |||
Url = x.Author.Url, | |||
Name = x.Author.Name | |||
}; | |||
} | |||
if (x.Thumbnail != null) | |||
{ | |||
embed.Thumbnail = new Message.File | |||
{ | |||
Url = x.Thumbnail.Url, | |||
ProxyUrl = x.Thumbnail.ProxyUrl, | |||
Width = x.Thumbnail.Width, | |||
Height = x.Thumbnail.Height | |||
}; | |||
} | |||
return embed; | |||
}).ToArray(); | |||
} | |||
else | |||
message.Embeds = new Message.Embed[0]; | |||
message.IsMentioningEveryone = extendedModel.IsMentioningEveryone; | |||
message.IsTTS = extendedModel.IsTextToSpeech; | |||
message.MentionIds = extendedModel.Mentions?.Select(x => x.Id)?.ToArray() ?? new string[0]; | |||
message.IsMentioningMe = message.MentionIds.Contains(_myId); | |||
message.RawText = extendedModel.Content; | |||
message.Timestamp = extendedModel.Timestamp; | |||
message.EditedTimestamp = extendedModel.EditedTimestamp; | |||
if (extendedModel.Author != null) | |||
message.UserId = extendedModel.Author.Id; | |||
} | |||
if (_isDebugMode) | |||
RaiseOnDebugMessage(DebugMessageType.Cache, $"Updated message {message.Id} in channel {message.Channel?.Name} ({message.ChannelId})."); | |||
}, | |||
message => | |||
{ | |||
if (_isDebugMode) | |||
RaiseOnDebugMessage(DebugMessageType.Cache, $"Destroyed message {message.Id} in channel {message.Channel?.Name} ({message.ChannelId})."); | |||
} | |||
); | |||
_roles = new AsyncCache<Role, API.Models.Role>( | |||
(key, parentKey) => | |||
{ | |||
if (_isDebugMode) | |||
RaiseOnDebugMessage(DebugMessageType.Cache, $"Created role {key} in server {parentKey}."); | |||
return new Role(key, parentKey, this); | |||
}, | |||
(role, model) => | |||
{ | |||
role.Name = model.Name; | |||
role.Permissions.RawValue = (uint)model.Permissions; | |||
if (_isDebugMode) | |||
RaiseOnDebugMessage(DebugMessageType.Cache, $"Updated role {role.Name} ({role.Id}) in server {role.Server?.Name} ({role.ServerId})."); | |||
}, | |||
role => | |||
{ | |||
if (_isDebugMode) | |||
RaiseOnDebugMessage(DebugMessageType.Cache, $"Destroyed role {role.Name} ({role.Id}) in server {role.Server?.Name} ({role.ServerId})."); | |||
} | |||
); | |||
_users = new AsyncCache<User, API.Models.UserReference>( | |||
(key, parentKey) => | |||
{ | |||
if (_isDebugMode) | |||
RaiseOnDebugMessage(DebugMessageType.Cache, $"Created user {key}."); | |||
return new User(key, this); | |||
}, | |||
(user, model) => | |||
{ | |||
user.AvatarId = model.Avatar; | |||
user.Discriminator = model.Discriminator; | |||
user.Name = model.Username; | |||
if (model is SelfUserInfo) | |||
{ | |||
var extendedModel = model as SelfUserInfo; | |||
user.Email = extendedModel.Email; | |||
user.IsVerified = extendedModel.IsVerified; | |||
} | |||
if (_isDebugMode) | |||
RaiseOnDebugMessage(DebugMessageType.Cache, $"Updated user {user?.Name} ({user.Id})."); | |||
}, | |||
user => | |||
{ | |||
if (_isDebugMode) | |||
RaiseOnDebugMessage(DebugMessageType.Cache, $"Destroyed user {user?.Name} ({user.Id})."); | |||
} | |||
); | |||
} | |||
/// <summary> Returns the user with the specified id, or null if none was found. </summary> | |||
public User GetUser(string id) => _users[id]; | |||
/// <summary> Returns the user with the specified name and discriminator, or null if none was found. </summary> | |||
/// <remarks> Name formats supported: Name and @Name. Search is case-insensitive. </remarks> | |||
public User GetUser(string name, string discriminator) | |||
{ | |||
if (name == null || discriminator == null) | |||
return null; | |||
if (name.StartsWith("@")) | |||
name = name.Substring(1); | |||
return _users | |||
.Where(x => | |||
string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) && | |||
x.Discriminator == discriminator | |||
) | |||
.FirstOrDefault(); | |||
} | |||
/// <summary> Returns all users with the specified name across all servers. </summary> | |||
/// <remarks> Name formats supported: Name and @Name. Search is case-insensitive. </remarks> | |||
public IEnumerable<User> FindUsers(string name) | |||
{ | |||
if (name == null) | |||
return new User[0]; | |||
if (name.StartsWith("@")) | |||
{ | |||
string name2 = name.Substring(1); | |||
return _users.Where(x => | |||
string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) || string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); | |||
} | |||
else | |||
{ | |||
return _users.Where(x => | |||
string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); | |||
} | |||
} | |||
/// <summary> Returns the user with the specified id, along with their server-specific data, or null if none was found. </summary> | |||
public Membership GetMember(string serverId, User user) | |||
=> GetMember(_servers[serverId], user?.Id); | |||
/// <summary> Returns the user with the specified id, along with their server-specific data, or null if none was found. </summary> | |||
public Membership GetMember(string serverId, string userId) | |||
=> GetMember(_servers[serverId], userId); | |||
/// <summary> Returns the user with the specified id, along with their server-specific data, or null if none was found. </summary> | |||
public Membership GetMember(Server server, User user) | |||
=> GetMember(server, user?.Id); | |||
/// <summary> Returns the user with the specified id, along with their server-specific data, or null if none was found. </summary> | |||
public Membership GetMember(Server server, string userId) | |||
{ | |||
if (server == null || userId == null) | |||
return null; | |||
return server.GetMember(userId); | |||
} | |||
/// <summary> Returns all users in with the specified server and name, along with their server-specific data. </summary> | |||
/// <remarks> Name formats supported: Name and @Name. Search is case-insensitive.</remarks> | |||
public IEnumerable<Membership> FindMembers(string serverId, string name) | |||
=> FindMembers(GetServer(serverId), name); | |||
/// <summary> Returns all users in with the specified server and name, along with their server-specific data. </summary> | |||
/// <remarks> Name formats supported: Name and @Name. Search is case-insensitive.</remarks> | |||
public IEnumerable<Membership> FindMembers(Server server, string name) | |||
{ | |||
if (server == null || name == null) | |||
return new Membership[0]; | |||
if (name.StartsWith("@")) | |||
{ | |||
string name2 = name.Substring(1); | |||
return server.Members.Where(x => | |||
{ | |||
var user = x.User; | |||
return string.Equals(user.Name, name, StringComparison.OrdinalIgnoreCase) || string.Equals(user.Name, name2, StringComparison.OrdinalIgnoreCase); | |||
}); | |||
} | |||
else | |||
{ | |||
return server.Members.Where(x => | |||
string.Equals(x.User.Name, name, StringComparison.OrdinalIgnoreCase)); | |||
} | |||
} | |||
/// <summary> Returns the server with the specified id, or null if none was found. </summary> | |||
public Server GetServer(string id) => _servers[id]; | |||
/// <summary> Returns all servers with the specified name. </summary> | |||
/// <remarks> Search is case-insensitive. </remarks> | |||
public IEnumerable<Server> FindServers(string name) | |||
{ | |||
if (name == null) | |||
return new Server[0]; | |||
return _servers.Where(x => | |||
string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); | |||
} | |||
/// <summary> Returns the channel with the specified id, or null if none was found. </summary> | |||
public Channel GetChannel(string id) => _channels[id]; | |||
/// <summary> Returns a private channel with the provided user. </summary> | |||
public Task<Channel> GetPMChannel(string userId, bool createIfNotExists = false) | |||
=> GetPMChannel(_users[userId], createIfNotExists); | |||
/// <summary> Returns a private channel with the provided user. </summary> | |||
public async Task<Channel> GetPMChannel(User user, bool createIfNotExists = false) | |||
{ | |||
if (user == null) | |||
{ | |||
if (createIfNotExists) | |||
throw new ArgumentNullException(nameof(user)); | |||
else | |||
return null; | |||
} | |||
var channel = user.PrivateChannel; | |||
if (channel == null && createIfNotExists) | |||
await CreatePMChannel(user); | |||
return channel; | |||
} | |||
/// <summary> Returns all channels with the specified server and name. </summary> | |||
/// <remarks> Name formats supported: Name and #Name. Search is case-insensitive. </remarks> | |||
public IEnumerable<Channel> FindChannels(Server server, string name) | |||
=> FindChannels(server?.Id, name); | |||
/// <summary> Returns all channels with the specified server and name. </summary> | |||
/// <remarks> Name formats supported: Name and #Name. Search is case-insensitive. </remarks> | |||
public IEnumerable<Channel> FindChannels(string serverId, string name) | |||
{ | |||
if (serverId == null || name == null) | |||
return new Channel[0]; | |||
if (name.StartsWith("#")) | |||
{ | |||
string name2 = name.Substring(1); | |||
return _channels.Where(x => x.ServerId == serverId && | |||
string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) || string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); | |||
} | |||
else | |||
{ | |||
return _channels.Where(x => x.ServerId == serverId && | |||
string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); | |||
} | |||
} | |||
/// <summary> Returns the role with the specified id, or null if none was found. </summary> | |||
public Role GetRole(string id) => _roles[id]; | |||
/// <summary> Returns all roles with the specified server and name. </summary> | |||
/// <remarks> Name formats supported: Name and @Name. Search is case-insensitive. </remarks> | |||
public IEnumerable<Role> FindRoles(Server server, string name) | |||
=> FindRoles(server?.Id, name); | |||
/// <summary> Returns all roles with the specified server and name. </summary> | |||
/// <remarks> Name formats supported: Name and @Name. Search is case-insensitive. </remarks> | |||
public IEnumerable<Role> FindRoles(string serverId, string name) | |||
{ | |||
if (serverId == null || name == null) | |||
return new Role[0]; | |||
if (name.StartsWith("@")) | |||
{ | |||
string name2 = name.Substring(1); | |||
return _roles.Where(x => x.ServerId == serverId && | |||
string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) || string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); | |||
} | |||
else | |||
{ | |||
return _roles.Where(x => x.ServerId == serverId && | |||
string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); | |||
} | |||
} | |||
/// <summary> Returns the message with the specified id, or null if none was found. </summary> | |||
public Message GetMessage(string id) => _messages[id]; | |||
} | |||
} |
@@ -2,6 +2,12 @@ | |||
namespace Discord | |||
{ | |||
public class DisconnectedEventArgs : EventArgs | |||
{ | |||
public readonly bool WasUnexpected; | |||
internal DisconnectedEventArgs(bool wasUnexpected) { WasUnexpected = wasUnexpected; } | |||
} | |||
internal abstract partial class DiscordWebSocket | |||
{ | |||
//Debug | |||
@@ -19,11 +25,11 @@ namespace Discord | |||
if (Connected != null) | |||
Connected(this, EventArgs.Empty); | |||
} | |||
public event EventHandler Disconnected; | |||
private void RaiseDisconnected() | |||
public event EventHandler<DisconnectedEventArgs> Disconnected; | |||
private void RaiseDisconnected(bool wasUnexpected) | |||
{ | |||
if (Disconnected != null) | |||
Disconnected(this, EventArgs.Empty); | |||
Disconnected(this, new DisconnectedEventArgs(wasUnexpected)); | |||
} | |||
} | |||
} |
@@ -18,13 +18,13 @@ namespace Discord | |||
protected readonly bool _isDebug; | |||
private readonly ConcurrentQueue<byte[]> _sendQueue; | |||
protected volatile CancellationTokenSource _disconnectToken; | |||
private volatile ClientWebSocket _webSocket; | |||
private volatile Task _tasks; | |||
protected CancellationTokenSource _disconnectToken; | |||
private ClientWebSocket _webSocket; | |||
private DateTime _lastHeartbeat; | |||
private Task _task; | |||
protected string _host; | |||
protected int _timeout, _heartbeatInterval; | |||
private DateTime _lastHeartbeat; | |||
private bool _isConnected; | |||
private bool _isConnected, _wasDisconnectedUnexpected; | |||
public DiscordWebSocket(DiscordClient client, int timeout, int interval, bool isDebug) | |||
{ | |||
@@ -54,7 +54,7 @@ namespace Discord | |||
OnConnect(); | |||
_lastHeartbeat = DateTime.UtcNow; | |||
_tasks = Task.Factory.ContinueWhenAll(CreateTasks(), x => | |||
_task = Task.Factory.ContinueWhenAll(CreateTasks(), x => | |||
{ | |||
if (_isDebug) | |||
RaiseOnDebugMessage(DebugMessageType.Connection, $"Disconnected."); | |||
@@ -64,6 +64,7 @@ namespace Discord | |||
_disconnectToken.Dispose(); | |||
_disconnectToken = null; | |||
_wasDisconnectedUnexpected = false; | |||
//Clear send queue | |||
_heartbeatInterval = 0; | |||
@@ -76,20 +77,20 @@ namespace Discord | |||
if (_isConnected) | |||
{ | |||
_isConnected = false; | |||
RaiseDisconnected(); | |||
RaiseDisconnected(_wasDisconnectedUnexpected); | |||
} | |||
_tasks = null; | |||
_task = null; | |||
}); | |||
} | |||
public Task ReconnectAsync() | |||
=> ConnectAsync(_host); | |||
public async Task DisconnectAsync() | |||
{ | |||
if (_tasks != null) | |||
if (_task != null) | |||
{ | |||
try { _disconnectToken.Cancel(); } catch (NullReferenceException) { } | |||
try { await _tasks; } catch (NullReferenceException) { } | |||
try { await _task; } catch (NullReferenceException) { } | |||
} | |||
} | |||
protected virtual void OnConnect() { } | |||
@@ -130,6 +131,7 @@ namespace Discord | |||
if (result.MessageType == WebSocketMessageType.Close) | |||
{ | |||
_wasDisconnectedUnexpected = true; | |||
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None); | |||
RaiseOnDebugMessage(DebugMessageType.Connection, $"Got Close Message ({result.CloseStatus?.ToString() ?? "Unexpected"}, {result.CloseStatusDescription ?? "No Reason"})"); | |||
return; | |||
@@ -0,0 +1,21 @@ | |||
using System; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
namespace Discord.Helpers | |||
{ | |||
internal static class Extensions | |||
{ | |||
public static async Task Wait(this CancellationTokenSource tokenSource) | |||
{ | |||
var token = tokenSource.Token; | |||
try { await Task.Delay(-1, token); } | |||
catch (OperationCanceledException) { } | |||
} | |||
public static async Task Wait(this CancellationToken token) | |||
{ | |||
try { await Task.Delay(-1, token); } | |||
catch (OperationCanceledException) { } | |||
} | |||
} | |||
} |
@@ -103,7 +103,7 @@ namespace Discord | |||
[JsonIgnore] | |||
public User User => _client.GetUser(UserId); | |||
/// <summary> Returns true if the current user created this message. </summary> | |||
public bool IsAuthor => _client.UserId == UserId; | |||
public bool IsAuthor => _client.User?.Id == UserId; | |||
internal Message(string id, string channelId, DiscordClient client) | |||
{ | |||
@@ -29,7 +29,7 @@ namespace Discord | |||
/// <summary> Returns the user that first created this server. </summary> | |||
public User Owner => _client.GetUser(OwnerId); | |||
/// <summary> Returns true if the current user created this server. </summary> | |||
public bool IsOwner => _client.UserId == OwnerId; | |||
public bool IsOwner => _client.User?.Id == OwnerId; | |||
/// <summary> Returns the id of the AFK voice channel for this server (see AFKTimeout). </summary> | |||
public string AFKChannelId { get; internal set; } | |||
@@ -69,7 +69,7 @@ namespace Discord | |||
_members = new AsyncCache<Membership, API.Models.MemberInfo>( | |||
(key, parentKey) => | |||
{ | |||
if (_client.Config.EnableDebug) | |||
if (_client.IsDebugMode) | |||
_client.RaiseOnDebugMessage(DebugMessageType.Cache, $"Created user {key} in server {parentKey}."); | |||
return new Membership(parentKey, key, _client); | |||
}, | |||
@@ -108,12 +108,12 @@ namespace Discord | |||
member.IsDeafened = extendedModel.IsDeafened; | |||
member.IsMuted = extendedModel.IsMuted; | |||
} | |||
if (_client.Config.EnableDebug) | |||
if (_client.IsDebugMode) | |||
_client.RaiseOnDebugMessage(DebugMessageType.Cache, $"Updated user {member.User?.Name} ({member.UserId}) in server {member.Server?.Name} ({member.ServerId})."); | |||
}, | |||
(member) => | |||
{ | |||
if (_client.Config.EnableDebug) | |||
if (_client.IsDebugMode) | |||
_client.RaiseOnDebugMessage(DebugMessageType.Cache, $"Destroyed user {member.User?.Name} ({member.UserId}) in server {member.Server?.Name} ({member.ServerId})."); | |||
} | |||
); | |||