make it work w multiple fronters

This commit is contained in:
Li 2023-12-28 23:28:49 +13:00
parent 52ae992fac
commit 324a14a4e4
16 changed files with 289 additions and 126 deletions

View File

@ -0,0 +1,49 @@
using System.Drawing;
using System.Drawing.Drawing2D;
namespace PluralRichPresence.Avatar
{
public class ImageSplitter
{
public int Width;
public int Height;
private Image[] images;
public ImageSplitter(int width, int height, Image[] images)
{
Width = width;
Height = height;
this.images = images;
}
public Bitmap Draw()
{
if (OperatingSystem.IsWindowsVersionAtLeast(6, 1))
{
Bitmap imageBuffer = new Bitmap(Width, Height);
using (Graphics graphicsObject = Graphics.FromImage(imageBuffer))
{
graphicsObject.Clear(Color.Black);
float partAngle = 360.0f / images.Length;
float currentAngle = 0;
foreach (Image image in images)
{
GraphicsPath path = new GraphicsPath();
path.AddPie(-Width / 2, -Height / 2, Width * 2, Height * 2, currentAngle, partAngle);
graphicsObject.SetClip(path);
graphicsObject.DrawImage(image, 0, 0, Width, Height);
currentAngle += partAngle;
}
}
return imageBuffer;
}
{
throw new NotSupportedException("This OS is unsupported");
}
}
}
}

View File

@ -0,0 +1,73 @@
namespace PluralRichPresence
{
public static class Config
{
private const string CFG_FILE = "plurality.cfg";
private const string SEPERATOR = ":";
private static Dictionary<string, string> cfgEntries = new Dictionary<string, string>();
static Config()
{
loadCfg();
}
private static void loadCfg()
{
cfgEntries.Clear();
if (!File.Exists(CFG_FILE))
saveCfg();
using (StreamReader cfgReader = File.OpenText(CFG_FILE))
{
for (string? line = cfgReader.ReadLine(); line is not null; line = cfgReader.ReadLine())
{
string[] values = line.Split(SEPERATOR);
cfgEntries.Add(values.First().Trim(), String.Join(SEPERATOR, values.Skip(1)).Trim());
}
}
}
private static void saveCfg()
{
using (StreamWriter cfgWriter = File.CreateText(CFG_FILE))
foreach (KeyValuePair<String, String> values in cfgEntries)
cfgWriter.WriteLine(String.Join(SEPERATOR, new String[] { values.Key.Trim(), values.Value.Trim() }));
}
public static void SetEntry(string key, string value)
{
key = key.Trim();
value = value.Trim().ReplaceLineEndings(String.Empty);
if (EntryExists(key))
cfgEntries[key] = value;
else
cfgEntries.Add(key, value);
saveCfg();
}
public static bool EntryExists(string key)
{
return cfgEntries.ContainsKey(key);
}
public static string GetEntry(string key)
{
return EntryExists(key) ? cfgEntries[key] : String.Empty;
}
public static void RemoveEntry(string key)
{
cfgEntries.Remove(key.Trim());
saveCfg();
}
public static string GetEntry(string key, string defaultValue)
{
if (!EntryExists(key))
{
SetEntry(key, defaultValue);
return defaultValue;
}
return GetEntry(key);
}
}
}

View File

@ -1,33 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PluralRichPresence
{
public class ConfigReader
{
const string CFG_NAME = "plurality.cfg";
const char SEPERATOR = ':';
public static string ReadString(string key)
{
StreamReader txtReader = new StreamReader(File.OpenRead(CFG_NAME));
for (string? line = txtReader.ReadLine(); line is not null; line = txtReader.ReadLine())
{
line = line.Trim().ReplaceLineEndings(String.Empty);
if (!line.Contains(SEPERATOR)) continue;
string[] configOptions = line.Split(SEPERATOR);
if (configOptions[0].Trim() == key)
return configOptions[1].Trim();
}
return "";
}
}
}

