277 lines
9.5 KiB
C#
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("<")
|
|||
|
? MyUser.Name[4..MyUser.Name.IndexOf(">_")]
|
|||
|
: 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("<")
|
|||
|
? MyUser.Name[4..MyUser.Name.IndexOf(">_")]
|
|||
|
: string.Empty;
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|