From 4d0743c4aa02214e88904add9db3b825dcb82da8 Mon Sep 17 00:00:00 2001 From: Tom Date: Fri, 7 Mar 2025 17:34:27 +0000 Subject: [PATCH] Fixed emote duplicate issue. Improved chatter tracking. Used stores in Login Handler in cases where databases was used instead. --- Server.cs | 53 ++++++++++++-------------- Socket/Handlers/ChatterHandler.cs | 14 +++++-- Socket/Handlers/EmoteDetailsHandler.cs | 21 +++++++++- Socket/Handlers/HermesLoginHandler.cs | 45 +++++----------------- 4 files changed, 64 insertions(+), 69 deletions(-) diff --git a/Server.cs b/Server.cs index ccf1f18..2033e3f 100644 --- a/Server.cs +++ b/Server.cs @@ -2,7 +2,6 @@ using System.Net.WebSockets; using System.Text; using System.Text.Json; using CommonSocketLibrary.Common; -using HermesSocketLibrary.Requests.Messages; using HermesSocketLibrary.Socket.Data; using HermesSocketServer.Socket; using ILogger = Serilog.ILogger; @@ -51,12 +50,12 @@ namespace HermesSocketLibrary continue; if (message.OpCode != 0) - _logger.Information($"rxm: {messageString} [ip: {socket.IPAddress}][id: {socket.Id}][name: {socket.Name}][token: {socket.ApiKey}][uid: {socket.SessionId}]"); + _logger.Information($"receive: {messageString} [ip: {socket.IPAddress}][id: {socket.Id}][name: {socket.Name}][token: {socket.ApiKey}][uid: {socket.SessionId}]"); if (message.OpCode < 0 || message.OpCode > 8 || message.OpCode == 2 || message.OpCode == 4) { await socket.Send(5, new LoggingMessage("Received an invalid message: " + messageString, HermesLoggingLevel.Error)); - return; + break; } bool loggedIn = !string.IsNullOrEmpty(socket.Id); @@ -64,13 +63,13 @@ namespace HermesSocketLibrary if (!loggedIn && !nonProtectedOps.Contains(message.OpCode)) { _logger.Warning($"An attempt was made to use protected routes while not logged in [ip: {socket.IPAddress}][id: {socket.Id}][name: {socket.Name}][token: {socket.ApiKey}][uid: {socket.SessionId}]"); - return; + break; } int[] protectedOps = { 0, 3, 5, 6, 7, 8 }; if (loggedIn && !protectedOps.Contains(message.OpCode)) { _logger.Warning($"An attempt was made to use non-protected routes while logged in [ip: {socket.IPAddress}][id: {socket.Id}][name: {socket.Name}][token: {socket.ApiKey}][uid: {socket.SessionId}]"); - return; + break; } if (message.Data == null) @@ -80,27 +79,7 @@ namespace HermesSocketLibrary } string data = message.Data.ToString()!; - if (message.OpCode == 0) - message.Data = JsonSerializer.Deserialize(data, _options); - else if (message.OpCode == 1) - message.Data = JsonSerializer.Deserialize(data, _options); - else if (message.OpCode == 3) - message.Data = JsonSerializer.Deserialize(data, _options); - else if (message.OpCode == 5) - message.Data = JsonSerializer.Deserialize(data, _options); - else if (message.OpCode == 6) - message.Data = JsonSerializer.Deserialize(data, _options); - else if (message.OpCode == 7) - message.Data = JsonSerializer.Deserialize(data, _options); - else if (message.OpCode == 8) - message.Data = JsonSerializer.Deserialize(data, _options); - //else if (message.OpCode == 9) - // message.Data = JsonSerializer.Deserialize(data, _options); - else - { - await socket.Send(5, new LoggingMessage("Received a message with invalid data: " + messageString, HermesLoggingLevel.Error)); - continue; - } + message.Data = DeserializeData(message.OpCode, data); await _handlers.Execute(socket, message.OpCode, message.Data); } catch (WebSocketException wse) @@ -120,7 +99,7 @@ namespace HermesSocketLibrary } catch (Exception e) { - _logger.Information(e, $"Client failed to disconnect [ip: {socket.IPAddress}][id: {socket.Id}][name: {socket.Name}][token: {socket.ApiKey}][uid: {socket.SessionId}]"); + _logger.Warning(e, $"Client failed to disconnect [ip: {socket.IPAddress}][id: {socket.Id}][name: {socket.Name}][token: {socket.ApiKey}][uid: {socket.SessionId}]"); } finally { @@ -130,7 +109,6 @@ namespace HermesSocketLibrary _logger.Information($"Client disconnected [ip: {socket.IPAddress}][id: {socket.Id}][name: {socket.Name}][token: {socket.ApiKey}][uid: {socket.SessionId}]"); // Update slave status of another client from the same user if available. - // TODO: Ensure one of the clients is always a non-slave. if (socket.Id != null && !socket.Slave) { var client = _sockets.GetSockets(socket.Id).Where(s => !s.WebLogin).FirstOrDefault(); @@ -141,5 +119,24 @@ namespace HermesSocketLibrary } } } + + private object? DeserializeData(int opcode, string data) + { + if (opcode == 0) + return JsonSerializer.Deserialize(data, _options); + else if (opcode == 1) + return JsonSerializer.Deserialize(data, _options); + else if (opcode == 3) + return JsonSerializer.Deserialize(data, _options); + else if (opcode == 5) + return JsonSerializer.Deserialize(data, _options); + else if (opcode == 6) + return JsonSerializer.Deserialize(data, _options); + else if (opcode == 7) + return JsonSerializer.Deserialize(data, _options); + else if (opcode == 8) + return JsonSerializer.Deserialize(data, _options); + return null; + } } } \ No newline at end of file diff --git a/Socket/Handlers/ChatterHandler.cs b/Socket/Handlers/ChatterHandler.cs index 745e619..094a735 100644 --- a/Socket/Handlers/ChatterHandler.cs +++ b/Socket/Handlers/ChatterHandler.cs @@ -6,10 +6,12 @@ namespace HermesSocketServer.Socket.Handlers { public class ChatterHandler : ISocketHandler { + private const int CHATTER_BUFFER_SIZE = 2000; + public int OperationCode { get; } = 6; private readonly Database _database; private readonly HashSet _chatters; - private readonly ChatterMessage[] _array; + private readonly long[] _array; private readonly ILogger _logger; private readonly object _lock; @@ -19,8 +21,8 @@ namespace HermesSocketServer.Socket.Handlers { _database = database; _logger = logger; - _chatters = new HashSet(1001); - _array = new ChatterMessage[1000]; + _chatters = new HashSet(CHATTER_BUFFER_SIZE); + _array = new long[CHATTER_BUFFER_SIZE]; _index = -1; _lock = new object(); } @@ -40,7 +42,11 @@ namespace HermesSocketServer.Socket.Handlers if (_index == _array.Length - 1) _index = -1; - _array[++_index] = data; + var previous = _array[++_index]; + if (previous != 0) { + _chatters.Remove(previous); + } + _array[_index] = data.Id; } try diff --git a/Socket/Handlers/EmoteDetailsHandler.cs b/Socket/Handlers/EmoteDetailsHandler.cs index 81fa2bb..e290e79 100644 --- a/Socket/Handlers/EmoteDetailsHandler.cs +++ b/Socket/Handlers/EmoteDetailsHandler.cs @@ -7,18 +7,25 @@ namespace HermesSocketServer.Socket.Handlers { public class EmoteDetailsHandler : ISocketHandler { + private const int EMOTE_BUFFER_SIZE = 5000; + public int OperationCode { get; } = 7; private readonly Database _database; private readonly HashSet _emotes; + private readonly string[] _array; private readonly ILogger _logger; private readonly object _lock; + private int _index; + public EmoteDetailsHandler(Database database, ILogger logger) { _database = database; + _emotes = new HashSet(EMOTE_BUFFER_SIZE); + _array = new string[EMOTE_BUFFER_SIZE]; _logger = logger; - _emotes = new HashSet(501); _lock = new object(); + _index = -1; } public async Task Execute(WebSocketUser sender, T message, HermesSocketManager sockets) @@ -40,6 +47,16 @@ namespace HermesSocketServer.Socket.Handlers } _emotes.Add(entry.Key); + + if (_index == _array.Length - 1) + _index = -1; + + var previous = _array[++_index]; + if (previous != null) + { + _emotes.Remove(previous); + } + _array[_index] = entry.Key; } } @@ -47,7 +64,7 @@ namespace HermesSocketServer.Socket.Handlers return; int rows = 0; - string sql = "INSERT INTO \"Emote\" (id, name) VALUES (@idd, @name)"; + string sql = "INSERT INTO \"Emote\" (id, name) VALUES (@idd, @name) ON CONFLICT (id) DO UPDATE SET name = @name;"; using (var connection = await _database.DataSource.OpenConnectionAsync()) { using (var command = new NpgsqlCommand(sql, connection)) diff --git a/Socket/Handlers/HermesLoginHandler.cs b/Socket/Handlers/HermesLoginHandler.cs index 39a3505..e9a5931 100644 --- a/Socket/Handlers/HermesLoginHandler.cs +++ b/Socket/Handlers/HermesLoginHandler.cs @@ -42,33 +42,30 @@ namespace HermesSocketServer.Socket.Handlers var result = await _database.ExecuteScalar(sql, new Dictionary() { { "key", data.ApiKey } }); string? userId = result?.ToString(); - if (userId == null) + if (string.IsNullOrWhiteSpace(userId)) return; IEnumerable recipients = Enumerable.Empty(); lock (_lock) { if (sender.Id != null) - throw new Exception("User already logged in."); + return; sender.Id = userId; - - if (string.IsNullOrWhiteSpace(sender.Id)) - throw new Exception("Credentials do not match."); - recipients = _sockets.GetSockets(userId).ToList().Where(s => s.SessionId != sender.SessionId); - sender.Slave = data.WebLogin || recipients.Where(r => r?.WebLogin != true).Any(); + sender.Slave = data.WebLogin || recipients.Where(r => r != null && !r.WebLogin).Any(); } sender.ApiKey = data.ApiKey; sender.WebLogin = data.WebLogin; + // Fetch channel data. var channel = _manager.Get(userId); if (channel == null) { channel = await _manager.Add(userId); if (channel == null) - return; + throw new Exception("Channel does not exist."); } sender.Name = channel.User.Name; @@ -79,8 +76,6 @@ namespace HermesSocketServer.Socket.Handlers _logger.Error($"Could not find username for a certain user [user id: {userId}][api key: {data.ApiKey}]"); return; } - if (string.IsNullOrEmpty(channel.User.DefaultVoice)) - _logger.Warning($"No default voice was set for an user [user id: {userId}][api key: {data.ApiKey}]"); sql = "select \"providerAccountId\" from \"Account\" where \"userId\" = @user and provider = @provider"; var result2 = await _database.ExecuteScalar(sql, new Dictionary() { { "user", userId }, { "provider", "twitch" } }); @@ -91,6 +86,7 @@ namespace HermesSocketServer.Socket.Handlers return; } + var voices = _voices.Get(); var ack = new LoginAckMessage() { UserId = userId, @@ -103,35 +99,14 @@ namespace HermesSocketServer.Socket.Handlers WordFilters = channel.Filters.Get().Values, DefaultTTSVoice = channel.User.DefaultVoice ?? _configuration.Tts.DefaultTtsVoice, TTSVoicesAvailable = _voices.Get().ToDictionary(v => v.Key, v => v.Value.Name), + EnabledTTSVoices = channel.VoiceStates.Get().Values.Where(v => v.Enabled && voices.ContainsKey(v.Id)).Select(v => voices[v.Id].Name).ToList(), + Connections = channel.Connections.Get().Values.ToList(), Slave = sender.Slave, }; - var userIdDict = new Dictionary() { { "user", userId } }; - ack.Connections = new List(); - string sql3 = "select \"name\", \"type\", \"clientId\", \"accessToken\", \"grantType\", \"scope\", \"expiresAt\", \"default\" from \"Connection\" where \"userId\" = @user"; - await _database.Execute(sql3, userIdDict, sql => - ack.Connections.Add(new Connection() - { - UserId = channel.Id, - Name = sql.GetString(0), - Type = sql.GetString(1), - ClientId = sql.GetString(2), - AccessToken = sql.GetString(3), - GrantType = sql.GetString(4), - Scope = sql.GetString(5), - ExpiresAt = sql.GetDateTime(6), - Default = sql.GetBoolean(7) - }) - ); - - ack.EnabledTTSVoices = new List(); - string sql5 = $"SELECT v.name FROM \"TtsVoiceState\" s " - + "INNER JOIN \"TtsVoice\" v ON s.\"ttsVoiceId\" = v.id " - + "WHERE \"userId\" = @user AND state = true"; - await _database.Execute(sql5, userIdDict, (r) => ack.EnabledTTSVoices.Add(r.GetString(0))); - await sender.Send(2, ack); + // Sending notification to other clients about another client logging in. string version = $"{data.MajorVersion}.{data.MinorVersion}.{data.PatchVersion}"; _logger.Information($"Hermes client logged in {(sender.Admin ? "as administrator " : "")}[name: {sender.Name}][id: {userId}][ip: {sender.IPAddress}][version: {version}][web: {data.WebLogin}]"); @@ -148,7 +123,7 @@ namespace HermesSocketServer.Socket.Handlers { try { - tasks.Add(socket.Send(2, ack)); + tasks.Add(socket!.Send(2, ack)); } catch (Exception) {