Add discord client
This commit is contained in:
parent
324a14a4e4
commit
ba683ad1f9
|
@ -0,0 +1,136 @@
|
|||
using Newtonsoft.Json;
|
||||
using PluralRichPresence.Api;
|
||||
using System.Dynamic;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
|
||||
namespace PluralRichPresence.Api
|
||||
{
|
||||
public class AWebSocket : IDisposable
|
||||
{
|
||||
private string websocketServerUri;
|
||||
SemaphoreSlim? semaphore = new SemaphoreSlim(1, 1);
|
||||
ClientWebSocket? wss = null;
|
||||
|
||||
public event EventHandler<DataReceivedEventArgs>? DataReceived;
|
||||
public event EventHandler<TextReceivedEventArgs>? TextReceived;
|
||||
public event EventHandler? Disconnected;
|
||||
|
||||
private void onTextReceived(string receivedString)
|
||||
{
|
||||
if (TextReceived is not null)
|
||||
{
|
||||
TextReceived(this, new TextReceivedEventArgs(receivedString));
|
||||
}
|
||||
}
|
||||
|
||||
private void onDataReceived(byte[] receivedData)
|
||||
{
|
||||
if (DataReceived is not null)
|
||||
{
|
||||
DataReceived(this, new DataReceivedEventArgs(receivedData));
|
||||
}
|
||||
}
|
||||
|
||||
private void onDisconnect()
|
||||
{
|
||||
if (Disconnected is not null)
|
||||
{
|
||||
if (semaphore is null || !semaphore.Wait(0)) return;
|
||||
Disconnected(this, new EventArgs());
|
||||
}
|
||||
}
|
||||
|
||||
public AWebSocket(string serverUri)
|
||||
{
|
||||
this.websocketServerUri = serverUri;
|
||||
}
|
||||
|
||||
private async Task<string> receiveMessageText()
|
||||
{
|
||||
byte[] byteArray = await receiveMessageBytes();
|
||||
return Encoding.UTF8.GetString(byteArray);
|
||||
}
|
||||
|
||||
private async Task<byte[]> receiveMessageBytes()
|
||||
{
|
||||
List<byte> totalPayload = new List<byte>();
|
||||
byte[] buffer = new byte[0x8000];
|
||||
|
||||
while (wss is not null && wss.State == WebSocketState.Open)
|
||||
{
|
||||
try
|
||||
{
|
||||
WebSocketReceiveResult? result = await wss.ReceiveAsync(buffer, CancellationToken.None);
|
||||
|
||||
for (int i = 0; i < result.Count; i++)
|
||||
totalPayload.Add(buffer[i]);
|
||||
|
||||
if (result.EndOfMessage)
|
||||
return totalPayload.ToArray();
|
||||
}
|
||||
catch(Exception) { onDisconnect(); break; };
|
||||
}
|
||||
|
||||
return totalPayload.ToArray();
|
||||
}
|
||||
|
||||
|
||||
private async Task receiveTask()
|
||||
{
|
||||
while (wss is not null && wss.State == WebSocketState.Open) {
|
||||
try {
|
||||
string message = await receiveMessageText();
|
||||
Logger.Debug("< " + message);
|
||||
onTextReceived(message);
|
||||
|
||||
}
|
||||
catch (Exception) { Logger.Debug("failed"); break; };
|
||||
}
|
||||
onDisconnect();
|
||||
}
|
||||
|
||||
public async Task Connect()
|
||||
{
|
||||
wss = new ClientWebSocket();
|
||||
await wss.ConnectAsync(new Uri(this.websocketServerUri), CancellationToken.None);
|
||||
_ = Task.Run(() => receiveTask());
|
||||
try {
|
||||
if(semaphore is not null)
|
||||
semaphore.Release();
|
||||
}
|
||||
catch (SemaphoreFullException) { };
|
||||
}
|
||||
|
||||
public async Task SendText(string text)
|
||||
{
|
||||
Logger.Debug("> "+text);
|
||||
try {
|
||||
if (wss is not null)
|
||||
await wss.SendAsync(Encoding.UTF8.GetBytes(text), WebSocketMessageType.Text, true, CancellationToken.None);
|
||||
}
|
||||
catch (Exception) { onDisconnect(); }
|
||||
}
|
||||
|
||||
public async Task Close()
|
||||
{
|
||||
if (wss is not null)
|
||||
await wss.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if(wss is not null)
|
||||
{
|
||||
this.Close().Wait();
|
||||
wss.Dispose();
|
||||
wss = null;
|
||||
}
|
||||
if (semaphore is not null)
|
||||
{
|
||||
semaphore.Dispose();
|
||||
semaphore = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
namespace PluralRichPresence.SimplyPlural
|
||||
namespace PluralRichPresence.Api
|
||||
{
|
||||
public class ApiType
|
||||
{
|
|
@ -0,0 +1,17 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PluralRichPresence.Api
|
||||
{
|
||||
public class DataReceivedEventArgs : EventArgs
|
||||
{
|
||||
public byte[] Data;
|
||||
public DataReceivedEventArgs(byte[] data)
|
||||
{
|
||||
this.Data = data;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PluralRichPresence.Api
|
||||
{
|
||||
public class TextReceivedEventArgs : EventArgs
|
||||
{
|
||||
public string Text;
|
||||
public TextReceivedEventArgs(string text)
|
||||
{
|
||||
this.Text = text;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,210 @@
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PluralRichPresence.Api;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Dynamic;
|
||||
using System.Linq;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Http.Json;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PluralRichPresence.Discord
|
||||
{
|
||||
public class DiscordClient : ApiType, IDiscordActivitySetter
|
||||
{
|
||||
const string DEFAULT_DISCORD_GATEWAY_URL = "wss://gateway.discord.gg/?v=10&encoding=json";
|
||||
private const string DEFAULT_APPLICATION_ID = "1163661006719963158";
|
||||
private static HttpClient client = new HttpClient();
|
||||
|
||||
private SystemMember? lastFronter = null;
|
||||
private int? seq = null;
|
||||
private Timer? keepAliveTimer = null;
|
||||
private AWebSocket? wSock = null;
|
||||
private ManualResetEvent waitForConnect = new ManualResetEvent(false);
|
||||
private int heartBeatInterval = 0;
|
||||
|
||||
private void wSockTextReceived(object? sender, TextReceivedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (e.Text == "") return;
|
||||
dynamic? jsonData = JsonConvert.DeserializeObject(e.Text);
|
||||
if (jsonData is null) return;
|
||||
seq = jsonData.s;
|
||||
switch ((int)jsonData.op)
|
||||
{
|
||||
case 0:
|
||||
if (jsonData.t == "READY") waitForConnect.Set();
|
||||
break;
|
||||
case 1:
|
||||
_ = sendKeepAlive();
|
||||
break;
|
||||
case 9:
|
||||
_ = reconnect();
|
||||
break;
|
||||
case 10:
|
||||
sendKeepAlive().Wait();
|
||||
heartBeatInterval = jsonData.d.heartbeat_interval;
|
||||
keepAliveTimer = new Timer((TimerCallback) => { _ = sendKeepAlive(); }, null, heartBeatInterval, 0);
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception) { };
|
||||
}
|
||||
|
||||
private void wSockDisconnected(object? sender, EventArgs e)
|
||||
{
|
||||
|
||||
_ = reconnect();
|
||||
}
|
||||
|
||||
private async Task<string> getExternalAsset(string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
dynamic extAssReq = new JObject();
|
||||
extAssReq.urls = new JArray(url);
|
||||
|
||||
StringContent apiReq = new StringContent(JsonConvert.SerializeObject(extAssReq));
|
||||
apiReq.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
|
||||
|
||||
HttpResponseMessage resp = await client.PostAsync("https://discord.com/api/v9/applications/" + Config.GetEntry("DISCORD_APPLICATION_ID", DEFAULT_APPLICATION_ID) + "/external-assets", apiReq);
|
||||
resp.EnsureSuccessStatusCode();
|
||||
string responseString = await resp.Content.ReadAsStringAsync();
|
||||
dynamic? jsonResponse = JsonConvert.DeserializeObject(responseString);
|
||||
if(jsonResponse is not null)
|
||||
return jsonResponse[0].external_asset_path;
|
||||
|
||||
return "1163671691000557591";
|
||||
}
|
||||
catch (Exception) { return "1163671691000557591"; };
|
||||
}
|
||||
|
||||
private async Task connect()
|
||||
{
|
||||
wSock = new AWebSocket(Config.GetEntry("DISCORD_GATEWAY_WEBSOCKET_URL", DEFAULT_DISCORD_GATEWAY_URL));
|
||||
await wSock.Connect();
|
||||
wSock.TextReceived += wSockTextReceived;
|
||||
wSock.Disconnected += wSockDisconnected;
|
||||
}
|
||||
private async Task reconnect()
|
||||
{
|
||||
waitForConnect.Reset();
|
||||
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
if (wSock is not null) wSock.Dispose();
|
||||
wSock = null;
|
||||
}
|
||||
catch (Exception) { };
|
||||
await connect();
|
||||
await sendLogin();
|
||||
|
||||
if (lastFronter is not null)
|
||||
SetFronter(lastFronter);
|
||||
}
|
||||
catch (Exception) { Logger.Debug("failed to connect."); continue; }
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public DiscordClient(string token) : base(token)
|
||||
{
|
||||
client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) discord/1.0.9036 Chrome/108.0.5359.215 Electron/22.3.26 Safari/537.36");
|
||||
client.DefaultRequestHeaders.TryAddWithoutValidation("Origin", "https://discord.com/");
|
||||
client.DefaultRequestHeaders.TryAddWithoutValidation("Referer", "https://discord.com/");
|
||||
client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", this.token);
|
||||
|
||||
_ = reconnect();
|
||||
}
|
||||
|
||||
private async Task sendLogin()
|
||||
{
|
||||
dynamic gatewayOp = new JObject();
|
||||
gatewayOp.op = 2;
|
||||
gatewayOp.d = new JObject();
|
||||
gatewayOp.d.token = this.token;
|
||||
gatewayOp.d.capabilities = 16381;
|
||||
|
||||
gatewayOp.d.properties = new JObject();
|
||||
gatewayOp.d.properties.os = "Windows";
|
||||
gatewayOp.d.properties.browser = "Discord Client";
|
||||
gatewayOp.d.properties.release_channel = "stable";
|
||||
gatewayOp.d.properties.client_version = "1.0.9035";
|
||||
gatewayOp.d.properties.os_version = "10.0.19045";
|
||||
gatewayOp.d.properties.os_arch = "x64";
|
||||
gatewayOp.d.properties.app_arch = "ia32";
|
||||
gatewayOp.d.properties.system_locale = "en-G";
|
||||
gatewayOp.d.properties.browser_user_agent = "ia32";
|
||||
gatewayOp.d.properties.browser_version = "22.3.26";
|
||||
gatewayOp.d.properties.client_build_number = 274388;
|
||||
gatewayOp.d.properties.native_build_number = 44780;
|
||||
gatewayOp.d.properties.client_event_source = null;
|
||||
|
||||
if (wSock is not null)
|
||||
await wSock.SendText(JsonConvert.SerializeObject(gatewayOp));
|
||||
}
|
||||
private async Task sendKeepAlive()
|
||||
{
|
||||
dynamic gatewayOp = new JObject();
|
||||
gatewayOp.op = 1;
|
||||
gatewayOp.d = seq;
|
||||
|
||||
Logger.Debug("SENDING KEEP ALIVE MESSAGE");
|
||||
|
||||
if (wSock is not null)
|
||||
await wSock.SendText(JsonConvert.SerializeObject(gatewayOp));
|
||||
|
||||
if(keepAliveTimer is not null)
|
||||
keepAliveTimer.Change(this.heartBeatInterval, 0);
|
||||
}
|
||||
|
||||
public void SetFronter(SystemMember sysMember)
|
||||
{
|
||||
waitForConnect.WaitOne();
|
||||
|
||||
dynamic gatewayOp = new JObject();
|
||||
gatewayOp.op = 3;
|
||||
|
||||
gatewayOp.d = new JObject();
|
||||
gatewayOp.d.status = "online";
|
||||
gatewayOp.d.since = 0;
|
||||
|
||||
dynamic fronterActivity = new JObject();
|
||||
fronterActivity.state = sysMember.Pronouns;
|
||||
fronterActivity.details = sysMember.Name;
|
||||
|
||||
if(sysMember.TimeStamp is not null)
|
||||
{
|
||||
fronterActivity.timestamps = new JObject();
|
||||
fronterActivity.timestamps.start = sysMember.TimeStamp;
|
||||
}
|
||||
|
||||
fronterActivity.assets = new JObject();
|
||||
fronterActivity.assets.large_image = (sysMember.ProfilePhotoUrl is not null) ? "mp:"+getExternalAsset(sysMember.ProfilePhotoUrl).Result : "1163671691000557591";
|
||||
fronterActivity.assets.large_text = sysMember.Name + " - " + sysMember.Pronouns;
|
||||
|
||||
if (sysMember.ProfilePhotoUrl is not null)
|
||||
fronterActivity.assets.small_image = "1163671691000557591";
|
||||
|
||||
fronterActivity.name = "Currently Fronting: " + sysMember.Name;
|
||||
fronterActivity.application_id = Config.GetEntry("DISCORD_APPLICATION_ID", DEFAULT_APPLICATION_ID);
|
||||
fronterActivity.type = 0;
|
||||
|
||||
gatewayOp.d.activities = new JArray(fronterActivity);
|
||||
gatewayOp.d.afk = false;
|
||||
gatewayOp.d.broadcast = null;
|
||||
|
||||
if(wSock is not null)
|
||||
_ = wSock.SendText(JsonConvert.SerializeObject(gatewayOp));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
using DiscordRPC;
|
||||
using DiscordRPC.Logging;
|
||||
|
||||
namespace PluralRichPresence.Discord
|
||||
{
|
||||
public class DiscordRpc : IDiscordActivitySetter
|
||||
{
|
||||
private const string DEFAULT_APPLICATION_ID = "1163661006719963158";
|
||||
private const string DEFAULT_ICON = "plural";
|
||||
private DiscordRpcClient client;
|
||||
public DiscordRpc()
|
||||
{
|
||||
client = new DiscordRpcClient(Config.GetEntry("DISCORD_APPLICATION_ID", DEFAULT_APPLICATION_ID));
|
||||
client.Logger = new ConsoleLogger() { Level = LogLevel.None };
|
||||
client.OnReady += (sender, e) => { };
|
||||
client.OnPresenceUpdate += (sender, e) => { };
|
||||
client.Initialize();
|
||||
}
|
||||
|
||||
public void SetFronter(SystemMember sysMember)
|
||||
{
|
||||
client.SetPresence(new RichPresence()
|
||||
{
|
||||
Details = sysMember.Name,
|
||||
State = sysMember.Pronouns,
|
||||
Assets = new Assets()
|
||||
{
|
||||
LargeImageKey = (sysMember.ProfilePhotoUrl is not null) ? sysMember.ProfilePhotoUrl : Config.GetEntry("PLURAL_ICON_NAME", DEFAULT_ICON),
|
||||
LargeImageText = sysMember.Name + " - " + sysMember.Pronouns,
|
||||
SmallImageKey = Config.GetEntry("PLURAL_ICON_NAME", DEFAULT_ICON)
|
||||
},
|
||||
Timestamps = new Timestamps()
|
||||
{
|
||||
StartUnixMilliseconds = sysMember.TimeStamp
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PluralRichPresence.Discord
|
||||
{
|
||||
public interface IDiscordActivitySetter
|
||||
{
|
||||
public void SetFronter(SystemMember sysMember);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PluralRichPresence.Discord
|
||||
{
|
||||
public class SystemMember
|
||||
{
|
||||
public string Name;
|
||||
public string Pronouns;
|
||||
public string? ProfilePhotoUrl;
|
||||
public ulong? TimeStamp;
|
||||
|
||||
|
||||
public SystemMember(string name, string pronouns, string? profilePhotoUrl, ulong? timeStamp)
|
||||
{
|
||||
this.Name = name;
|
||||
this.Pronouns = pronouns;
|
||||
this.ProfilePhotoUrl = profilePhotoUrl;
|
||||
this.TimeStamp = timeStamp;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
using DiscordRPC;
|
||||
using DiscordRPC.Logging;
|
||||
|
||||
namespace PluralRichPresence
|
||||
{
|
||||
|
||||
public class DiscordRPC
|
||||
{
|
||||
private DiscordRpcClient client;
|
||||
private const string DEFAULT_APPLICATION_ID = "1163661006719963158";
|
||||
public DiscordRPC()
|
||||
{
|
||||
client = new DiscordRpcClient(Config.GetEntry("DISCORD_APPLICATION_ID", DEFAULT_APPLICATION_ID));
|
||||
|
||||
client.Logger = new ConsoleLogger() { Level = LogLevel.None };
|
||||
|
||||
client.OnReady += (sender, e) => {};
|
||||
|
||||
client.OnPresenceUpdate += (sender, e) => {};
|
||||
|
||||
client.Initialize();
|
||||
}
|
||||
|
||||
public void SetFronter(string user, string pronouns, string profile, ulong? timeStamp)
|
||||
{
|
||||
client.SetPresence(new RichPresence()
|
||||
{
|
||||
Details = user,
|
||||
State = pronouns,
|
||||
Assets = new Assets()
|
||||
{
|
||||
LargeImageKey = profile,
|
||||
LargeImageText = user + " - " + pronouns,
|
||||
SmallImageKey = "plural"
|
||||
},
|
||||
Timestamps = new Timestamps()
|
||||
{
|
||||
StartUnixMilliseconds = timeStamp
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
using PluralRichPresence.Avatar;
|
||||
using PluralRichPresence.SimplyPlural;
|
||||
using PluralRichPresence.Discord;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Linq;
|
||||
|
@ -10,7 +11,7 @@ namespace PluralRichPresence
|
|||
internal class Program
|
||||
{
|
||||
private static ManualResetEvent waitHandle = new ManualResetEvent(false);
|
||||
private static DiscordRPC discordRpc = new DiscordRPC();
|
||||
private static IDiscordActivitySetter? discordSetter = null;
|
||||
private static System? system;
|
||||
private const string KEY_SIMPLY_PLURAL_TOKEN = "SIMPLY_PLURAL_TOKEN";
|
||||
|
||||
|
@ -72,21 +73,27 @@ namespace PluralRichPresence
|
|||
if (frontingMembers.Length > 0)
|
||||
{
|
||||
Console.Write("\r" + FmtFronterNames(frontingMembers) + " is fronting!");
|
||||
discordRpc.SetFronter(FmtFronterNames(frontingMembers),
|
||||
if (discordSetter is null) return;
|
||||
|
||||
discordSetter.SetFronter(new SystemMember(FmtFronterNames(frontingMembers),
|
||||
FmtFronterPronouns(frontingMembers),
|
||||
FmtAvatar(frontingMembers),
|
||||
frontingMembers.OrderByDescending(o => o.FrontStartTime).First().FrontStartTime);
|
||||
frontingMembers.OrderByDescending(o => o.FrontStartTime).First().FrontStartTime));
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Write("\r No one is fronting!");
|
||||
discordRpc.SetFronter("No one is fronting.", "This doesn't make much sense?", "plural", null);
|
||||
if (discordSetter is null) return;
|
||||
|
||||
discordSetter.SetFronter(new SystemMember("No one is fronting.", "This doesn't make much sense?", null, null));
|
||||
}
|
||||
}
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
discordSetter = new DiscordClient(Config.GetEntry("DISCORD_TOKEN"));
|
||||
//discordSetter = new DiscordRpc();
|
||||
|
||||
if(!Config.EntryExists(KEY_SIMPLY_PLURAL_TOKEN))
|
||||
if (!Config.EntryExists(KEY_SIMPLY_PLURAL_TOKEN))
|
||||
{
|
||||
Console.Write("Enter Simply Plural API Key: ");
|
||||
string? token = Console.ReadLine();
|
||||
|
|
|
@ -6,14 +6,13 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
|||
<PropertyGroup>
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Any CPU</Platform>
|
||||
<PublishDir>bin\Release\net8.0\publish\win-x86\</PublishDir>
|
||||
<PublishDir>bin\Release\net8.0\publish\linux-x64\</PublishDir>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<_TargetId>Folder</_TargetId>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
|
||||
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
<PublishReadyToRun>false</PublishReadyToRun>
|
||||
<PublishTrimmed>false</PublishTrimmed>
|
||||
</PropertyGroup>
|
||||
</Project>
|
|
@ -4,7 +4,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
|||
-->
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<History>True|2023-12-28T10:24:43.7413097Z;True|2023-12-28T23:24:32.3866287+13:00;True|2023-12-28T23:22:28.7251892+13:00;True|2023-12-28T21:45:45.2231538+13:00;True|2023-12-28T21:00:25.2444119+13:00;True|2023-12-28T20:56:12.0172711+13:00;True|2023-12-28T20:54:22.1893325+13:00;True|2023-12-28T20:53:03.6353258+13:00;True|2023-11-19T17:13:26.6652642+13:00;True|2023-11-05T19:18:40.8205304+13:00;True|2023-11-05T19:17:01.7660736+13:00;True|2023-11-05T19:11:32.6382930+13:00;True|2023-11-05T19:11:09.6820258+13:00;False|2023-11-05T19:10:42.2427675+13:00;True|2023-10-18T14:13:41.8203499+13:00;True|2023-10-18T14:05:36.8381808+13:00;True|2023-10-17T21:30:38.9682339+13:00;</History>
|
||||
<History>True|2024-03-15T06:01:46.3092686Z;True|2023-12-28T23:24:43.7413097+13:00;True|2023-12-28T23:24:32.3866287+13:00;True|2023-12-28T23:22:28.7251892+13:00;True|2023-12-28T21:45:45.2231538+13:00;True|2023-12-28T21:00:25.2444119+13:00;True|2023-12-28T20:56:12.0172711+13:00;True|2023-12-28T20:54:22.1893325+13:00;True|2023-12-28T20:53:03.6353258+13:00;True|2023-11-19T17:13:26.6652642+13:00;True|2023-11-05T19:18:40.8205304+13:00;True|2023-11-05T19:17:01.7660736+13:00;True|2023-11-05T19:11:32.6382930+13:00;True|2023-11-05T19:11:09.6820258+13:00;False|2023-11-05T19:10:42.2427675+13:00;True|2023-10-18T14:13:41.8203499+13:00;True|2023-10-18T14:05:36.8381808+13:00;True|2023-10-17T21:30:38.9682339+13:00;</History>
|
||||
<LastFailureDetails />
|
||||
</PropertyGroup>
|
||||
</Project>
|
|
@ -3,6 +3,7 @@ using Newtonsoft.Json.Linq;
|
|||
using System.Net.Http.Headers;
|
||||
using System;
|
||||
using System.Text;
|
||||
using PluralRichPresence.Api;
|
||||
|
||||
namespace PluralRichPresence.SimplyPlural
|
||||
{
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
using Newtonsoft.Json;
|
||||
using PluralRichPresence.Api;
|
||||
using System.Dynamic;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
|
||||
namespace PluralRichPresence.SimplyPlural
|
||||
{
|
||||
public class Socket : ApiType
|
||||
{
|
||||
private AWebSocket? wSock;
|
||||
public const int KEEPALIVE_INTERVAL = 10 * 1000;
|
||||
public const string DEFAULT_WEBSOCKET_SERVER_URI = "wss://api.apparyllis.com/v1/socket";
|
||||
public event EventHandler? FronterChanged;
|
||||
SemaphoreSlim semaphore = new SemaphoreSlim(1, 1);
|
||||
ClientWebSocket? wss = null;
|
||||
Timer? keepAliveTimer = null;
|
||||
|
||||
|
||||
|
@ -22,34 +21,6 @@ namespace PluralRichPresence.SimplyPlural
|
|||
FronterChanged(this, new EventArgs());
|
||||
}
|
||||
}
|
||||
private async Task<string> receiveMessageText()
|
||||
{
|
||||
byte[] byteArray = await receiveMessageBytes();
|
||||
return Encoding.UTF8.GetString(byteArray);
|
||||
}
|
||||
private async Task<byte[]> receiveMessageBytes()
|
||||
{
|
||||
List<byte> totalPayload = new List<byte>();
|
||||
byte[] buffer = new byte[0x8000];
|
||||
|
||||
while (wss is not null && wss.State == WebSocketState.Open)
|
||||
{
|
||||
try
|
||||
{
|
||||
WebSocketReceiveResult? result = await wss.ReceiveAsync(buffer, CancellationToken.None);
|
||||
|
||||
for (int i = 0; i < result.Count; i++)
|
||||
totalPayload.Add(buffer[i]);
|
||||
|
||||
if (result.EndOfMessage)
|
||||
return totalPayload.ToArray();
|
||||
}
|
||||
catch(Exception) { await reconnect(); break; };
|
||||
}
|
||||
|
||||
return totalPayload.ToArray();
|
||||
}
|
||||
|
||||
private void doUpdate(dynamic jsonData)
|
||||
{
|
||||
string target = jsonData.target;
|
||||
|
@ -61,20 +32,46 @@ namespace PluralRichPresence.SimplyPlural
|
|||
}
|
||||
}
|
||||
|
||||
private async Task receiveTask()
|
||||
private async Task reconnect()
|
||||
{
|
||||
while (wss is not null && wss.State == WebSocketState.Open)
|
||||
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
string message = await receiveMessageText();
|
||||
Logger.Debug("< " + message);
|
||||
if (message == "pong") continue;
|
||||
try
|
||||
{
|
||||
if (wSock is not null) wSock.Dispose();
|
||||
wSock = null;
|
||||
}
|
||||
catch (Exception) { };
|
||||
await connect();
|
||||
await sendLogin();
|
||||
|
||||
this.keepAliveTimer = new Timer((TimerCallback) => { _ = sendKeepAlive(); }, null, KEEPALIVE_INTERVAL, 0);
|
||||
}
|
||||
catch (Exception) { Logger.Debug("failed to connect."); continue; }
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
private async Task connect()
|
||||
{
|
||||
wSock = new AWebSocket(Config.GetEntry("SIMPLY_PLURAL_WEBSOCKET_URI", DEFAULT_WEBSOCKET_SERVER_URI));
|
||||
await wSock.Connect();
|
||||
wSock.TextReceived += wSockTextReceived;
|
||||
wSock.Disconnected += wSockDisconnected;
|
||||
}
|
||||
|
||||
private void wSockTextReceived(object? sender, TextReceivedEventArgs e)
|
||||
{
|
||||
string message = e.Text;
|
||||
if (message == "pong") return;
|
||||
|
||||
try
|
||||
{
|
||||
dynamic? jsonData = JsonConvert.DeserializeObject(message);
|
||||
if (jsonData is null) continue;
|
||||
if (jsonData is null) return;
|
||||
string? type = jsonData.msg;
|
||||
switch (type)
|
||||
{
|
||||
|
@ -89,58 +86,19 @@ namespace PluralRichPresence.SimplyPlural
|
|||
}
|
||||
catch (Exception) { };
|
||||
}
|
||||
catch (Exception) { Logger.Debug("failed"); break; };
|
||||
}
|
||||
}
|
||||
private async Task reconnect()
|
||||
{
|
||||
if (!await semaphore.WaitAsync(0)) return;
|
||||
|
||||
while (true)
|
||||
private void wSockDisconnected(object? sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
if (wss is not null) await wss.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
|
||||
wss = null;
|
||||
}
|
||||
catch (Exception) { };
|
||||
await connect();
|
||||
await sendLogin();
|
||||
|
||||
_ = Task.Run(() => receiveTask());
|
||||
this.keepAliveTimer = new Timer((TimerCallback) => { _ = sendKeepAlive(); }, null, KEEPALIVE_INTERVAL, 0);
|
||||
}
|
||||
catch (Exception) { Logger.Debug("failed to connect."); continue; }
|
||||
break;
|
||||
_ = reconnect();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
catch (SemaphoreFullException) { };
|
||||
|
||||
}
|
||||
private async Task connect()
|
||||
{
|
||||
wss = new ClientWebSocket();
|
||||
await wss.ConnectAsync(new Uri(Config.GetEntry("SIMPLY_PLURAL_WEBSOCKET_URI", DEFAULT_WEBSOCKET_SERVER_URI)), CancellationToken.None);
|
||||
}
|
||||
|
||||
private async Task sendText(string text)
|
||||
{
|
||||
Logger.Debug("> "+text);
|
||||
try
|
||||
{
|
||||
await wss.SendAsync(Encoding.UTF8.GetBytes(text), WebSocketMessageType.Text, true, CancellationToken.None);
|
||||
}
|
||||
catch (Exception) { await reconnect(); }
|
||||
}
|
||||
private async Task sendKeepAlive()
|
||||
{
|
||||
await sendText("ping");
|
||||
if (wSock is not null) {
|
||||
await wSock.SendText("ping");
|
||||
}
|
||||
|
||||
if (keepAliveTimer is not null) keepAliveTimer.Change(KEEPALIVE_INTERVAL, 0);
|
||||
|
||||
}
|
||||
|
@ -150,7 +108,8 @@ namespace PluralRichPresence.SimplyPlural
|
|||
data.op = "authenticate";
|
||||
data.token = token;
|
||||
string authenticateJson = JsonConvert.SerializeObject(data);
|
||||
await sendText(authenticateJson);
|
||||
if(wSock is not null)
|
||||
await wSock.SendText(authenticateJson);
|
||||
}
|
||||
|
||||
public Socket(string token) : base(token)
|
||||
|
|
Loading…
Reference in New Issue