| @@ -175,6 +175,9 @@ | |||||
| <Compile Include="..\Discord.Net\Format.cs"> | <Compile Include="..\Discord.Net\Format.cs"> | ||||
| <Link>Format.cs</Link> | <Link>Format.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net\Helpers\EpochTime.cs"> | |||||
| <Link>Helpers\EpochTime.cs</Link> | |||||
| </Compile> | |||||
| <Compile Include="..\Discord.Net\Helpers\Extensions.cs"> | <Compile Include="..\Discord.Net\Helpers\Extensions.cs"> | ||||
| <Link>Helpers\Extensions.cs</Link> | <Link>Helpers\Extensions.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| @@ -1,4 +1,5 @@ | |||||
| using Discord.API; | using Discord.API; | ||||
| using Discord.Helpers; | |||||
| using System; | using System; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Linq; | using System.Linq; | ||||
| @@ -638,6 +639,34 @@ namespace Discord | |||||
| return _api.EditProfile(currentPassword: currentPassword, username: username, email: email ?? _currentUser?.Email, password: password, | return _api.EditProfile(currentPassword: currentPassword, username: username, email: email ?? _currentUser?.Email, password: password, | ||||
| avatarType: avatarType, avatar: avatar); | avatarType: avatarType, avatar: avatar); | ||||
| } | } | ||||
| public Task SetStatus(string status = null, int? gameId = null) | |||||
| { | |||||
| if (status == null && gameId == null) | |||||
| throw new ArgumentNullException("Either status or gameId must be non-null"); | |||||
| if (status != null) | |||||
| { | |||||
| switch (status) | |||||
| { | |||||
| case UserStatus.Online: | |||||
| case UserStatus.Away: | |||||
| _status = status; | |||||
| break; | |||||
| default: | |||||
| throw new ArgumentException($"Invalid status, must be {UserStatus.Online} or {UserStatus.Away}"); | |||||
| } | |||||
| } | |||||
| if (gameId != null) | |||||
| _gameId = gameId; | |||||
| return SendStatus(); | |||||
| } | |||||
| private Task SendStatus() | |||||
| { | |||||
| _dataSocket.SendStatus(_status == UserStatus.Away ? EpochTime.GetMilliseconds() : (ulong?)null, _gameId); | |||||
| return TaskHelper.CompletedTask; | |||||
| } | |||||
| //Roles | //Roles | ||||
| /// <summary> Note: due to current API limitations, the created role cannot be returned. </summary> | /// <summary> Note: due to current API limitations, the created role cannot be returned. </summary> | ||||
| @@ -24,6 +24,8 @@ namespace Discord | |||||
| private readonly ConcurrentDictionary<string, DiscordSimpleClient> _voiceClients; | private readonly ConcurrentDictionary<string, DiscordSimpleClient> _voiceClients; | ||||
| private bool _sentInitialLog; | private bool _sentInitialLog; | ||||
| private uint _nextVoiceClientId; | private uint _nextVoiceClientId; | ||||
| private string _status; | |||||
| private int? _gameId; | |||||
| public new DiscordClientConfig Config => _config as DiscordClientConfig; | public new DiscordClientConfig Config => _config as DiscordClientConfig; | ||||
| @@ -69,8 +71,13 @@ namespace Discord | |||||
| _roles = new Roles(this, cacheLock); | _roles = new Roles(this, cacheLock); | ||||
| _servers = new Servers(this, cacheLock); | _servers = new Servers(this, cacheLock); | ||||
| _users = new Users(this, cacheLock); | _users = new Users(this, cacheLock); | ||||
| _status = UserStatus.Online; | |||||
| this.Connected += (s, e) => _api.CancelToken = CancelToken; | |||||
| this.Connected += async (s, e) => | |||||
| { | |||||
| _api.CancelToken = CancelToken; | |||||
| await SendStatus(); | |||||
| }; | |||||
| VoiceDisconnected += (s, e) => | VoiceDisconnected += (s, e) => | ||||
| { | { | ||||
| @@ -0,0 +1,10 @@ | |||||
| using System; | |||||
| namespace Discord.Helpers | |||||
| { | |||||
| public class EpochTime | |||||
| { | |||||
| private static readonly DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); | |||||
| public static ulong GetMilliseconds() => (ulong)(DateTime.UtcNow - epoch).TotalMilliseconds; | |||||
| } | |||||
| } | |||||
| @@ -2,17 +2,15 @@ | |||||
| #pragma warning disable CS0649 | #pragma warning disable CS0649 | ||||
| #pragma warning disable CS0169 | #pragma warning disable CS0169 | ||||
| using Discord.Helpers; | |||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using System; | |||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| namespace Discord.WebSockets.Data | namespace Discord.WebSockets.Data | ||||
| { | { | ||||
| internal sealed class KeepAliveCommand : WebSocketMessage<ulong> | internal sealed class KeepAliveCommand : WebSocketMessage<ulong> | ||||
| { | { | ||||
| public KeepAliveCommand() : base(1, GetTimestamp()) { } | |||||
| private static readonly DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); | |||||
| private static ulong GetTimestamp() => (ulong)(DateTime.UtcNow - epoch).TotalMilliseconds; | |||||
| public KeepAliveCommand() : base(1, EpochTime.GetMilliseconds()) { } | |||||
| } | } | ||||
| internal sealed class LoginCommand : WebSocketMessage<LoginCommand.Data> | internal sealed class LoginCommand : WebSocketMessage<LoginCommand.Data> | ||||
| { | { | ||||
| @@ -33,9 +31,9 @@ namespace Discord.WebSockets.Data | |||||
| public class Data | public class Data | ||||
| { | { | ||||
| [JsonProperty("idle_since")] | [JsonProperty("idle_since")] | ||||
| public string IdleSince; | |||||
| public ulong? IdleSince; | |||||
| [JsonProperty("game_id")] | [JsonProperty("game_id")] | ||||
| public string GameId; | |||||
| public int? GameId; | |||||
| } | } | ||||
| } | } | ||||
| internal sealed class JoinVoiceCommand : WebSocketMessage<JoinVoiceCommand.Data> | internal sealed class JoinVoiceCommand : WebSocketMessage<JoinVoiceCommand.Data> | ||||
| @@ -80,13 +80,11 @@ namespace Discord.WebSockets.Data | |||||
| var payload = token.ToObject<ReadyEvent>(); | var payload = token.ToObject<ReadyEvent>(); | ||||
| _sessionId = payload.SessionId; | _sessionId = payload.SessionId; | ||||
| _heartbeatInterval = payload.HeartbeatInterval; | _heartbeatInterval = payload.HeartbeatInterval; | ||||
| QueueMessage(new UpdateStatusCommand()); | |||||
| } | } | ||||
| else if (msg.Type == "RESUMED") | else if (msg.Type == "RESUMED") | ||||
| { | { | ||||
| var payload = token.ToObject<ResumedEvent>(); | var payload = token.ToObject<ResumedEvent>(); | ||||
| _heartbeatInterval = payload.HeartbeatInterval; | _heartbeatInterval = payload.HeartbeatInterval; | ||||
| QueueMessage(new UpdateStatusCommand()); | |||||
| } | } | ||||
| RaiseReceivedEvent(msg.Type, token); | RaiseReceivedEvent(msg.Type, token); | ||||
| if (msg.Type == "READY" || msg.Type == "RESUMED") | if (msg.Type == "READY" || msg.Type == "RESUMED") | ||||
| @@ -114,6 +112,14 @@ namespace Discord.WebSockets.Data | |||||
| return new KeepAliveCommand(); | return new KeepAliveCommand(); | ||||
| } | } | ||||
| public void SendStatus(ulong? idleSince, int? gameId) | |||||
| { | |||||
| var updateStatus = new UpdateStatusCommand(); | |||||
| updateStatus.Payload.IdleSince = idleSince; | |||||
| updateStatus.Payload.GameId = gameId; | |||||
| QueueMessage(updateStatus); | |||||
| } | |||||
| public void SendJoinVoice(string serverId, string channelId) | public void SendJoinVoice(string serverId, string channelId) | ||||
| { | { | ||||
| var joinVoice = new JoinVoiceCommand(); | var joinVoice = new JoinVoiceCommand(); | ||||