View File

@ -1,11 +1,5 @@
using DiscordRPC;
using DiscordRPC.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit;
using System.Text;
using System.Threading.Tasks;
namespace PluralRichPresence
{
@ -13,12 +7,12 @@ namespace PluralRichPresence
public class DiscordRPC
{
private DiscordRpcClient client;
private const string DEFAULT_APPLICATION_ID = "1163661006719963158";
public DiscordRPC()
{
client = new DiscordRpcClient(ConfigReader.ReadString("APPLICATION_ID"));
client = new DiscordRpcClient(Config.GetEntry("DISCORD_APPLICATION_ID", DEFAULT_APPLICATION_ID));
client.Logger = new ConsoleLogger() { Level = LogLevel.Warning };
client.Logger = new ConsoleLogger() { Level = LogLevel.None };
client.OnReady += (sender, e) => {};

View File

@ -0,0 +1,20 @@
namespace PluralRichPresence
{
public class Logger
{
public static void Debug(string text)
{
#if DEBUG
Console.WriteLine(text);
#endif
}
public static void Error(string text)
{
Console.Error.WriteLine(text);
}
public static void Info(string text)
{
Console.WriteLine(text);
}
}
}

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
@ -10,6 +10,7 @@
<ItemGroup>
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.Drawing.Common" Version="8.0.0" />
</ItemGroup>
</Project>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<_LastSelectedProfileId>C:\Users\Li\Documents\git\PluralRichPresence\PluralRichPresnce\Properties\PublishProfiles\FolderProfile.pubxml</_LastSelectedProfileId>
<_LastSelectedProfileId>C:\Users\Li\Desktop\git\PluralRichPresence\PluralRichPresnce\Properties\PublishProfiles\FolderProfile.pubxml</_LastSelectedProfileId>
</PropertyGroup>
</Project>

View File

@ -1,15 +1,9 @@
using DiscordRPC;
using DiscordRPC.Logging;
using Newtonsoft.Json;
using PluralRichPresence.Avatar;
using PluralRichPresence.SimplyPlural;
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading.Tasks;
using static System.Net.Mime.MediaTypeNames;
using System.Net;
namespace PluralRichPresence
{
@ -17,29 +11,97 @@ namespace PluralRichPresence
{
private static ManualResetEvent waitHandle = new ManualResetEvent(false);
private static DiscordRPC discordRpc = new DiscordRPC();
public static System system;
public static async Task UpdateFronterStatus()
private static System? system;
private const string KEY_SIMPLY_PLURAL_TOKEN = "SIMPLY_PLURAL_TOKEN";
public static string FmtFronterNames(Member[] fronters)
{
// get fronting members
Member[] frontingMembers = await system.GetCurrentFronterInfo();
if (frontingMembers.Length > 0)
List<string> names = new List<string>();
foreach(Member fronter in fronters)
names.Add(fronter.Name);
return String.Join(", ", names.ToArray());
}
public static string FmtFronterPronouns(Member[] fronters)
{
List<string> pronouns = new List<string>();
foreach (Member fronter in fronters)
pronouns.Add(fronter.Pronouns);
return String.Join(", ", pronouns.ToArray());
}
public static string FmtAvatar(Member[] fronters)
{
if (false && fronters.Length > 1 && OperatingSystem.IsWindowsVersionAtLeast(6, 1))
{
Member fronter = frontingMembers.First();
discordRpc.SetFronter(fronter.Name, fronter.Pronouns, (fronter.AvatarURL is null) ? "plural" : fronter.AvatarURL, fronter.FrontStartTime);
List<Image> images = new List<Image>();
foreach (Member fronter in fronters)
{
byte[]? avaData = fronter.DownloadAvatar();
if (avaData is not null)
using (MemoryStream ms = new MemoryStream(avaData))
images.Add(Image.FromStream(ms));
}
ImageSplitter splitter = new ImageSplitter(600, 600, images.ToArray());
using (Bitmap bmp = splitter.Draw())
{
using (MemoryStream pngData = new MemoryStream())
{
bmp.Save(pngData, ImageFormat.Png);
byte[] pngBytes = pngData.ToArray();
return "data:image/png;base64,"+Convert.ToBase64String(pngBytes);
}
}
}
else
{
string? avaUrl = fronters.First().AvatarURL;
return (avaUrl is null) ? String.Empty : avaUrl;
}
}
public static async Task UpdateFronterStatus()
{
// get fronting members
if (system is null) return;
Member[] frontingMembers = await system.GetCurrentFronterInfo();
if (frontingMembers.Length > 0)
{
Console.Write("\r" + FmtFronterNames(frontingMembers) + " is fronting!");
discordRpc.SetFronter(FmtFronterNames(frontingMembers),
FmtFronterPronouns(frontingMembers),
FmtAvatar(frontingMembers),
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);
}
}
public static void Main(string[] args)
public static async Task Main(string[] args)
{
system = new System(ConfigReader.ReadString("SIMPLY_PLURAL_TOKEN"));
system.FronterChanged += onFronterChanged;
_ = UpdateFronterStatus();
if(!Config.EntryExists(KEY_SIMPLY_PLURAL_TOKEN))
{
Console.Write("Enter Simply Plural API Key: ");
string? token = Console.ReadLine();
if (token is not null)
Config.SetEntry(KEY_SIMPLY_PLURAL_TOKEN, token);
}
Logger.Info("Connecting to Simply Plural ...");
try
{
system = new System(Config.GetEntry(KEY_SIMPLY_PLURAL_TOKEN));
system.FronterChanged += onFronterChanged;
await UpdateFronterStatus();
}
catch (HttpRequestException) { Logger.Error("Simply Plural API key is invalid!"); }
waitHandle.WaitOne();
}

View File

@ -6,10 +6,10 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<PublishDir>bin\Release\net7.0\publish\win-x86\</PublishDir>
<PublishDir>bin\Release\net8.0\publish\win-x86\</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<_TargetId>Folder</_TargetId>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishSingleFile>true</PublishSingleFile>

View File

@ -4,7 +4,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project>
<PropertyGroup>
<History>True|2023-11-19T04:13:26.6652642Z;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|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>
<LastFailureDetails />
</PropertyGroup>
</Project>

View File

@ -1,10 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PluralRichPresence.SimplyPlural
namespace PluralRichPresence.SimplyPlural
{
public class ApiType
{

View File

@ -1,10 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PluralRichPresence.SimplyPlural
namespace PluralRichPresence.SimplyPlural
{
public class Fronter
{

View File

@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Net.WebRequestMethods;
using System.Drawing;
namespace PluralRichPresence.SimplyPlural
{
@ -28,6 +23,13 @@ namespace PluralRichPresence.SimplyPlural
}
public ulong? FrontStartTime = null;
public byte[]? DownloadAvatar()
{
if(this.AvatarURL is not null)
return Rest.Download(this.AvatarURL).Result;
return null;
}
public Member(string systemId,
string name,
string pronouns,

View File

@ -1,27 +1,40 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Net.Http.Headers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PluralRichPresence.SimplyPlural
{
public class Rest : ApiType
{
private HttpClient client = new HttpClient();
private const string HOST = "https://api.apparyllis.com";
private static HttpClient client = new HttpClient();
private const string DEFAULT_HOST = "https://api.apparyllis.com";
private async Task<dynamic> get(string apiEndpoint)
{
HttpResponseMessage response = await client.GetAsync(HOST + apiEndpoint);
HttpResponseMessage response = await client.GetAsync(Config.GetEntry("SIMPLY_PLURAL_API_URI", DEFAULT_HOST) + apiEndpoint);
response.EnsureSuccessStatusCode();
string responseText = Encoding.UTF8.GetString(await response.Content.ReadAsByteArrayAsync());
Console.WriteLine("HTTP: " + responseText);
Logger.Debug("HTTP: " + responseText);
dynamic? result = JsonConvert.DeserializeObject(responseText);
return (result is null) ? new JObject() : (dynamic)result;
}
public static async Task<byte[]> Download(string url)
{
HttpResponseMessage response = await client.GetAsync(url);
return await response.Content.ReadAsByteArrayAsync();
}
public static async Task<string> UploadImage(string url, byte[] data)
{
MultipartFormDataContent requestContent = new MultipartFormDataContent();
ByteArrayContent imageContent = new ByteArrayContent(data);
imageContent.Headers.ContentType = MediaTypeHeaderValue.Parse("image/png");
requestContent.Add(imageContent, "image", "image.png");
await client.PostAsync(url, requestContent);
return "";
}
public async Task<string> GetSystemId()
{
dynamic system = await get("/v1/me");

View File

@ -1,25 +1,20 @@
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using System.Dynamic;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace PluralRichPresence.SimplyPlural
{
public class Socket : ApiType
{
public const int KEEPALIVE_INTERVAL = 10 * 1000;
public const string WEBSOCKET_SERVER_URI = "wss://api.apparyllis.com/v1/socket";
public event EventHandler FronterChanged;
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;
private void onFronterChanged(dynamic fronterChangedEventData)
{
if(FronterChanged is not null)
@ -55,7 +50,7 @@ namespace PluralRichPresence.SimplyPlural
return totalPayload.ToArray();
}
private async Task doUpdate(dynamic jsonData)
private void doUpdate(dynamic jsonData)
{
string target = jsonData.target;
switch (target)
@ -73,20 +68,28 @@ namespace PluralRichPresence.SimplyPlural
try
{
string message = await receiveMessageText();
Console.WriteLine("< " + message);
Logger.Debug("< " + message);
if (message == "pong") continue;
try
{
dynamic? jsonData = JsonConvert.DeserializeObject(message);
if (jsonData is null) continue;
string type = jsonData.msg;
if (type == "update")
await doUpdate(jsonData);
string? type = jsonData.msg;
switch (type)
{
case "update":
doUpdate(jsonData);
break;
case null:
default:
break;
}
}
catch (Exception) { };
}
catch (Exception) { Console.WriteLine("failed"); break; };
catch (Exception) { Logger.Debug("failed"); break; };
}
}
private async Task reconnect()
@ -109,7 +112,7 @@ namespace PluralRichPresence.SimplyPlural
_ = Task.Run(() => receiveTask());
this.keepAliveTimer = new Timer((TimerCallback) => { _ = sendKeepAlive(); }, null, KEEPALIVE_INTERVAL, 0);
}
catch (Exception) { Console.WriteLine("failed to connect."); continue; }
catch (Exception) { Logger.Debug("failed to connect."); continue; }
break;
}
@ -122,14 +125,13 @@ namespace PluralRichPresence.SimplyPlural
}
private async Task connect()
{
Console.WriteLine("Connecting to " + WEBSOCKET_SERVER_URI);
wss = new ClientWebSocket();
await wss.ConnectAsync(new Uri(WEBSOCKET_SERVER_URI), CancellationToken.None);
await wss.ConnectAsync(new Uri(Config.GetEntry("SIMPLY_PLURAL_WEBSOCKET_URI", DEFAULT_WEBSOCKET_SERVER_URI)), CancellationToken.None);
}
private async Task sendText(string text)
{
Console.WriteLine("> "+text);
Logger.Debug("> "+text);
try
{
await wss.SendAsync(Encoding.UTF8.GetBytes(text), WebSocketMessageType.Text, true, CancellationToken.None);

View File

@ -1,12 +1,4 @@
using Newtonsoft.Json.Linq;
using PluralRichPresence.SimplyPlural;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using PluralRichPresence.SimplyPlural;
namespace PluralRichPresence
{
public class System