Fixed emote duplicate issue. Improved chatter tracking. Used stores in Login Handler in cases where databases was used instead.

This commit is contained in:
Tom
2025-03-07 17:34:27 +00:00
parent fd0bca5c7c
commit 4d0743c4aa
4 changed files with 64 additions and 69 deletions

View File

@ -2,7 +2,6 @@ using System.Net.WebSockets;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using CommonSocketLibrary.Common; using CommonSocketLibrary.Common;
using HermesSocketLibrary.Requests.Messages;
using HermesSocketLibrary.Socket.Data; using HermesSocketLibrary.Socket.Data;
using HermesSocketServer.Socket; using HermesSocketServer.Socket;
using ILogger = Serilog.ILogger; using ILogger = Serilog.ILogger;
@ -51,12 +50,12 @@ namespace HermesSocketLibrary
continue; continue;
if (message.OpCode != 0) 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) 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)); await socket.Send(5, new LoggingMessage("Received an invalid message: " + messageString, HermesLoggingLevel.Error));
return; break;
} }
bool loggedIn = !string.IsNullOrEmpty(socket.Id); bool loggedIn = !string.IsNullOrEmpty(socket.Id);
@ -64,13 +63,13 @@ namespace HermesSocketLibrary
if (!loggedIn && !nonProtectedOps.Contains(message.OpCode)) 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}]"); _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 }; int[] protectedOps = { 0, 3, 5, 6, 7, 8 };
if (loggedIn && !protectedOps.Contains(message.OpCode)) 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}]"); _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) if (message.Data == null)
@ -80,27 +79,7 @@ namespace HermesSocketLibrary
} }
string data = message.Data.ToString()!; string data = message.Data.ToString()!;
if (message.OpCode == 0) message.Data = DeserializeData(message.OpCode, data);
message.Data = JsonSerializer.Deserialize<HeartbeatMessage>(data, _options);
else if (message.OpCode == 1)
message.Data = JsonSerializer.Deserialize<HermesLoginMessage>(data, _options);
else if (message.OpCode == 3)
message.Data = JsonSerializer.Deserialize<RequestMessage>(data, _options);
else if (message.OpCode == 5)
message.Data = JsonSerializer.Deserialize<LoggingMessage>(data, _options);
else if (message.OpCode == 6)
message.Data = JsonSerializer.Deserialize<ChatterMessage>(data, _options);
else if (message.OpCode == 7)
message.Data = JsonSerializer.Deserialize<EmoteDetailsMessage>(data, _options);
else if (message.OpCode == 8)
message.Data = JsonSerializer.Deserialize<EmoteUsageMessage>(data, _options);
//else if (message.OpCode == 9)
// message.Data = JsonSerializer.Deserialize<SlaveMessage>(data, _options);
else
{
await socket.Send(5, new LoggingMessage("Received a message with invalid data: " + messageString, HermesLoggingLevel.Error));
continue;
}
await _handlers.Execute(socket, message.OpCode, message.Data); await _handlers.Execute(socket, message.OpCode, message.Data);
} }
catch (WebSocketException wse) catch (WebSocketException wse)
@ -120,7 +99,7 @@ namespace HermesSocketLibrary
} }
catch (Exception e) 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 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}]"); _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. // 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) if (socket.Id != null && !socket.Slave)
{ {
var client = _sockets.GetSockets(socket.Id).Where(s => !s.WebLogin).FirstOrDefault(); 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<HeartbeatMessage>(data, _options);
else if (opcode == 1)
return JsonSerializer.Deserialize<HermesLoginMessage>(data, _options);
else if (opcode == 3)
return JsonSerializer.Deserialize<RequestMessage>(data, _options);
else if (opcode == 5)
return JsonSerializer.Deserialize<LoggingMessage>(data, _options);
else if (opcode == 6)
return JsonSerializer.Deserialize<ChatterMessage>(data, _options);
else if (opcode == 7)
return JsonSerializer.Deserialize<EmoteDetailsMessage>(data, _options);
else if (opcode == 8)
return JsonSerializer.Deserialize<EmoteUsageMessage>(data, _options);
return null;
}
} }
} }

View File

