sochkeal/Satori/SockChatClient.cs
2023-06-28 22:12:23 +02:00

277 lines
9.5 KiB
C#

using PureWebSockets;
using System;
using System.Collections.Generic;
using System.Net.WebSockets;
using System.Threading;
namespace SockChatKeepAlive {
public sealed class SockChatClient : IDisposable {
private readonly FutamiCommon Common;
private readonly PersistentData Persist;
private readonly Func<string[]> GetAuthInfo;
private PureWebSocket WebSocket;
private Timer Pinger;
private readonly object PersistAccess = new();
public record ChatUser(string Id, string Name, string Colour);
private Dictionary<string, ChatUser> Users { get; set; } = new();
public ChatUser MyUser { get; private set; }
private bool IsDisposed = false;
private DateTimeOffset LastPong = DateTimeOffset.Now;
public SockChatClient(
FutamiCommon common,
PersistentData persist,
Func<string[]> getAuthInfo
) {
Common = common;
Persist = persist;
GetAuthInfo = getAuthInfo;
}
~SockChatClient() {
DoDispose();
}
public void Dispose() {
DoDispose();
GC.SuppressFinalize(this);
}
private void DoDispose() {
if(IsDisposed)
return;
IsDisposed = true;
Disconnect();
}
public void Connect() {
string server = Common.Servers[RNG.Next(Common.Servers.Length)];
if(server.StartsWith("//"))
server = "wss:" + server;
Console.WriteLine($"Connecting to {server}...");
Disconnect();
WebSocket = new PureWebSocket(server, new PureWebSocketOptions {
SubProtocols = new[] { "sockchat" },
});
WebSocket.OnOpened += WebSocket_OnOpen;
WebSocket.OnClosed += WebSocket_OnClose;
WebSocket.OnMessage += WebSocket_OnMessage;
WebSocket.Connect();
}
public void Disconnect() {
MyUser = null;
if(Pinger != null) {
Pinger.Dispose();
Pinger = null;
}
if(WebSocket == null)
return;
try {
WebSocket.OnOpened -= WebSocket_OnOpen;
WebSocket.OnClosed -= WebSocket_OnClose;
WebSocket.OnMessage -= WebSocket_OnMessage;
WebSocket.Disconnect();
WebSocket = null;
} catch { }
}
public void SendMessage(string text) {
if(MyUser == null)
return;
Send("2", MyUser.Id, text.Replace("\t", " "));
}
public void SendMessage(object obj)
=> SendMessage(obj.ToString());
public void SendPing() {
if(MyUser != null)
Send("0", MyUser.Id);
}
public void Send(params object[] args) {
WebSocket.Send(string.Join("\t", args));
}
private void WebSocket_OnOpen(object sender) {
Console.WriteLine("WebSocket connected.");
Console.WriteLine();
string[] authInfo = GetAuthInfo();
Send("1", authInfo[0] ?? string.Empty, authInfo[1] ?? string.Empty);
}
private void WebSocket_OnClose(object sender, WebSocketCloseStatus reason) {
Console.WriteLine($"WebSocket disconnected: {reason}");
Disconnect();
if(!IsDisposed) {
lock(PersistAccess)
Persist.WasGracefulDisconnect = false;
Thread.Sleep(10000);
Connect();
}
}
private void WebSocket_OnMessage(object sender, string data) {
string[] args = data.Split('\t');
if(args.Length < 1)
return;
switch(args[0]) {
case "0":
TimeSpan pongDiff = DateTimeOffset.Now - LastPong;
LastPong = DateTimeOffset.Now;
lock(PersistAccess)
Persist.SatoriTotalUptime += (long)pongDiff.TotalMilliseconds;
break;
case "1":
if(MyUser == null) {
if(args[1] == "y") {
Pinger = new Timer(x => SendPing(), null, 0, Common.Ping * 1000);
} else {
Disconnect();
return;
}
}
Users[args[2]] = new(args[2], args[3], args[4]);
if(MyUser == null) {
MyUser = Users[args[2]];
lock(PersistAccess) {
if(Persist.WasGracefulDisconnect)
Persist.AFKString = MyUser.Name.StartsWith("&lt;")
? MyUser.Name[4..MyUser.Name.IndexOf("&gt;_")]
: string.Empty;
else {
string afkString = Persist.AFKString;
if(!string.IsNullOrWhiteSpace(afkString))
SendMessage($"/afk {afkString}");
}
}
} else
Console.WriteLine($"!! {args[2]} <{args[3]}> joined.");
break;
case "2":
Console.Write($":: {{{args[4]}}} [{DateTimeOffset.FromUnixTimeSeconds(long.Parse(args[1])):G}] ");
if(Users.ContainsKey(args[2]))
Console.Write($"{Users[args[2]].Id} <{Users[args[2]].Name}>");
else
Console.Write("*");
Console.WriteLine();
foreach(string line in args[3].Split(" <br/> ")) {
Console.Write("> ");
Console.WriteLine(line.Replace('\f', '\t').Trim());
}
Console.WriteLine();
break;
case "3":
Users.Remove(args[1]);
Console.WriteLine($"!! {args[1]} left.");
break;
case "5":
switch(args[1]) {
case "0":
Users[args[2]] = new(args[2], args[3], args[4]);
Console.WriteLine($"!! {args[2]} <{args[3]}> entered channel.");
break;
case "1":
Users.Remove(args[2]);
Console.WriteLine($"!! {args[2]} switched channel.");
break;
}
break;
case "6":
Console.WriteLine($"!! Message {args[1]} was deleted.");
break;
case "7":
if(args.Length < 2)
break;
switch(args[1]) {
case "0":
if(args.Length < 3 || !int.TryParse(args[2], out int amount))
break;
int offset = 3;
for(int i = 0; i < amount; i++) {
Users[args[offset++]] = new(args[offset], args[offset++], args[offset++]);
offset += 2;
}
break;
case "1":
Console.Write($":: {{{args[8]}}} [{DateTimeOffset.FromUnixTimeSeconds(long.Parse(args[2])):G}] ");
if(args[3].Equals("-1", StringComparison.Ordinal))
Console.Write("*");
else
Console.Write($"{args[3]} <{args[4]}>");
Console.WriteLine();
foreach(string line in args[7].Split(" <br/> ")) {
Console.Write("> ");
Console.WriteLine(line.Replace('\f', '\t').Trim());
}
Console.WriteLine();
break;
}
break;
case "8":
if(args[1] is "0" or "4")
Console.WriteLine("!! Message list cleared");
if(args[1] is "1" or "3" or "4") {
Console.WriteLine("!! User list cleared");
Users.Clear();
Users.Add(MyUser.Id, MyUser);
}
if(args[1] is "2" or "3" or "4")
Console.WriteLine("!! Channel list cleared");
break;
case "9":
Console.WriteLine($"!! Kicked from server: {args[1]} {args[2]}");
break;
case "10":
Users[args[1]] = new(args[1], args[2], args[3]);
Console.WriteLine($"!! {args[1]} updated: {args[2]}.");
if(MyUser.Id == args[1]) {
MyUser = Users[args[1]];
lock(PersistAccess)
Persist.AFKString = MyUser.Name.StartsWith("&lt;")
? MyUser.Name[4..MyUser.Name.IndexOf("&gt;_")]
: string.Empty;
}
break;
}
}
}
}