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
|
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.Avatar;
|
||||||
using PluralRichPresence.SimplyPlural;
|
using PluralRichPresence.SimplyPlural;
|
||||||
|
using PluralRichPresence.Discord;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Drawing.Imaging;
|
using System.Drawing.Imaging;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
@ -10,7 +11,7 @@ namespace PluralRichPresence
|
||||||
internal class Program
|
internal class Program
|
||||||
{
|
{
|
||||||
private static ManualResetEvent waitHandle = new ManualResetEvent(false);
|
private static ManualResetEvent waitHandle = new ManualResetEvent(false);
|
||||||
private static DiscordRPC discordRpc = new DiscordRPC();
|
private static IDiscordActivitySetter? discordSetter = null;
|
||||||
private static System? system;
|
private static System? system;
|
||||||
private const string KEY_SIMPLY_PLURAL_TOKEN = "SIMPLY_PLURAL_TOKEN";
|
private const string KEY_SIMPLY_PLURAL_TOKEN = "SIMPLY_PLURAL_TOKEN";
|
||||||
|
|
||||||
|
@ -72,21 +73,27 @@ namespace PluralRichPresence
|
||||||
if (frontingMembers.Length > 0)
|
if (frontingMembers.Length > 0)
|
||||||
{
|
{
|
||||||
Console.Write("\r" + FmtFronterNames(frontingMembers) + " is fronting!");
|
Console.Write("\r" + FmtFronterNames(frontingMembers) + " is fronting!");
|
||||||
discordRpc.SetFronter(FmtFronterNames(frontingMembers),
|
if (discordSetter is null) return;
|
||||||
FmtFronterPronouns(frontingMembers),
|
|
||||||
FmtAvatar(frontingMembers),
|
discordSetter.SetFronter(new SystemMember(FmtFronterNames(frontingMembers),
|
||||||
frontingMembers.OrderByDescending(o => o.FrontStartTime).First().FrontStartTime);
|
FmtFronterPronouns(frontingMembers),
|
||||||
|
FmtAvatar(frontingMembers),
|
||||||
|
frontingMembers.OrderByDescending(o => o.FrontStartTime).First().FrontStartTime));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Console.Write("\r No one is fronting!");
|
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)
|
public static async Task Main(string[] args)
|
||||||
{
|
{
|
||||||
|
discordSetter = new DiscordClient(Config.GetEntry("DISCORD_TOKEN"));
|
||||||
if(!Config.EntryExists(KEY_SIMPLY_PLURAL_TOKEN))
|
//discordSetter = new DiscordRpc();
|
||||||
|
|
||||||
|
if (!Config.EntryExists(KEY_SIMPLY_PLURAL_TOKEN))
|
||||||
{
|
{
|
||||||
Console.Write("Enter Simply Plural API Key: ");
|
Console.Write("Enter Simply Plural API Key: ");
|
||||||
string? token = Console.ReadLine();
|
string? token = Console.ReadLine();
|
||||||
|
|
|
@ -6,14 +6,13 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Configuration>Release</Configuration>
|
<Configuration>Release</Configuration>
|
||||||
<Platform>Any CPU</Platform>
|
<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>
|
<PublishProtocol>FileSystem</PublishProtocol>
|
||||||
<_TargetId>Folder</_TargetId>
|
<_TargetId>Folder</_TargetId>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
|
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
|
||||||
<SelfContained>true</SelfContained>
|
<SelfContained>true</SelfContained>
|
||||||
<PublishSingleFile>true</PublishSingleFile>
|
<PublishSingleFile>true</PublishSingleFile>
|
||||||
<PublishReadyToRun>false</PublishReadyToRun>
|
|
||||||
<PublishTrimmed>false</PublishTrimmed>
|
<PublishTrimmed>false</PublishTrimmed>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
|
@ -4,7 +4,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||||
-->
|
-->
|
||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<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 />
|
<LastFailureDetails />
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
|
@ -3,6 +3,7 @@ using Newtonsoft.Json.Linq;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System;
|
using System;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using PluralRichPresence.Api;
|
||||||
|
|
||||||
namespace PluralRichPresence.SimplyPlural
|
namespace PluralRichPresence.SimplyPlural
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using PluralRichPresence.Api;
|
||||||
using System.Dynamic;
|
using System.Dynamic;
|
||||||
using System.Net.WebSockets;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace PluralRichPresence.SimplyPlural
|
namespace PluralRichPresence.SimplyPlural
|
||||||
{
|
{
|
||||||
public class Socket : ApiType
|
public class Socket : ApiType
|
||||||
{
|
{
|
||||||
|
private AWebSocket? wSock;
|
||||||
public const int KEEPALIVE_INTERVAL = 10 * 1000;
|
public const int KEEPALIVE_INTERVAL = 10 * 1000;
|
||||||
public const string DEFAULT_WEBSOCKET_SERVER_URI = "wss://api.apparyllis.com/v1/socket";
|
public const string DEFAULT_WEBSOCKET_SERVER_URI = "wss://api.apparyllis.com/v1/socket";
|
||||||
public event EventHandler? FronterChanged;
|
public event EventHandler? FronterChanged;
|
||||||
SemaphoreSlim semaphore = new SemaphoreSlim(1, 1);
|
|
||||||
ClientWebSocket? wss = null;
|
|
||||||
Timer? keepAliveTimer = null;
|
Timer? keepAliveTimer = null;
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,34 +21,6 @@ namespace PluralRichPresence.SimplyPlural
|
||||||
FronterChanged(this, new EventArgs());
|
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)
|
private void doUpdate(dynamic jsonData)
|
||||||
{
|
{
|
||||||
string target = jsonData.target;
|
string target = jsonData.target;
|
||||||
|
@ -61,40 +32,8 @@ namespace PluralRichPresence.SimplyPlural
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task receiveTask()
|
|
||||||
{
|
|
||||||
while (wss is not null && wss.State == WebSocketState.Open)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
string message = await receiveMessageText();
|
|
||||||
Logger.Debug("< " + message);
|
|
||||||
if (message == "pong") continue;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
dynamic? jsonData = JsonConvert.DeserializeObject(message);
|
|
||||||
if (jsonData is null) continue;
|
|
||||||
string? type = jsonData.msg;
|
|
||||||
switch (type)
|
|
||||||
{
|
|
||||||
case "update":
|
|
||||||
doUpdate(jsonData);
|
|
||||||
break;
|
|
||||||
case null:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception) { };
|
|
||||||
}
|
|
||||||
catch (Exception) { Logger.Debug("failed"); break; };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private async Task reconnect()
|
private async Task reconnect()
|
||||||
{
|
{
|
||||||
if (!await semaphore.WaitAsync(0)) return;
|
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
|
@ -102,45 +41,64 @@ namespace PluralRichPresence.SimplyPlural
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (wss is not null) await wss.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
|
if (wSock is not null) wSock.Dispose();
|
||||||
wss = null;
|
wSock = null;
|
||||||
}
|
}
|
||||||
catch (Exception) { };
|
catch (Exception) { };
|
||||||
await connect();
|
await connect();
|
||||||
await sendLogin();
|
await sendLogin();
|
||||||
|
|
||||||
_ = Task.Run(() => receiveTask());
|
|
||||||
this.keepAliveTimer = new Timer((TimerCallback) => { _ = sendKeepAlive(); }, null, KEEPALIVE_INTERVAL, 0);
|
this.keepAliveTimer = new Timer((TimerCallback) => { _ = sendKeepAlive(); }, null, KEEPALIVE_INTERVAL, 0);
|
||||||
}
|
}
|
||||||
catch (Exception) { Logger.Debug("failed to connect."); continue; }
|
catch (Exception) { Logger.Debug("failed to connect."); continue; }
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
semaphore.Release();
|
|
||||||
}
|
|
||||||
catch (SemaphoreFullException) { };
|
|
||||||
|
|
||||||
}
|
}
|
||||||
private async Task connect()
|
private async Task connect()
|
||||||
{
|
{
|
||||||
wss = new ClientWebSocket();
|
wSock = new AWebSocket(Config.GetEntry("SIMPLY_PLURAL_WEBSOCKET_URI", DEFAULT_WEBSOCKET_SERVER_URI));
|
||||||
await wss.ConnectAsync(new Uri(Config.GetEntry("SIMPLY_PLURAL_WEBSOCKET_URI", DEFAULT_WEBSOCKET_SERVER_URI)), CancellationToken.None);
|
await wSock.Connect();
|
||||||
|
wSock.TextReceived += wSockTextReceived;
|
||||||
|
wSock.Disconnected += wSockDisconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task sendText(string text)
|
private void wSockTextReceived(object? sender, TextReceivedEventArgs e)
|
||||||
{
|
{
|
||||||
Logger.Debug("> "+text);
|
string message = e.Text;
|
||||||
|
if (message == "pong") return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await wss.SendAsync(Encoding.UTF8.GetBytes(text), WebSocketMessageType.Text, true, CancellationToken.None);
|
dynamic? jsonData = JsonConvert.DeserializeObject(message);
|
||||||
|
if (jsonData is null) return;
|
||||||
|
string? type = jsonData.msg;
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case "update":
|
||||||
|
doUpdate(jsonData);
|
||||||
|
break;
|
||||||
|
case null:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception) { await reconnect(); }
|
catch (Exception) { };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void wSockDisconnected(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
_ = reconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private async Task sendKeepAlive()
|
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);
|
if (keepAliveTimer is not null) keepAliveTimer.Change(KEEPALIVE_INTERVAL, 0);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -150,7 +108,8 @@ namespace PluralRichPresence.SimplyPlural
|
||||||
data.op = "authenticate";
|
data.op = "authenticate";
|
||||||
data.token = token;
|
data.token = token;
|
||||||
string authenticateJson = JsonConvert.SerializeObject(data);
|
string authenticateJson = JsonConvert.SerializeObject(data);
|
||||||
await sendText(authenticateJson);
|
if(wSock is not null)
|
||||||
|
await wSock.SendText(authenticateJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Socket(string token) : base(token)
|
public Socket(string token) : base(token)
|
||||||
|
|
Loading…
Reference in New Issue