@ -6,10 +6,12 @@ namespace HermesSocketServer.Socket.Handlers
{ {
public class ChatterHandler : ISocketHandler public class ChatterHandler : ISocketHandler
{ {
private const int CHATTER_BUFFER_SIZE = 2000;
public int OperationCode { get; } = 6; public int OperationCode { get; } = 6;
private readonly Database _database; private readonly Database _database;
private readonly HashSet<long> _chatters; private readonly HashSet<long> _chatters;
private readonly ChatterMessage[] _array; private readonly long[] _array;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly object _lock; private readonly object _lock;
@ -19,8 +21,8 @@ namespace HermesSocketServer.Socket.Handlers
{ {
_database = database; _database = database;
_logger = logger; _logger = logger;
_chatters = new HashSet<long>(1001); _chatters = new HashSet<long>(CHATTER_BUFFER_SIZE);
_array = new ChatterMessage[1000]; _array = new long[CHATTER_BUFFER_SIZE];
_index = -1; _index = -1;
_lock = new object(); _lock = new object();
} }
@ -40,7 +42,11 @@ namespace HermesSocketServer.Socket.Handlers
if (_index == _array.Length - 1) if (_index == _array.Length - 1)
_index = -1; _index = -1;
_array[++_index] = data; var previous = _array[++_index];
if (previous != 0) {
_chatters.Remove(previous);
}
_array[_index] = data.Id;
} }
try try

View File

@ -7,18 +7,25 @@ namespace HermesSocketServer.Socket.Handlers
{ {
public class EmoteDetailsHandler : ISocketHandler public class EmoteDetailsHandler : ISocketHandler
{ {
private const int EMOTE_BUFFER_SIZE = 5000;
public int OperationCode { get; } = 7; public int OperationCode { get; } = 7;
private readonly Database _database; private readonly Database _database;
private readonly HashSet<string> _emotes; private readonly HashSet<string> _emotes;
private readonly string[] _array;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly object _lock; private readonly object _lock;
private int _index;
public EmoteDetailsHandler(Database database, ILogger logger) public EmoteDetailsHandler(Database database, ILogger logger)
{ {
_database = database; _database = database;
_emotes = new HashSet<string>(EMOTE_BUFFER_SIZE);
_array = new string[EMOTE_BUFFER_SIZE];
_logger = logger; _logger = logger;
_emotes = new HashSet<string>(501);
_lock = new object(); _lock = new object();
_index = -1;
} }
public async Task Execute<T>(WebSocketUser sender, T message, HermesSocketManager sockets) public async Task Execute<T>(WebSocketUser sender, T message, HermesSocketManager sockets)
@ -40,6 +47,16 @@ namespace HermesSocketServer.Socket.Handlers
} }
_emotes.Add(entry.Key); _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; return;
int rows = 0; 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 connection = await _database.DataSource.OpenConnectionAsync())
{ {
using (var command = new NpgsqlCommand(sql, connection)) using (var command = new NpgsqlCommand(sql, connection))

View File

@ -42,33 +42,30 @@ namespace HermesSocketServer.Socket.Handlers
var result = await _database.ExecuteScalar(sql, new Dictionary<string, object>() { { "key", data.ApiKey } }); var result = await _database.ExecuteScalar(sql, new Dictionary<string, object>() { { "key", data.ApiKey } });
string? userId = result?.ToString(); string? userId = result?.ToString();
if (userId == null) if (string.IsNullOrWhiteSpace(userId))
return; return;
IEnumerable<WebSocketUser?> recipients = Enumerable.Empty<WebSocketUser?>(); IEnumerable<WebSocketUser?> recipients = Enumerable.Empty<WebSocketUser?>();
lock (_lock) lock (_lock)
{ {
if (sender.Id != null) if (sender.Id != null)
throw new Exception("User already logged in."); return;
sender.Id = userId; 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); 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.ApiKey = data.ApiKey;
sender.WebLogin = data.WebLogin; sender.WebLogin = data.WebLogin;
// Fetch channel data.
var channel = _manager.Get(userId); var channel = _manager.Get(userId);
if (channel == null) if (channel == null)
{ {
channel = await _manager.Add(userId); channel = await _manager.Add(userId);
if (channel == null) if (channel == null)
return; throw new Exception("Channel does not exist.");
} }
sender.Name = channel.User.Name; 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}]"); _logger.Error($"Could not find username for a certain user [user id: {userId}][api key: {data.ApiKey}]");
return; 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"; sql = "select \"providerAccountId\" from \"Account\" where \"userId\" = @user and provider = @provider";
var result2 = await _database.ExecuteScalar(sql, new Dictionary<string, object>() { { "user", userId }, { "provider", "twitch" } }); var result2 = await _database.ExecuteScalar(sql, new Dictionary<string, object>() { { "user", userId }, { "provider", "twitch" } });
@ -91,6 +86,7 @@ namespace HermesSocketServer.Socket.Handlers
return; return;
} }
var voices = _voices.Get();
var ack = new LoginAckMessage() var ack = new LoginAckMessage()
{ {
UserId = userId, UserId = userId,
@ -103,35 +99,14 @@ namespace HermesSocketServer.Socket.Handlers
WordFilters = channel.Filters.Get().Values, WordFilters = channel.Filters.Get().Values,
DefaultTTSVoice = channel.User.DefaultVoice ?? _configuration.Tts.DefaultTtsVoice, DefaultTTSVoice = channel.User.DefaultVoice ?? _configuration.Tts.DefaultTtsVoice,
TTSVoicesAvailable = _voices.Get().ToDictionary(v => v.Key, v => v.Value.Name), 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, Slave = sender.Slave,
}; };
var userIdDict = new Dictionary<string, object>() { { "user", userId } };
ack.Connections = new List<Connection>();
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>();
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); await sender.Send(2, ack);
// Sending notification to other clients about another client logging in.
string version = $"{data.MajorVersion}.{data.MinorVersion}.{data.PatchVersion}"; 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}]"); _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 try
{ {
tasks.Add(socket.Send(2, ack)); tasks.Add(socket!.Send(2, ack));
} }
catch (Exception) catch (Exception)
{ {