diff --git a/Requests/CreateTTSUser.cs b/Requests/CreateTTSUser.cs index 140bb06..439bf2d 100644 --- a/Requests/CreateTTSUser.cs +++ b/Requests/CreateTTSUser.cs @@ -1,4 +1,3 @@ -using HermesSocketLibrary.db; using HermesSocketServer.Models; using ILogger = Serilog.ILogger; @@ -8,28 +7,26 @@ namespace HermesSocketServer.Requests { public string Name => "create_tts_user"; public string[] RequiredKeys => ["chatter", "voice"]; - private Database _database; private readonly ServerConfiguration _configuration; private ILogger _logger; - public CreateTTSUser(Database database, ServerConfiguration configuration, ILogger logger) + public CreateTTSUser(ServerConfiguration configuration, ILogger logger) { - _database = database; _configuration = configuration; _logger = logger; } - public async Task Grant(Channel channel, IDictionary data) + public Task Grant(Channel channel, IDictionary data) { if (!long.TryParse(data["chatter"].ToString(), out long chatterId)) - return RequestResult.Failed("Invalid Twitch user id"); + return Task.FromResult(RequestResult.Failed("Invalid Twitch user id.")); data["user"] = channel.Id; - data["voice"] = data["voice"].ToString()!; + var voiceId = data["voice"].ToString()!; - var check = await _database.ExecuteScalar("SELECT state FROM \"TtsVoiceState\" WHERE \"userId\" = @user AND \"ttsVoiceId\" = @voice", data) ?? false; - if ((check is not bool state || !state) && chatterId != _configuration.Tts.OwnerId) - return RequestResult.Failed("Voice is disabled on this channel."); + var check = channel.VoiceStates.Get(voiceId)?.Enabled ?? false; + if (!check && chatterId != _configuration.Tts.OwnerId) + return Task.FromResult(RequestResult.Failed("Voice is disabled on this channel.")); bool result = channel.Chatters.Set(chatterId.ToString(), new ChatterVoice() { @@ -40,10 +37,10 @@ namespace HermesSocketServer.Requests if (result) { - _logger.Information($"Selected a tts voice [voice: {data["voice"]}] for user [chatter: {data["chatter"]}] in channel [channel: {data["user"]}]"); - return RequestResult.Successful(null); + _logger.Information($"Selected a tts voice [voice: {voiceId}] for user [chatter: {chatterId}] in channel [channel: {channel.Id}]"); + return Task.FromResult(RequestResult.Successful(null)); } - return RequestResult.Failed("Something went wrong when updating the cache."); + return Task.FromResult(RequestResult.Failed("Something went wrong when updating the cache.")); } } } \ No newline at end of file diff --git a/Requests/UpdateTTSUser.cs b/Requests/UpdateTTSUser.cs index 6416fd6..0f037d2 100644 --- a/Requests/UpdateTTSUser.cs +++ b/Requests/UpdateTTSUser.cs @@ -1,6 +1,4 @@ -using HermesSocketLibrary.db; using HermesSocketServer.Models; -using HermesSocketServer.Services; using ILogger = Serilog.ILogger; namespace HermesSocketServer.Requests @@ -9,28 +7,27 @@ namespace HermesSocketServer.Requests { public string Name => "update_tts_user"; public string[] RequiredKeys => ["chatter", "voice"]; - private Database _database; private readonly ServerConfiguration _configuration; private ILogger _logger; - public UpdateTTSUser(Database database, ServerConfiguration configuration, ILogger logger) + public UpdateTTSUser(ServerConfiguration configuration, ILogger logger) { - _database = database; _configuration = configuration; _logger = logger; } - public async Task Grant(Channel channel, IDictionary data) + public Task Grant(Channel channel, IDictionary data) { if (!long.TryParse(data["chatter"].ToString(), out long chatterId)) - return RequestResult.Failed("Chatter should be an integer, representing the Twitch User Id of the chatter."); + return Task.FromResult(RequestResult.Failed("Invalid Twitch user id.")); + data["user"] = channel.Id; var voiceId = data["voice"].ToString()!; - var check = await _database.ExecuteScalar("SELECT state FROM \"TtsVoiceState\" WHERE \"userId\" = @user AND \"ttsVoiceId\" = @voice", data) ?? false; - if ((check is not bool state || !state) && chatterId != _configuration.Tts.OwnerId) - return RequestResult.Failed("Voice is either non-existent or disabled on this channel."); + var check = channel.VoiceStates.Get(voiceId)?.Enabled ?? false; + if (!check && chatterId != _configuration.Tts.OwnerId) + return Task.FromResult(RequestResult.Failed("Voice is either non-existent or disabled on this channel.")); var voice = new ChatterVoice() { @@ -43,9 +40,9 @@ namespace HermesSocketServer.Requests if (result) { _logger.Information($"Updated chatter's selected tts voice on channel [chatter id: {chatterId}][voice id: {voiceId}][channel: {channel.Id}]"); - return RequestResult.Successful(voice); + return Task.FromResult(RequestResult.Successful(voice)); } - return RequestResult.Failed("Soemthing went wrong when updating the cache."); + return Task.FromResult(RequestResult.Failed("Soemthing went wrong when updating the cache.")); } } } \ No newline at end of file diff --git a/Requests/UpdateTTSVoiceState.cs b/Requests/UpdateTTSVoiceState.cs index 08c7cc7..921e78c 100644 --- a/Requests/UpdateTTSVoiceState.cs +++ b/Requests/UpdateTTSVoiceState.cs @@ -20,20 +20,20 @@ namespace HermesSocketServer.Requests public Task Grant(Channel channel, IDictionary data) { - var id = data["voice"].ToString()!; - var state = data["state"].ToString() == "True"; + var voiceId = data["voice"].ToString()!; + var state = data["state"].ToString()?.ToLower() == "true"; var voiceState = new TTSVoiceState() { - Id = id, + Id = voiceId, UserId = channel.Id, Enabled = state, }; - var result = channel.VoiceStates.Set(id, voiceState); + var result = channel.VoiceStates.Set(voiceId, voiceState); if (result) { - _logger.Information($"Updated voice state on channel [voice id: {id}][state: {state}][channel: {channel.Id}]"); + _logger.Information($"Updated voice state on channel [voice id: {voiceId}][state: {state}][channel: {channel.Id}]"); return Task.FromResult(RequestResult.Successful(voiceState)); } return Task.FromResult(RequestResult.Failed("Something went wrong when updating the database.")); diff --git a/Server.cs b/Server.cs index 15a29c0..ccf1f18 100644 --- a/Server.cs +++ b/Server.cs @@ -2,6 +2,7 @@ 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; @@ -32,7 +33,7 @@ namespace HermesSocketLibrary public async Task Handle(WebSocketUser socket, HttpContext context) { - _logger.Information($"Socket connected [ip: {socket.IPAddress}][uid: {socket.UID}]"); + _logger.Information($"Socket connected [ip: {socket.IPAddress}][uid: {socket.SessionId}]"); _sockets.Add(socket); var buffer = new byte[1024 * 8]; @@ -44,70 +45,71 @@ namespace HermesSocketLibrary if (result == null || result.MessageType == WebSocketMessageType.Close || !socket.Connected) break; - string message = Encoding.UTF8.GetString(buffer, 0, result.Count).TrimEnd('\0'); - var obj = JsonSerializer.Deserialize(message, _options); - if (obj == null) + string messageString = Encoding.UTF8.GetString(buffer, 0, result.Count).TrimEnd('\0'); + var message = JsonSerializer.Deserialize(messageString, _options); + if (message == null) continue; - if (obj.OpCode != 0) - _logger.Information($"rxm: {message} [ip: {socket.IPAddress}][id: {socket.Id}][name: {socket.Name}][token: {socket.ApiKey}][uid: {socket.UID}]"); + if (message.OpCode != 0) + _logger.Information($"rxm: {messageString} [ip: {socket.IPAddress}][id: {socket.Id}][name: {socket.Name}][token: {socket.ApiKey}][uid: {socket.SessionId}]"); - int[] nonProtectedOps = { 0, 1 }; - if (string.IsNullOrEmpty(socket.Id) && !nonProtectedOps.Contains(obj.OpCode)) + if (message.OpCode < 0 || message.OpCode > 8 || message.OpCode == 2 || message.OpCode == 4) { - _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.UID}]"); + await socket.Send(5, new LoggingMessage("Received an invalid message: " + messageString, HermesLoggingLevel.Error)); + return; + } + + bool loggedIn = !string.IsNullOrEmpty(socket.Id); + int[] nonProtectedOps = { 0, 1 }; + 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; } int[] protectedOps = { 0, 3, 5, 6, 7, 8 }; - if (!string.IsNullOrEmpty(socket.Id) && !protectedOps.Contains(obj.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.UID}]"); + _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; } - /** - * 0: Heartbeat - * 1: Login RX - * 2: Login Ack TX - * 3: Request RX - * 4: Request Ack TX - * 5: Logging RX/TX - */ - if (obj.Data == null) + if (message.Data == null) { await socket.Send(5, new LoggingMessage("Received no data in the message.", HermesLoggingLevel.Warn)); continue; } - - string data = obj.Data.ToString()!; - if (obj.OpCode == 0) - obj.Data = JsonSerializer.Deserialize(data, _options); - else if (obj.OpCode == 1) - obj.Data = JsonSerializer.Deserialize(data, _options); - else if (obj.OpCode == 3) - obj.Data = JsonSerializer.Deserialize(data, _options); - else if (obj.OpCode == 5) - obj.Data = JsonSerializer.Deserialize(data, _options); - else if (obj.OpCode == 6) - obj.Data = JsonSerializer.Deserialize(data, _options); - else if (obj.OpCode == 7) - obj.Data = JsonSerializer.Deserialize(data, _options); - else if (obj.OpCode == 8) - obj.Data = JsonSerializer.Deserialize(data, _options); + + 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 an invalid message: " + message, HermesLoggingLevel.Error)); + await socket.Send(5, new LoggingMessage("Received a message with invalid data: " + messageString, HermesLoggingLevel.Error)); continue; } - await _handlers.Execute(socket, obj.OpCode, obj.Data); + await _handlers.Execute(socket, message.OpCode, message.Data); } catch (WebSocketException wse) { - _logger.Error(wse, $"Error trying to process a socket message [code: {wse.ErrorCode}][ip: {socket.IPAddress}][id: {socket.Id}][name: {socket.Name}][token: {socket.ApiKey}][uid: {socket.UID}]"); + _logger.Error(wse, $"Error trying to process a socket message [code: {wse.ErrorCode}][ip: {socket.IPAddress}][id: {socket.Id}][name: {socket.Name}][token: {socket.ApiKey}][uid: {socket.SessionId}]"); } catch (Exception e) { - _logger.Error(e, $"Error trying to process a socket message [ip: {socket.IPAddress}][id: {socket.Id}][name: {socket.Name}][token: {socket.ApiKey}][uid: {socket.UID}]"); + _logger.Error(e, $"Error trying to process a socket message [ip: {socket.IPAddress}][id: {socket.Id}][name: {socket.Name}][token: {socket.ApiKey}][uid: {socket.SessionId}]"); } } @@ -118,14 +120,26 @@ 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.UID}]"); + _logger.Information(e, $"Client failed to disconnect [ip: {socket.IPAddress}][id: {socket.Id}][name: {socket.Name}][token: {socket.ApiKey}][uid: {socket.SessionId}]"); } finally { socket.Dispose(); _sockets.Remove(socket); } - _logger.Information($"Client disconnected [ip: {socket.IPAddress}][id: {socket.Id}][name: {socket.Name}][token: {socket.ApiKey}][uid: {socket.UID}]"); + _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(); + if (client != null) + { + await client.Send(9, new SlaveMessage() { Slave = false }); + client.Slave = false; + } + } } } } \ No newline at end of file diff --git a/Services/ChannelManager.cs b/Services/ChannelManager.cs index 990aade..94eafc2 100644 --- a/Services/ChannelManager.cs +++ b/Services/ChannelManager.cs @@ -38,8 +38,8 @@ namespace HermesSocketServer.Services { lock (_lock) { - if (_channels.ContainsKey(userId)) - return Task.FromResult(null); + if (_channels.TryGetValue(userId, out var channel)) + return Task.FromResult(channel); var actionTable = _configuration.Database.Tables["Action"]; var chatterTable = _configuration.Database.Tables["Chatter"]; @@ -62,7 +62,7 @@ namespace HermesSocketServer.Services var redemptions = new RedemptionStore(userId, redemptionTable, actions, _database, _logger); var voiceStates = new VoiceStateStore(userId, ttsVoiceStateTable, _voices, _database, _logger); - var channel = new Channel() + channel = new Channel() { Id = userId, User = user, diff --git a/Socket/Handlers/HermesLoginHandler.cs b/Socket/Handlers/HermesLoginHandler.cs index 4144335..39a3505 100644 --- a/Socket/Handlers/HermesLoginHandler.cs +++ b/Socket/Handlers/HermesLoginHandler.cs @@ -45,16 +45,24 @@ namespace HermesSocketServer.Socket.Handlers if (userId == null) return; + IEnumerable recipients = Enumerable.Empty(); lock (_lock) { if (sender.Id != null) - return; + throw new Exception("User already logged in."); sender.Id = userId; - sender.ApiKey = data.ApiKey; - sender.WebLogin = data.WebLogin; + + 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.ApiKey = data.ApiKey; + sender.WebLogin = data.WebLogin; + var channel = _manager.Get(userId); if (channel == null) { @@ -73,11 +81,12 @@ namespace HermesSocketServer.Socket.Handlers } 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" } }); var providerId = result2?.ToString(); - if (providerId == null) { + if (providerId == null) + { _logger.Warning($"Could not find the Provider Account Id [user id: {userId}][provider: twitch]"); return; } @@ -86,7 +95,7 @@ namespace HermesSocketServer.Socket.Handlers { UserId = userId, ProviderAccountId = providerId, - SessionId = sender.UID, + SessionId = sender.SessionId, UserName = channel.User.Name, OwnerId = _configuration.Tts.OwnerId, Admin = sender.Admin, @@ -94,6 +103,7 @@ 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), + Slave = sender.Slave, }; var userIdDict = new Dictionary() { { "user", userId } }; @@ -122,7 +132,7 @@ namespace HermesSocketServer.Socket.Handlers await sender.Send(2, ack); - string version = data.MajorVersion == null ? "unknown" : $"{data.MajorVersion}.{data.MinorVersion}"; + 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}]"); ack = new LoginAckMessage() @@ -133,7 +143,6 @@ namespace HermesSocketServer.Socket.Handlers WebLogin = data.WebLogin }; - var recipients = _sockets.GetSockets(userId).ToList().Where(s => s.UID != sender.UID); var tasks = new List(); foreach (var socket in recipients) { diff --git a/Socket/Handlers/LoggingHandler.cs b/Socket/Handlers/LoggingHandler.cs index be4769c..21e975b 100644 --- a/Socket/Handlers/LoggingHandler.cs +++ b/Socket/Handlers/LoggingHandler.cs @@ -38,7 +38,7 @@ namespace HermesSocketServer.Socket.Handlers return Task.CompletedTask; } - logging.Invoke(message.Exception, message.Message + $" [ip: {sender.IPAddress}][id: {sender.Id}][name: {sender.Name}][token: {sender.ApiKey}][uid: {sender.UID}]"); + logging.Invoke(message.Exception, message.Message + $" [ip: {sender.IPAddress}][id: {sender.Id}][name: {sender.Name}][token: {sender.ApiKey}][uid: {sender.SessionId}]"); return Task.CompletedTask; } } diff --git a/Socket/SocketManager.cs b/Socket/SocketManager.cs index 2441927..f82f0a9 100644 --- a/Socket/SocketManager.cs +++ b/Socket/SocketManager.cs @@ -6,9 +6,10 @@ namespace HermesSocketServer.Socket { public class HermesSocketManager { - private IList _sockets; - private System.Timers.Timer _timer; - private ILogger _logger; + private readonly IList _sockets; + private readonly System.Timers.Timer _timer; + private readonly ILogger _logger; + private readonly object _lock = new object(); public HermesSocketManager(ILogger logger) @@ -18,31 +19,40 @@ namespace HermesSocketServer.Socket _timer.Elapsed += async (sender, e) => await HandleHeartbeats(e); _timer.Enabled = true; _logger = logger; + _lock = new object(); } public void Add(WebSocketUser socket) { - _sockets.Add(socket); + lock (_lock) + { + _sockets.Add(socket); + } } public IList GetAllSockets() { - return _sockets.AsReadOnly(); + lock (_lock) + { + return _sockets.AsReadOnly(); + } } public IEnumerable GetSockets(string userId) { - foreach (var socket in _sockets) + lock (_lock) { - if (socket.Id == userId) - yield return socket; + return _sockets.Where(s => s.Id == userId); } } public bool Remove(WebSocketUser socket) { - return _sockets.Remove(socket); + lock (_lock) + { + return _sockets.Remove(socket); + } } private async Task HandleHeartbeats(ElapsedEventArgs e) diff --git a/Socket/WebSocketUser.cs b/Socket/WebSocketUser.cs index a5fc6fa..0ee0dd2 100644 --- a/Socket/WebSocketUser.cs +++ b/Socket/WebSocketUser.cs @@ -22,7 +22,8 @@ namespace HermesSocketServer.Socket public WebSocketState State { get => _socket.State; } public IPAddress? IPAddress { get => _ipAddress; } public bool Connected { get => _connected; } - public string UID { get; } + public bool Slave { get; set; } + public string SessionId { get; } public string? ApiKey { get; set; } public string? Id { get; set; } public string? Name { get; set; } @@ -43,7 +44,7 @@ namespace HermesSocketServer.Socket Admin = false; WebLogin = false; _cts = new CancellationTokenSource(); - UID = Guid.NewGuid().ToString("D"); + SessionId = Guid.NewGuid().ToString("D"); LastHeartbeatReceived = DateTime.UtcNow; } diff --git a/Store/ActionStore.cs b/Store/ActionStore.cs index 101409f..dda346a 100644 --- a/Store/ActionStore.cs +++ b/Store/ActionStore.cs @@ -47,6 +47,9 @@ namespace HermesSocketServer.Store ArgumentException.ThrowIfNullOrWhiteSpace(value.Name, nameof(value.Name)); ArgumentException.ThrowIfNullOrWhiteSpace(value.Type, nameof(value.Type)); ArgumentNullException.ThrowIfNull(value.Data, nameof(value.Data)); + + if (value.Name.Length > 36) + throw new ArgumentException("Action name cannot be longer than 36 characters."); } protected override void OnInitialModify(string key, RedeemableAction oldValue, RedeemableAction newValue) diff --git a/Store/ConnectionStore.cs b/Store/ConnectionStore.cs index 6d57ae4..7de27d7 100644 --- a/Store/ConnectionStore.cs +++ b/Store/ConnectionStore.cs @@ -54,6 +54,9 @@ namespace HermesSocketServer.Store ArgumentException.ThrowIfNullOrWhiteSpace(value.Scope, nameof(value.Scope)); ArgumentNullException.ThrowIfNull(value.ExpiresAt, nameof(value.ExpiresAt)); ArgumentNullException.ThrowIfNull(value.Default, nameof(value.Default)); + + if (value.Name.Length > 36) + throw new ArgumentException("Action name cannot be longer than 36 characters."); } protected override void OnInitialModify(string key, Connection oldValue, Connection newValue) @@ -70,6 +73,8 @@ namespace HermesSocketServer.Store ArgumentNullException.ThrowIfNull(newValue.Default, nameof(newValue.Default)); ArgumentOutOfRangeException.ThrowIfNotEqual(oldValue.UserId, newValue.UserId, nameof(oldValue.UserId)); ArgumentOutOfRangeException.ThrowIfNotEqual(oldValue.Name, newValue.Name, nameof(oldValue.Name)); + ArgumentOutOfRangeException.ThrowIfNotEqual(oldValue.Type, newValue.Type, nameof(oldValue.Type)); + ArgumentOutOfRangeException.ThrowIfNotEqual(oldValue.ClientId, newValue.ClientId, nameof(oldValue.ClientId)); } } } \ No newline at end of file diff --git a/Store/GroupPermissionStore.cs b/Store/GroupPermissionStore.cs index 560d434..c163b25 100644 --- a/Store/GroupPermissionStore.cs +++ b/Store/GroupPermissionStore.cs @@ -48,6 +48,8 @@ namespace HermesSocketServer.Store ArgumentNullException.ThrowIfNull(value.GroupId, nameof(value.GroupId)); ArgumentException.ThrowIfNullOrWhiteSpace(value.Path, nameof(value.Path)); + if (value.Path.Length > 100) + throw new ArgumentException("The path cannot be longer than 100 characters."); if (_groups.Get(value.GroupId.ToString()) == null) throw new ArgumentException("The group id does not exist."); } @@ -60,6 +62,9 @@ namespace HermesSocketServer.Store ArgumentException.ThrowIfNullOrWhiteSpace(newValue.Path, nameof(newValue.Path)); ArgumentOutOfRangeException.ThrowIfNotEqual(oldValue.UserId, newValue.UserId, nameof(oldValue.UserId)); ArgumentOutOfRangeException.ThrowIfNotEqual(oldValue.GroupId, newValue.GroupId, nameof(oldValue.GroupId)); + + if (oldValue.Path != newValue.Path && newValue.Path.Length > 100) + throw new ArgumentException("The path cannot be longer than 100 characters."); } protected override void OnPostRemove(string key, GroupPermission value) diff --git a/Store/VoiceStateStore.cs b/Store/VoiceStateStore.cs index d2de867..7fcc2dd 100644 --- a/Store/VoiceStateStore.cs +++ b/Store/VoiceStateStore.cs @@ -50,7 +50,9 @@ namespace HermesSocketServer.Store ArgumentException.ThrowIfNullOrWhiteSpace(value.Id, nameof(value.Id)); ArgumentException.ThrowIfNullOrWhiteSpace(value.UserId, nameof(value.UserId)); ArgumentNullException.ThrowIfNull(value.Enabled, nameof(value.Enabled)); - ArgumentNullException.ThrowIfNull(_voices.Get(value.Id)); + + if (_voices.Get(value.Id) == null) + throw new ArgumentException("The voice does not exist."); } protected override void OnInitialModify(string key, TTSVoiceState oldValue, TTSVoiceState newValue)