PluralRichPresence/PluralRichPresnce/Discord/DiscordClient.cs

211 lines
8.0 KiB
C#

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));
}
}
}