From 9fb966474f0f5fd2a0f0b7b133f4ff011a01b6e5 Mon Sep 17 00:00:00 2001 From: Tom Date: Fri, 12 Jul 2024 17:36:09 +0000 Subject: [PATCH] Added groups & permissions. Fixed 7tv reconnection. Added more subcommands for refresh. --- Chat/ChatMessageHandler.cs | 54 ++++--- Chat/Commands/AddTTSVoiceCommand.cs | 5 +- Chat/Commands/ChatCommand.cs | 5 +- Chat/Commands/ChatCommandManager.cs | 49 ++++-- Chat/Commands/OBSCommand.cs | 2 +- Chat/Commands/RefreshTTSDataCommand.cs | 87 ++++++++++- Chat/Commands/RemoveTTSVoiceCommand.cs | 4 +- Chat/Commands/SkipAllCommand.cs | 2 +- Chat/Commands/SkipCommand.cs | 2 +- Chat/Commands/TTSCommand.cs | 2 +- Chat/Commands/VersionCommand.cs | 2 +- Chat/Commands/VoiceCommand.cs | 2 +- Chat/Groups/ChatterGroupManager.cs | 75 +++++++++ Chat/Groups/Group.cs | 9 ++ Chat/Groups/GroupChatter.cs | 8 + Chat/Groups/IChatterGroupManager.cs | 15 ++ Chat/Groups/Permissions/GroupPermission.cs | 10 ++ .../Permissions/GroupPermissionManager.cs | 145 ++++++++++++++++++ .../Permissions/IGroupPermissionManager.cs | 11 ++ Chat/Speech/TTSPlayer.cs | 8 +- .../{HermesClient.cs => HermesApiClient.cs} | 68 ++++++-- Hermes/Socket/Handlers/HeartbeatHandler.cs | 4 +- Hermes/Socket/Handlers/LoginAckHandler.cs | 7 +- Hermes/Socket/HermesSocketClient.cs | 4 +- OBS/Socket/Data/OBSSceneItem.cs | 10 ++ OBS/Socket/Data/RequestBatchMessage.cs | 7 +- OBS/Socket/Data/RequestResponseMessage.cs | 2 + OBS/Socket/Handlers/IdentifiedHandler.cs | 14 +- .../Handlers/RequestBatchResponseHandler.cs | 2 +- OBS/Socket/Handlers/RequestResponseHandler.cs | 75 +++++++-- OBS/Socket/Manager/OBSManager.cs | 134 ++++++++++------ Seven/ChatterDatabase.cs | 43 ++++++ Seven/{Emotes.cs => EmoteDatabase.cs} | 3 +- Seven/Socket/Handlers/EndOfStreamHandler.cs | 9 +- Startup.cs | 6 +- TTS.cs | 78 ++++++++-- Twitch/TwitchApiClient.cs | 2 +- 37 files changed, 806 insertions(+), 159 deletions(-) create mode 100644 Chat/Groups/ChatterGroupManager.cs create mode 100644 Chat/Groups/Group.cs create mode 100644 Chat/Groups/GroupChatter.cs create mode 100644 Chat/Groups/IChatterGroupManager.cs create mode 100644 Chat/Groups/Permissions/GroupPermission.cs create mode 100644 Chat/Groups/Permissions/GroupPermissionManager.cs create mode 100644 Chat/Groups/Permissions/IGroupPermissionManager.cs rename Hermes/{HermesClient.cs => HermesApiClient.cs} (57%) create mode 100644 OBS/Socket/Data/OBSSceneItem.cs create mode 100644 Seven/ChatterDatabase.cs rename Seven/{Emotes.cs => EmoteDatabase.cs} (93%) diff --git a/Chat/ChatMessageHandler.cs b/Chat/ChatMessageHandler.cs index 14e1d08..8f99383 100644 --- a/Chat/ChatMessageHandler.cs +++ b/Chat/ChatMessageHandler.cs @@ -10,17 +10,22 @@ using TwitchChatTTS.Seven; using TwitchChatTTS.Chat.Commands; using TwitchChatTTS.Hermes.Socket; using HermesSocketLibrary.Socket.Data; +using TwitchChatTTS.Chat.Groups.Permissions; +using TwitchChatTTS.Chat.Groups; public class ChatMessageHandler { private readonly User _user; - private readonly Configuration _configuration; - private readonly EmoteDatabase _emotes; private readonly TTSPlayer _player; private readonly ChatCommandManager _commands; + private readonly IGroupPermissionManager _permissionManager; + private readonly IChatterGroupManager _chatterGroupManager; + private readonly EmoteDatabase _emotes; private readonly OBSSocketClient? _obsClient; private readonly HermesSocketClient? _hermesClient; + private readonly Configuration _configuration; + private readonly ILogger _logger; private Regex sfxRegex; @@ -33,6 +38,8 @@ public class ChatMessageHandler User user, TTSPlayer player, ChatCommandManager commands, + IGroupPermissionManager permissionManager, + IChatterGroupManager chatterGroupManager, EmoteDatabase emotes, [FromKeyedServices("obs")] SocketClient obsClient, [FromKeyedServices("hermes")] SocketClient hermesClient, @@ -43,13 +50,15 @@ public class ChatMessageHandler _user = user; _player = player; _commands = commands; + _permissionManager = permissionManager; + _chatterGroupManager = chatterGroupManager; _emotes = emotes; _obsClient = obsClient as OBSSocketClient; _hermesClient = hermesClient as HermesSocketClient; _configuration = configuration; _logger = logger; - _chatters = null; + _chatters = new HashSet(); sfxRegex = new Regex(@"\(([A-Za-z0-9_-]+)\)"); } @@ -66,18 +75,28 @@ public class ChatMessageHandler var chatterId = long.Parse(m.UserId); var tasks = new List(); - var blocked = _user.ChatterFilters.TryGetValue(m.Username, out TTSUsernameFilter? filter) && filter.Tag == "blacklisted"; + var permissionPath = "tts.chat.messages.read"; + if (!string.IsNullOrWhiteSpace(m.CustomRewardId)) + permissionPath = "tts.chat.redemptions.read"; + + var checks = new bool[] { true, m.IsSubscriber, m.IsVip, m.IsModerator, m.IsBroadcaster }; + var defaultGroups = new string[] { "everyone", "subscribers", "vip", "moderators", "broadcaster" }; + var customGroups = _chatterGroupManager.GetGroupNamesFor(chatterId); + var groups = defaultGroups.Where((e, i) => checks[i]).Union(customGroups); + + var permission = chatterId == _user.OwnerId ? true : _permissionManager.CheckIfAllowed(groups, permissionPath); + var blocked = permission != true; if (!blocked || m.IsBroadcaster) { try { - var commandResult = await _commands.Execute(msg, m); + var commandResult = await _commands.Execute(msg, m, groups); if (commandResult != ChatCommandResult.Unknown) return new MessageResult(MessageStatus.Command, -1, -1); } catch (Exception ex) { - _logger.Error(ex, $"Failed executing a chat command [message: {msg}][chatter: {m.Username}][cid: {m.UserId}][mid: {m.Id}]"); + _logger.Error(ex, $"Failed executing a chat command [message: {msg}][chatter: {m.Username}][chatter id: {m.UserId}][message id: {m.Id}]"); } } @@ -100,7 +119,7 @@ public class ChatMessageHandler // Filter highly repetitive words (like emotes) from the message. int totalEmoteUsed = 0; var emotesUsed = new HashSet(); - var words = msg.Split(" "); + var words = msg.Split(' '); var wordCounter = new Dictionary(); string filteredMsg = string.Empty; var newEmotes = new Dictionary(); @@ -164,26 +183,13 @@ public class ChatMessageHandler } // Determine the priority of this message - int priority = 0; - if (m.IsStaff) - priority = int.MinValue; - else if (filter?.Tag == "priority") - priority = int.MinValue + 1; - else if (m.IsModerator) - priority = -100; - else if (m.IsVip) - priority = -10; - else if (m.IsPartner) - priority = -5; - else if (m.IsHighlighted) - priority = -1; - priority = Math.Min(priority, -m.SubscribedMonthCount * (m.IsSubscriber ? 2 : 1)); + int priority = _chatterGroupManager.GetPriorityFor(groups) + m.SubscribedMonthCount * (m.IsSubscriber ? 10 : 5); // Determine voice selected. string voiceSelected = _user.DefaultTTSVoice; - if (long.TryParse(e.ChatMessage.UserId, out long userId) && _user.VoicesSelected?.ContainsKey(userId) == true) + if (_user.VoicesSelected?.ContainsKey(chatterId) == true) { - var voiceId = _user.VoicesSelected[userId]; + var voiceId = _user.VoicesSelected[chatterId]; if (_user.VoicesAvailable.TryGetValue(voiceId, out string? voiceName) && voiceName != null) { if (_user.VoicesEnabled.Contains(voiceName) || chatterId == _user.OwnerId || m.IsStaff) @@ -230,7 +236,7 @@ public class ChatMessageHandler if (parts.Length == 1) { - _logger.Information($"Username: {m.Username}; User ID: {m.UserId}; Voice: {voice}; Priority: {priority}; Message: {message}; Month: {m.SubscribedMonthCount}; {badgesString}"); + _logger.Information($"Username: {m.Username}; User ID: {m.UserId}; Voice: {voice}; Priority: {priority}; Message: {message}; Month: {m.SubscribedMonthCount}; Reward Id: {m.CustomRewardId}; {badgesString}"); _player.Add(new TTSMessage() { Voice = voice, diff --git a/Chat/Commands/AddTTSVoiceCommand.cs b/Chat/Commands/AddTTSVoiceCommand.cs index f1640bb..e730bb1 100644 --- a/Chat/Commands/AddTTSVoiceCommand.cs +++ b/Chat/Commands/AddTTSVoiceCommand.cs @@ -14,6 +14,8 @@ namespace TwitchChatTTS.Chat.Commands private readonly SocketClient _hermesClient; private readonly ILogger _logger; + public new bool DefaultPermissionsOverwrite { get => true; } + public AddTTSVoiceCommand( User user, [FromKeyedServices("parameter-unvalidated")] ChatCommandParameter ttsVoiceParameter, @@ -28,14 +30,13 @@ namespace TwitchChatTTS.Chat.Commands AddParameter(ttsVoiceParameter); } - public override async Task CheckPermissions(ChatMessage message, long broadcasterId) + public override async Task CheckDefaultPermissions(ChatMessage message, long broadcasterId) { return false; } public override async Task Execute(IList args, ChatMessage message, long broadcasterId) { - //var HermesClient = _serviceProvider.GetRequiredKeyedService>("hermes"); if (_hermesClient == null) return; if (_user == null || _user.VoicesAvailable == null) diff --git a/Chat/Commands/ChatCommand.cs b/Chat/Commands/ChatCommand.cs index d832d56..9427a29 100644 --- a/Chat/Commands/ChatCommand.cs +++ b/Chat/Commands/ChatCommand.cs @@ -8,12 +8,15 @@ namespace TwitchChatTTS.Chat.Commands public string Name { get; } public string Description { get; } public IList Parameters { get => _parameters.AsReadOnly(); } + public bool DefaultPermissionsOverwrite { get; } + private IList _parameters; public ChatCommand(string name, string description) { Name = name; Description = description; + DefaultPermissionsOverwrite = false; _parameters = new List(); } @@ -24,7 +27,7 @@ namespace TwitchChatTTS.Chat.Commands } } - public abstract Task CheckPermissions(ChatMessage message, long broadcasterId); + public abstract Task CheckDefaultPermissions(ChatMessage message, long broadcasterId); public abstract Task Execute(IList args, ChatMessage message, long broadcasterId); } } \ No newline at end of file diff --git a/Chat/Commands/ChatCommandManager.cs b/Chat/Commands/ChatCommandManager.cs index 2a021b7..777ab80 100644 --- a/Chat/Commands/ChatCommandManager.cs +++ b/Chat/Commands/ChatCommandManager.cs @@ -1,6 +1,8 @@ using System.Text.RegularExpressions; using Microsoft.Extensions.DependencyInjection; using Serilog; +using TwitchChatTTS.Chat.Groups; +using TwitchChatTTS.Chat.Groups.Permissions; using TwitchLib.Client.Models; namespace TwitchChatTTS.Chat.Commands @@ -10,15 +12,26 @@ namespace TwitchChatTTS.Chat.Commands private IDictionary _commands; private readonly TwitchBotAuth _token; private readonly User _user; + private readonly IGroupPermissionManager _permissionManager; + private readonly IChatterGroupManager _chatterGroupManager; private readonly IServiceProvider _serviceProvider; private readonly ILogger _logger; private string CommandStartSign { get; } = "!"; - public ChatCommandManager(TwitchBotAuth token, User user, IServiceProvider serviceProvider, ILogger logger) + public ChatCommandManager( + TwitchBotAuth token, + User user, + IGroupPermissionManager permissionManager, + IChatterGroupManager chatterGroupManager, + IServiceProvider serviceProvider, + ILogger logger + ) { _token = token; _user = user; + _permissionManager = permissionManager; + _chatterGroupManager = chatterGroupManager; _serviceProvider = serviceProvider; _logger = logger; @@ -56,7 +69,7 @@ namespace TwitchChatTTS.Chat.Commands } } - public async Task Execute(string arg, ChatMessage message) + public async Task Execute(string arg, ChatMessage message, IEnumerable groups) { if (_token.BroadcasterId == null) return ChatCommandResult.Unknown; @@ -80,19 +93,31 @@ namespace TwitchChatTTS.Chat.Commands if (!_commands.TryGetValue(com, out ChatCommand? command) || command == null) { // Could be for another bot or just misspelled. - _logger.Debug($"Failed to find command named '{com}' [args: {arg}][chatter: {message.Username}][cid: {message.UserId}]"); + _logger.Debug($"Failed to find command named '{com}' [args: {arg}][chatter: {message.Username}][chatter id: {message.UserId}]"); return ChatCommandResult.Missing; } - if (!await command.CheckPermissions(message, broadcasterId) && message.UserId != _user.OwnerId?.ToString() && !message.IsStaff) + // Check if command can be executed by this chatter. + long chatterId = long.Parse(message.UserId); + if (chatterId != _user.OwnerId) { - _logger.Warning($"Chatter is missing permission to execute command named '{com}' [args: {arg}][chatter: {message.Username}][cid: {message.UserId}]"); - return ChatCommandResult.Permission; + var executable = command.DefaultPermissionsOverwrite ? false : CanExecute(chatterId, groups, com); + if (executable == false) + { + _logger.Debug($"Denied permission to use command [chatter id: {chatterId}][command: {com}]"); + return ChatCommandResult.Permission; + } + else if (executable == null && !await command.CheckDefaultPermissions(message, broadcasterId)) + { + _logger.Debug($"Chatter is missing default permission to execute command named '{com}' [args: {arg}][chatter: {message.Username}][chatter id: {message.UserId}]"); + return ChatCommandResult.Permission; + } } + // Check if the syntax is correct. if (command.Parameters.Count(p => !p.Optional) > args.Length) { - _logger.Warning($"Command syntax issue when executing command named '{com}' [args: {arg}][chatter: {message.Username}][cid: {message.UserId}]"); + _logger.Debug($"Command syntax issue when executing command named '{com}' [args: {arg}][chatter: {message.Username}][chatter id: {message.UserId}]"); return ChatCommandResult.Syntax; } @@ -111,12 +136,18 @@ namespace TwitchChatTTS.Chat.Commands } catch (Exception e) { - _logger.Error(e, $"Command '{arg}' failed."); + _logger.Error(e, $"Command '{arg}' failed [args: {arg}][chatter: {message.Username}][chatter id: {message.UserId}]"); return ChatCommandResult.Fail; } - _logger.Information($"Executed the {com} command [arguments: {arg}]"); + _logger.Information($"Executed the {com} command [args: {arg}][chatter: {message.Username}][chatter id: {message.UserId}]"); return ChatCommandResult.Success; } + + private bool? CanExecute(long chatterId, IEnumerable groups, string path) + { + _logger.Debug($"Checking for permission [chatter id: {chatterId}][group: {string.Join(", ", groups)}][path: {path}]"); + return _permissionManager.CheckIfAllowed(groups, path); + } } } \ No newline at end of file diff --git a/Chat/Commands/OBSCommand.cs b/Chat/Commands/OBSCommand.cs index bb1d92d..4081af2 100644 --- a/Chat/Commands/OBSCommand.cs +++ b/Chat/Commands/OBSCommand.cs @@ -30,7 +30,7 @@ namespace TwitchChatTTS.Chat.Commands AddParameter(unvalidatedParameter); } - public override async Task CheckPermissions(ChatMessage message, long broadcasterId) + public override async Task CheckDefaultPermissions(ChatMessage message, long broadcasterId) { return message.IsModerator || message.IsBroadcaster; } diff --git a/Chat/Commands/RefreshTTSDataCommand.cs b/Chat/Commands/RefreshTTSDataCommand.cs index 0a52ea7..89b562e 100644 --- a/Chat/Commands/RefreshTTSDataCommand.cs +++ b/Chat/Commands/RefreshTTSDataCommand.cs @@ -1,4 +1,7 @@ using Serilog; +using TwitchChatTTS.Chat.Groups; +using TwitchChatTTS.Chat.Groups.Permissions; +using TwitchChatTTS.OBS.Socket.Manager; using TwitchChatTTS.Twitch.Redemptions; using TwitchLib.Client.Models; @@ -8,30 +11,43 @@ namespace TwitchChatTTS.Chat.Commands { private readonly User _user; private readonly RedemptionManager _redemptionManager; + private readonly IGroupPermissionManager _permissionManager; + private readonly IChatterGroupManager _chatterGroupManager; + private readonly OBSManager _obsManager; private readonly HermesApiClient _hermesApi; private readonly ILogger _logger; - public RefreshTTSDataCommand(User user, RedemptionManager redemptionManager, HermesApiClient hermesApi, ILogger logger) - : base("refresh", "Refreshes certain TTS related data on the client.") + public RefreshTTSDataCommand( + User user, + RedemptionManager redemptionManager, + IGroupPermissionManager permissionManager, + IChatterGroupManager chatterGroupManager, + OBSManager obsManager, + HermesApiClient hermesApi, + ILogger logger + ) : base("refresh", "Refreshes certain TTS related data on the client.") { _user = user; _redemptionManager = redemptionManager; + _permissionManager = permissionManager; + _chatterGroupManager = chatterGroupManager; + _obsManager = obsManager; _hermesApi = hermesApi; _logger = logger; } - public override async Task CheckPermissions(ChatMessage message, long broadcasterId) + public override async Task CheckDefaultPermissions(ChatMessage message, long broadcasterId) { return message.IsModerator || message.IsBroadcaster; } public override async Task Execute(IList args, ChatMessage message, long broadcasterId) { - var service = args.FirstOrDefault(); - if (service == null) + var value = args.FirstOrDefault(); + if (value == null) return; - switch (service) + switch (value.ToLower()) { case "tts_voice_enabled": var voicesEnabled = await _hermesApi.FetchTTSEnabledVoices(); @@ -52,6 +68,13 @@ namespace TwitchChatTTS.Chat.Commands _logger.Information($"{_user.ChatterFilters.Where(f => f.Value.Tag == "blacklisted").Count()} username(s) have been blocked."); _logger.Information($"{_user.ChatterFilters.Where(f => f.Value.Tag == "priority").Count()} user(s) have been prioritized."); break; + case "selected_voices": + { + var voicesSelected = await _hermesApi.FetchTTSChatterSelectedVoices(); + _user.VoicesSelected = voicesSelected.ToDictionary(s => s.ChatterId, s => s.Voice); + _logger.Information($"{_user.VoicesSelected.Count} TTS voices have been selected for specific chatters."); + break; + } case "default_voice": _user.DefaultTTSVoice = await _hermesApi.FetchTTSDefaultVoice(); _logger.Information("TTS Default Voice: " + _user.DefaultTTSVoice); @@ -62,6 +85,58 @@ namespace TwitchChatTTS.Chat.Commands _redemptionManager.Initialize(redemptions, redemptionActions.ToDictionary(a => a.Name, a => a)); _logger.Information($"Redemption Manager has been refreshed with {redemptionActions.Count()} actions & {redemptions.Count()} redemptions."); break; + case "obs_cache": + { + try + { + _obsManager.ClearCache(); + await _obsManager.GetGroupList(async groups => await _obsManager.GetGroupSceneItemList(groups)); + } + catch (Exception e) + { + _logger.Error(e, "Failed to load OBS group info via command."); + } + break; + } + case "permissions": + { + _chatterGroupManager.Clear(); + _permissionManager.Clear(); + + var groups = await _hermesApi.FetchGroups(); + var groupsById = groups.ToDictionary(g => g.Id, g => g); + foreach (var group in groups) + _chatterGroupManager.Add(group); + _logger.Information($"{groups.Count()} groups have been loaded."); + + var groupChatters = await _hermesApi.FetchGroupChatters(); + _logger.Debug($"{groupChatters.Count()} group users have been fetched."); + + var permissions = await _hermesApi.FetchGroupPermissions(); + foreach (var permission in permissions) + { + _logger.Debug($"Adding group permission [id: {permission.Id}][group id: {permission.GroupId}][path: {permission.Path}][allow: {permission.Allow?.ToString() ?? "null"}]"); + if (groupsById.TryGetValue(permission.GroupId, out var group)) + { + _logger.Warning($"Failed to find group by id [id: {permission.Id}][group id: {permission.GroupId}][path: {permission.Path}]"); + continue; + } + + var path = $"{group.Name}.{permission.Path}"; + _permissionManager.Set(path, permission.Allow); + _logger.Debug($"Added group permission [id: {permission.Id}][group id: {permission.GroupId}][path: {permission.Path}]"); + } + _logger.Information($"{permissions.Count()} group permissions have been loaded."); + + foreach (var chatter in groupChatters) + if (groupsById.TryGetValue(chatter.GroupId, out var group)) + _chatterGroupManager.Add(chatter.ChatterId, group.Name); + _logger.Information($"Users in each group have been loaded."); + break; + } + default: + _logger.Warning($"Unknown refresh value given [value: {value}]"); + break; } } } diff --git a/Chat/Commands/RemoveTTSVoiceCommand.cs b/Chat/Commands/RemoveTTSVoiceCommand.cs index dd7aca8..75d62fb 100644 --- a/Chat/Commands/RemoveTTSVoiceCommand.cs +++ b/Chat/Commands/RemoveTTSVoiceCommand.cs @@ -14,6 +14,8 @@ namespace TwitchChatTTS.Chat.Commands private readonly SocketClient _hermesClient; private ILogger _logger; + public new bool DefaultPermissionsOverwrite { get => true; } + public RemoveTTSVoiceCommand( [FromKeyedServices("parameter-unvalidated")] ChatCommandParameter ttsVoiceParameter, User user, @@ -28,7 +30,7 @@ namespace TwitchChatTTS.Chat.Commands AddParameter(ttsVoiceParameter); } - public override async Task CheckPermissions(ChatMessage message, long broadcasterId) + public override async Task CheckDefaultPermissions(ChatMessage message, long broadcasterId) { return false; } diff --git a/Chat/Commands/SkipAllCommand.cs b/Chat/Commands/SkipAllCommand.cs index bad77d3..dd2270e 100644 --- a/Chat/Commands/SkipAllCommand.cs +++ b/Chat/Commands/SkipAllCommand.cs @@ -15,7 +15,7 @@ namespace TwitchChatTTS.Chat.Commands _logger = logger; } - public override async Task CheckPermissions(ChatMessage message, long broadcasterId) + public override async Task CheckDefaultPermissions(ChatMessage message, long broadcasterId) { return message.IsModerator || message.IsVip || message.IsBroadcaster; } diff --git a/Chat/Commands/SkipCommand.cs b/Chat/Commands/SkipCommand.cs index 5206ef8..39d02fc 100644 --- a/Chat/Commands/SkipCommand.cs +++ b/Chat/Commands/SkipCommand.cs @@ -15,7 +15,7 @@ namespace TwitchChatTTS.Chat.Commands _logger = logger; } - public override async Task CheckPermissions(ChatMessage message, long broadcasterId) + public override async Task CheckDefaultPermissions(ChatMessage message, long broadcasterId) { return message.IsModerator || message.IsVip || message.IsBroadcaster; } diff --git a/Chat/Commands/TTSCommand.cs b/Chat/Commands/TTSCommand.cs index e24e3ec..d2ed749 100644 --- a/Chat/Commands/TTSCommand.cs +++ b/Chat/Commands/TTSCommand.cs @@ -30,7 +30,7 @@ namespace TwitchChatTTS.Chat.Commands AddParameter(unvalidatedParameter); } - public override async Task CheckPermissions(ChatMessage message, long broadcasterId) + public override async Task CheckDefaultPermissions(ChatMessage message, long broadcasterId) { return message.IsModerator || message.IsBroadcaster; } diff --git a/Chat/Commands/VersionCommand.cs b/Chat/Commands/VersionCommand.cs index cd8da55..74b195d 100644 --- a/Chat/Commands/VersionCommand.cs +++ b/Chat/Commands/VersionCommand.cs @@ -13,7 +13,7 @@ namespace TwitchChatTTS.Chat.Commands _logger = logger; } - public override async Task CheckPermissions(ChatMessage message, long broadcasterId) + public override async Task CheckDefaultPermissions(ChatMessage message, long broadcasterId) { return message.IsBroadcaster; } diff --git a/Chat/Commands/VoiceCommand.cs b/Chat/Commands/VoiceCommand.cs index 4e2b25d..e085878 100644 --- a/Chat/Commands/VoiceCommand.cs +++ b/Chat/Commands/VoiceCommand.cs @@ -28,7 +28,7 @@ namespace TwitchChatTTS.Chat.Commands AddParameter(ttsVoiceParameter); } - public override async Task CheckPermissions(ChatMessage message, long broadcasterId) + public override async Task CheckDefaultPermissions(ChatMessage message, long broadcasterId) { return message.IsModerator || message.IsBroadcaster || message.IsSubscriber || message.Bits >= 100; } diff --git a/Chat/Groups/ChatterGroupManager.cs b/Chat/Groups/ChatterGroupManager.cs new file mode 100644 index 0000000..240ecc8 --- /dev/null +++ b/Chat/Groups/ChatterGroupManager.cs @@ -0,0 +1,75 @@ + +using System.Collections.Concurrent; +using Serilog; + +namespace TwitchChatTTS.Chat.Groups +{ + public class ChatterGroupManager : IChatterGroupManager + { + private readonly IDictionary _groups; + private readonly IDictionary> _chatters; + private readonly ILogger _logger; + + + public ChatterGroupManager(ILogger logger) { + _logger = logger; + _groups = new ConcurrentDictionary(); + _chatters = new ConcurrentDictionary>(); + } + + public void Add(Group group) { + _groups.Add(group.Name, group); + } + + public void Add(long chatter, string groupName) { + _chatters.Add(chatter, new List() { groupName }); + } + + public void Add(long chatter, ICollection groupNames) { + if (_chatters.TryGetValue(chatter, out var list)) { + foreach (var group in groupNames) + list.Add(group); + } else + _chatters.Add(chatter, groupNames); + } + + public void Clear() { + _groups.Clear(); + _chatters.Clear(); + } + + public Group? Get(string groupName) { + if (_groups.TryGetValue(groupName, out var group)) + return group; + return null; + } + + public IEnumerable GetGroupNamesFor(long chatter) { + if (_chatters.TryGetValue(chatter, out var groups)) + return groups.Select(g => _groups[g].Name); + + return Array.Empty(); + } + + public int GetPriorityFor(long chatter) { + if (!_chatters.TryGetValue(chatter, out var groups)) + return 0; + + return GetPriorityFor(groups); + } + + public int GetPriorityFor(IEnumerable groupNames) { + return groupNames.Select(g => _groups.TryGetValue(g, out var group) ? group : null).Where(g => g != null).Max(g => g.Priority); + } + + public bool Remove(long chatterId, string groupId) { + if (_chatters.TryGetValue(chatterId, out var groups)) { + groups.Remove(groupId); + _logger.Debug($"Removed chatter from group [chatter id: {chatterId}][group name: {_groups[groupId]}][group id: {groupId}]"); + return true; + } + _logger.Debug($"Failed to remove chatter from group [chatter id: {chatterId}][group name: {_groups[groupId]}][group id: {groupId}]"); + return false; + } + } +} \ No newline at end of file diff --git a/Chat/Groups/Group.cs b/Chat/Groups/Group.cs new file mode 100644 index 0000000..cc3d356 --- /dev/null +++ b/Chat/Groups/Group.cs @@ -0,0 +1,9 @@ +namespace TwitchChatTTS.Chat.Groups +{ + public class Group + { + public string Id { get; set; } + public string Name { get; set; } + public int Priority { get; set; } + } +} \ No newline at end of file diff --git a/Chat/Groups/GroupChatter.cs b/Chat/Groups/GroupChatter.cs new file mode 100644 index 0000000..446291f --- /dev/null +++ b/Chat/Groups/GroupChatter.cs @@ -0,0 +1,8 @@ +namespace TwitchChatTTS.Chat.Groups +{ + public class GroupChatter + { + public string GroupId { get; set; } + public long ChatterId { get; set;} + } +} \ No newline at end of file diff --git a/Chat/Groups/IChatterGroupManager.cs b/Chat/Groups/IChatterGroupManager.cs new file mode 100644 index 0000000..860620a --- /dev/null +++ b/Chat/Groups/IChatterGroupManager.cs @@ -0,0 +1,15 @@ +namespace TwitchChatTTS.Chat.Groups +{ + public interface IChatterGroupManager + { + void Add(Group group); + void Add(long chatter, string group); + void Add(long chatter, ICollection groupIds); + void Clear(); + Group? Get(string groupId); + IEnumerable GetGroupNamesFor(long chatter); + int GetPriorityFor(long chatter); + int GetPriorityFor(IEnumerable groupIds); + bool Remove(long chatter, string groupId); + } +} \ No newline at end of file diff --git a/Chat/Groups/Permissions/GroupPermission.cs b/Chat/Groups/Permissions/GroupPermission.cs new file mode 100644 index 0000000..0356c78 --- /dev/null +++ b/Chat/Groups/Permissions/GroupPermission.cs @@ -0,0 +1,10 @@ +namespace TwitchChatTTS.Chat.Groups.Permissions +{ + public class GroupPermission + { + public string Id { get; set; } + public string GroupId { get; set; } + public string Path { get; set; } + public bool? Allow { get; set; } + } +} \ No newline at end of file diff --git a/Chat/Groups/Permissions/GroupPermissionManager.cs b/Chat/Groups/Permissions/GroupPermissionManager.cs new file mode 100644 index 0000000..9207128 --- /dev/null +++ b/Chat/Groups/Permissions/GroupPermissionManager.cs @@ -0,0 +1,145 @@ +using System.Collections.ObjectModel; +using Serilog; + +namespace TwitchChatTTS.Chat.Groups.Permissions +{ + public class GroupPermissionManager : IGroupPermissionManager + { + private PermissionNode _root; + private ILogger _logger; + + + public GroupPermissionManager(ILogger logger) + { + _logger = logger; + _root = new PermissionNode(string.Empty, null, null); + } + + + public bool? CheckIfAllowed(string path) + { + var res = Get(path)?.Allow; + _logger.Debug($"Permission Node GET {path} = {res?.ToString() ?? "null"}"); + return res; + } + + public bool? CheckIfAllowed(IEnumerable groups, string path) { + bool overall = false; + foreach (var group in groups) { + var result = CheckIfAllowed($"{group}.{path}"); + if (result == false) + return false; + if (result == true) + overall = true; + } + return overall ? true : null; + } + + public void Clear() + { + if (_root.Children != null) + _root.Children.Clear(); + } + + public bool Remove(string path) + { + var node = Get(path); + if (node == null || node.Parent == null) + return false; + + var parts = path.Split('.'); + var last = parts.Last(); + if (parts.Length > 1 && parts[parts.Length - 1] == node.Parent.Name || parts.Length == 1 && node.Parent.Name == null) + { + node.Parent.Remove(last); + _logger.Debug($"Permission Node REMOVE priv {path}"); + return true; + } + return false; + } + + public void Set(string path, bool? allow) + { + var node = Get(path, true); + node.Allow = allow; + _logger.Debug($"Permission Node ADD {path} = {allow?.ToString() ?? "null"}"); + } + + private PermissionNode Get(string path, bool edit = false) + { + return Get(_root, path.ToLower(), edit); + } + + private PermissionNode Get(PermissionNode node, string path, bool edit) + { + if (path.Length == 0) + return node; + + var parts = path.Split('.'); + var name = parts.First(); + var next = node.Children?.FirstOrDefault(n => n.Name == name); + if (next == null) + { + if (!edit) + return node; + + next = new PermissionNode(name, node, null); + node.Add(next); + } + return Get(next, string.Join('.', parts.Skip(1)), edit); + } + } + + internal class PermissionNode + { + public string Name { get; } + public bool? Allow + { + get + { + var current = this; + while (current._allow == null && current._parent != null) + current = current._parent; + return current._allow; + } + set => _allow = value; + } + public int Priority; + internal PermissionNode? Parent { get => _parent; } + public IList? Children { get => _children == null ? null : new ReadOnlyCollection(_children); } + + private bool? _allow; + private PermissionNode? _parent; + private IList? _children; + + + public PermissionNode(string name, PermissionNode? parent, bool? allow) + { + Name = name; + _parent = parent; + _allow = allow; + } + + internal void Add(PermissionNode child) + { + if (_children == null) + _children = new List(); + _children.Add(child); + } + + public void Remove(string name) + { + if (_children == null || !_children.Any()) + return; + + for (var i = 0; i < _children.Count; i++) + { + if (_children[i].Name == name) + { + _children.RemoveAt(i); + break; + } + } + } + } +} \ No newline at end of file diff --git a/Chat/Groups/Permissions/IGroupPermissionManager.cs b/Chat/Groups/Permissions/IGroupPermissionManager.cs new file mode 100644 index 0000000..ee787dc --- /dev/null +++ b/Chat/Groups/Permissions/IGroupPermissionManager.cs @@ -0,0 +1,11 @@ +namespace TwitchChatTTS.Chat.Groups.Permissions +{ + public interface IGroupPermissionManager + { + void Set(string path, bool? allow); + bool? CheckIfAllowed(string path); + bool? CheckIfAllowed(IEnumerable groups, string path); + void Clear(); + bool Remove(string path); + } +} \ No newline at end of file diff --git a/Chat/Speech/TTSPlayer.cs b/Chat/Speech/TTSPlayer.cs index 124b792..90f75c2 100644 --- a/Chat/Speech/TTSPlayer.cs +++ b/Chat/Speech/TTSPlayer.cs @@ -11,8 +11,8 @@ public class TTSPlayer public TTSPlayer() { - _messages = new PriorityQueue(); - _buffer = new PriorityQueue(); + _messages = new PriorityQueue(new DescendingOrder()); + _buffer = new PriorityQueue(new DescendingOrder()); _mutex = new Mutex(); _mutex2 = new Mutex(); } @@ -104,6 +104,10 @@ public class TTSPlayer { return _messages.Count == 0; } + + private class DescendingOrder : IComparer { + public int Compare(int x, int y) => y.CompareTo(x); + } } public class TTSMessage diff --git a/Hermes/HermesClient.cs b/Hermes/HermesApiClient.cs similarity index 57% rename from Hermes/HermesClient.cs rename to Hermes/HermesApiClient.cs index eec233a..c5093be 100644 --- a/Hermes/HermesClient.cs +++ b/Hermes/HermesApiClient.cs @@ -4,10 +4,14 @@ using System.Text.Json; using HermesSocketLibrary.Requests.Messages; using TwitchChatTTS.Hermes; using TwitchChatTTS.Twitch.Redemptions; +using TwitchChatTTS.Chat.Groups.Permissions; +using TwitchChatTTS.Chat.Groups; public class HermesApiClient { private readonly WebClientWrap _web; + + public const string BASE_URL = "tomtospeech.com"; public HermesApiClient(Configuration configuration) { @@ -24,15 +28,14 @@ public class HermesApiClient _web.AddHeader("x-api-key", configuration.Hermes.Token); } - public async Task GetTTSVersion() + public async Task GetLatestTTSVersion() { - var version = await _web.GetJson("https://hermes.goblincaves.com/api/info/version"); - return version; + return await _web.GetJson($"https://{BASE_URL}/api/info/version"); } public async Task FetchHermesAccountDetails() { - var account = await _web.GetJson("https://hermes.goblincaves.com/api/account"); + var account = await _web.GetJson($"https://{BASE_URL}/api/account"); if (account == null || account.Id == null || account.Username == null) throw new NullReferenceException("Invalid value found while fetching for hermes account data."); return account; @@ -40,7 +43,7 @@ public class HermesApiClient public async Task FetchTwitchBotToken() { - var token = await _web.GetJson("https://hermes.goblincaves.com/api/token/bot"); + var token = await _web.GetJson($"https://{BASE_URL}/api/token/bot"); if (token == null || token.ClientId == null || token.AccessToken == null || token.RefreshToken == null || token.ClientSecret == null) throw new Exception("Failed to fetch Twitch API token from Hermes."); @@ -49,7 +52,7 @@ public class HermesApiClient public async Task> FetchTTSUsernameFilters() { - var filters = await _web.GetJson>("https://hermes.goblincaves.com/api/settings/tts/filter/users"); + var filters = await _web.GetJson>($"https://{BASE_URL}/api/settings/tts/filter/users"); if (filters == null) throw new Exception("Failed to fetch TTS username filters from Hermes."); @@ -58,7 +61,7 @@ public class HermesApiClient public async Task FetchTTSDefaultVoice() { - var data = await _web.GetJson("https://hermes.goblincaves.com/api/settings/tts/default"); + var data = await _web.GetJson($"https://{BASE_URL}/api/settings/tts/default"); if (data == null) throw new Exception("Failed to fetch TTS default voice from Hermes."); @@ -67,7 +70,7 @@ public class HermesApiClient public async Task> FetchTTSChatterSelectedVoices() { - var voices = await _web.GetJson>("https://hermes.goblincaves.com/api/settings/tts/selected"); + var voices = await _web.GetJson>($"https://{BASE_URL}/api/settings/tts/selected"); if (voices == null) throw new Exception("Failed to fetch TTS chatter selected voices from Hermes."); @@ -76,7 +79,7 @@ public class HermesApiClient public async Task> FetchTTSEnabledVoices() { - var voices = await _web.GetJson>("https://hermes.goblincaves.com/api/settings/tts"); + var voices = await _web.GetJson>($"https://{BASE_URL}/api/settings/tts"); if (voices == null) throw new Exception("Failed to fetch TTS enabled voices from Hermes."); @@ -85,7 +88,7 @@ public class HermesApiClient public async Task> FetchTTSWordFilters() { - var filters = await _web.GetJson>("https://hermes.goblincaves.com/api/settings/tts/filter/words"); + var filters = await _web.GetJson>($"https://{BASE_URL}/api/settings/tts/filter/words"); if (filters == null) throw new Exception("Failed to fetch TTS word filters from Hermes."); @@ -94,20 +97,59 @@ public class HermesApiClient public async Task> FetchRedemptions() { - var redemptions = await _web.GetJson>("https://hermes.goblincaves.com/api/settings/redemptions", new JsonSerializerOptions() + var redemptions = await _web.GetJson>($"https://{BASE_URL}/api/settings/redemptions", new JsonSerializerOptions() { PropertyNameCaseInsensitive = false, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); if (redemptions == null) - throw new Exception("Failed to redemptions from Hermes."); + throw new Exception("Failed to fetch redemptions from Hermes."); return redemptions; } + public async Task> FetchGroups() + { + var groups = await _web.GetJson>($"https://{BASE_URL}/api/settings/groups", new JsonSerializerOptions() + { + PropertyNameCaseInsensitive = false, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }); + if (groups == null) + throw new Exception("Failed to fetch groups from Hermes."); + + return groups; + } + + public async Task> FetchGroupChatters() + { + var chatters = await _web.GetJson>($"https://{BASE_URL}/api/settings/groups/users", new JsonSerializerOptions() + { + PropertyNameCaseInsensitive = false, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }); + if (chatters == null) + throw new Exception("Failed to fetch groups from Hermes."); + + return chatters; + } + + public async Task> FetchGroupPermissions() + { + var permissions = await _web.GetJson>($"https://{BASE_URL}/api/settings/groups/permissions", new JsonSerializerOptions() + { + PropertyNameCaseInsensitive = false, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }); + if (permissions == null) + throw new Exception("Failed to fetch group permissions from Hermes."); + + return permissions; + } + public async Task> FetchRedeemableActions() { - var actions = await _web.GetJson>("https://hermes.goblincaves.com/api/settings/redemptions/actions"); + var actions = await _web.GetJson>($"https://{BASE_URL}/api/settings/redemptions/actions"); if (actions == null) throw new Exception("Failed to fetch redeemable actions from Hermes."); diff --git a/Hermes/Socket/Handlers/HeartbeatHandler.cs b/Hermes/Socket/Handlers/HeartbeatHandler.cs index 5f7e664..8a13d5c 100644 --- a/Hermes/Socket/Handlers/HeartbeatHandler.cs +++ b/Hermes/Socket/Handlers/HeartbeatHandler.cs @@ -21,11 +21,9 @@ namespace TwitchChatTTS.Hermes.Socket.Handlers return; if (sender is not HermesSocketClient client) - { return; - } - _logger.Verbose("Received heartbeat."); + _logger.Verbose("Received heartbeat from server."); client.LastHeartbeatReceived = DateTime.UtcNow; diff --git a/Hermes/Socket/Handlers/LoginAckHandler.cs b/Hermes/Socket/Handlers/LoginAckHandler.cs index 35e45b6..4b78db5 100644 --- a/Hermes/Socket/Handlers/LoginAckHandler.cs +++ b/Hermes/Socket/Handlers/LoginAckHandler.cs @@ -31,13 +31,10 @@ namespace TwitchChatTTS.Hermes.Socket.Handlers _logger.Warning("Another client has connected to the same account."); return; } - else - { - client.UserId = message.UserId; - _logger.Information($"Logged in as {_user.TwitchUsername}."); - } + client.UserId = message.UserId; _user.OwnerId = message.OwnerId; + _logger.Information($"Logged in as {_user.TwitchUsername} {(message.WebLogin ? "via web" : "via TTS app")}."); await client.Send(3, new RequestMessage() { diff --git a/Hermes/Socket/HermesSocketClient.cs b/Hermes/Socket/HermesSocketClient.cs index 00d88eb..59e3171 100644 --- a/Hermes/Socket/HermesSocketClient.cs +++ b/Hermes/Socket/HermesSocketClient.cs @@ -17,6 +17,8 @@ namespace TwitchChatTTS.Hermes.Socket private System.Timers.Timer _heartbeatTimer; private System.Timers.Timer _reconnectTimer; + public const string BASE_URL = "ws.tomtospeech.com"; + public HermesSocketClient( Configuration configuration, [FromKeyedServices("hermes")] HandlerManager handlerManager, @@ -84,7 +86,7 @@ namespace TwitchChatTTS.Hermes.Socket { try { - await ConnectAsync($"wss://hermes-ws.goblincaves.com"); + await ConnectAsync($"wss://{HermesSocketClient.BASE_URL}"); Connected = true; } catch (Exception) diff --git a/OBS/Socket/Data/OBSSceneItem.cs b/OBS/Socket/Data/OBSSceneItem.cs new file mode 100644 index 0000000..0a58a40 --- /dev/null +++ b/OBS/Socket/Data/OBSSceneItem.cs @@ -0,0 +1,10 @@ +namespace TwitchChatTTS.OBS.Socket.Data +{ + public class OBSSceneItem + { + public string SourceUuid { get; set; } + public string SourceName { get; set; } + public string SourceType { get; set; } + public int SceneItemId { get; set; } + } +} \ No newline at end of file diff --git a/OBS/Socket/Data/RequestBatchMessage.cs b/OBS/Socket/Data/RequestBatchMessage.cs index cdf52bf..ced8708 100644 --- a/OBS/Socket/Data/RequestBatchMessage.cs +++ b/OBS/Socket/Data/RequestBatchMessage.cs @@ -1,3 +1,5 @@ +using Newtonsoft.Json; + namespace TwitchChatTTS.OBS.Socket.Data { public class RequestBatchMessage @@ -5,7 +7,7 @@ namespace TwitchChatTTS.OBS.Socket.Data public string RequestId { get; set; } public bool HaltOnFailure { get; set; } public RequestBatchExecutionType ExecutionType { get; set; } - public IEnumerable Requests { get; set;} + public IEnumerable Requests { get; set; } public RequestBatchMessage(string id, IEnumerable requests, bool haltOnFailure = false, RequestBatchExecutionType executionType = RequestBatchExecutionType.SerialRealtime) { @@ -16,7 +18,8 @@ namespace TwitchChatTTS.OBS.Socket.Data } } - public enum RequestBatchExecutionType { + public enum RequestBatchExecutionType + { None = -1, SerialRealtime = 0, SerialFrame = 1, diff --git a/OBS/Socket/Data/RequestResponseMessage.cs b/OBS/Socket/Data/RequestResponseMessage.cs index 40fcf65..71e569c 100644 --- a/OBS/Socket/Data/RequestResponseMessage.cs +++ b/OBS/Socket/Data/RequestResponseMessage.cs @@ -1,3 +1,5 @@ +using Newtonsoft.Json; + namespace TwitchChatTTS.OBS.Socket.Data { public class RequestResponseMessage diff --git a/OBS/Socket/Handlers/IdentifiedHandler.cs b/OBS/Socket/Handlers/IdentifiedHandler.cs index e7511c9..08d1b76 100644 --- a/OBS/Socket/Handlers/IdentifiedHandler.cs +++ b/OBS/Socket/Handlers/IdentifiedHandler.cs @@ -2,16 +2,19 @@ using CommonSocketLibrary.Abstract; using CommonSocketLibrary.Common; using Serilog; using TwitchChatTTS.OBS.Socket.Data; +using TwitchChatTTS.OBS.Socket.Manager; namespace TwitchChatTTS.OBS.Socket.Handlers { public class IdentifiedHandler : IWebSocketHandler { + private readonly OBSManager _manager; private readonly ILogger _logger; public int OperationCode { get; } = 2; - public IdentifiedHandler(ILogger logger) + public IdentifiedHandler(OBSManager manager, ILogger logger) { + _manager = manager; _logger = logger; } @@ -22,6 +25,15 @@ namespace TwitchChatTTS.OBS.Socket.Handlers sender.Connected = true; _logger.Information("Connected to OBS via rpc version " + message.NegotiatedRpcVersion + "."); + + try + { + await _manager.GetGroupList(async groups => await _manager.GetGroupSceneItemList(groups)); + } + catch (Exception e) + { + _logger.Error(e, "Failed to load OBS group info upon OBS identification."); + } } } } \ No newline at end of file diff --git a/OBS/Socket/Handlers/RequestBatchResponseHandler.cs b/OBS/Socket/Handlers/RequestBatchResponseHandler.cs index deaf57f..10e4c99 100644 --- a/OBS/Socket/Handlers/RequestBatchResponseHandler.cs +++ b/OBS/Socket/Handlers/RequestBatchResponseHandler.cs @@ -44,7 +44,7 @@ namespace TwitchChatTTS.OBS.Socket.Handlers try { _logger.Debug($"Request response from OBS request batch #{i + 1}/{count}: {results[i]}"); - var response = JsonSerializer.Deserialize(results[i].ToString(), new JsonSerializerOptions() + var response = JsonSerializer.Deserialize(results[i].ToString()!, new JsonSerializerOptions() { PropertyNameCaseInsensitive = false, PropertyNamingPolicy = JsonNamingPolicy.CamelCase diff --git a/OBS/Socket/Handlers/RequestResponseHandler.cs b/OBS/Socket/Handlers/RequestResponseHandler.cs index 0e2ee0b..5ff467a 100644 --- a/OBS/Socket/Handlers/RequestResponseHandler.cs +++ b/OBS/Socket/Handlers/RequestResponseHandler.cs @@ -1,3 +1,4 @@ +using System.Text.Json; using CommonSocketLibrary.Abstract; using CommonSocketLibrary.Common; using Serilog; @@ -72,10 +73,7 @@ namespace TwitchChatTTS.OBS.Socket.Handlers _logger.Debug($"Found the scene item by name [scene: {sceneName}][source: {sourceName}][id: {sceneItemId}][obs request id: {message.RequestId}]."); //_manager.AddSourceId(sceneName.ToString(), sourceName.ToString(), (long) sceneItemId); - requestData.ResponseValues = new Dictionary - { - { "sceneItemId", sceneItemId } - }; + requestData.ResponseValues = message.ResponseData; break; } case "GetSceneItemTransform": @@ -102,10 +100,7 @@ namespace TwitchChatTTS.OBS.Socket.Handlers } _logger.Debug($"Fetched OBS transformation data [scene: {sceneName}][scene item id: {sceneItemId}][transformation: {transformData}][obs request id: {message.RequestId}]"); - requestData.ResponseValues = new Dictionary - { - { "sceneItemTransform", transformData } - }; + requestData.ResponseValues = message.ResponseData; break; } case "GetSceneItemEnabled": @@ -132,10 +127,7 @@ namespace TwitchChatTTS.OBS.Socket.Handlers } _logger.Debug($"Fetched OBS scene item visibility [scene: {sceneName}][scene item id: {sceneItemId}][visibility: {sceneItemVisibility}][obs request id: {message.RequestId}]"); - requestData.ResponseValues = new Dictionary - { - { "sceneItemEnabled", sceneItemVisibility } - }; + requestData.ResponseValues = message.ResponseData; break; } case "SetSceneItemTransform": @@ -168,11 +160,68 @@ namespace TwitchChatTTS.OBS.Socket.Handlers _logger.Debug($"Received response from OBS for updating scene item visibility [scene: {sceneName}][scene item id: {sceneItemId}][obs request id: {message.RequestId}]"); break; } + case "GetGroupList": + { + if (message.ResponseData == null) + { + _logger.Warning($"OBS Response is null [obs request id: {message.RequestId}]"); + return; + } + if (!message.ResponseData.TryGetValue("groups", out object? value) || value == null) + { + _logger.Warning($"Failed to fetch the scene item visibility [obs request id: {message.RequestId}]"); + return; + } + var groups = JsonSerializer.Deserialize>(value.ToString()); + _logger.Debug($"Fetched OBS groups [obs request id: {message.RequestId}]"); + requestData.ResponseValues = new Dictionary() + { + { "groups", groups } + }; + break; + } + case "GetGroupSceneItemList": + { + if (!request.RequestData.TryGetValue("sceneName", out object? sceneName) || sceneName == null) + { + _logger.Warning($"Failed to find the scene name that was requested [obs request id: {message.RequestId}]"); + return; + } + if (message.ResponseData == null) + { + _logger.Warning($"OBS Response is null [scene: {sceneName}][obs request id: {message.RequestId}]"); + return; + } + if (!message.ResponseData.TryGetValue("sceneItems", out object? value) || value == null) + { + _logger.Warning($"Failed to fetch the scene item visibility [scene: {sceneName}][obs request id: {message.RequestId}]"); + return; + } + _logger.Debug($"Fetched OBS scene items in group [scene: {sceneName}][obs request id: {message.RequestId}]"); + var sceneItems = JsonSerializer.Deserialize>(value.ToString()!, new JsonSerializerOptions() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }); + if (sceneItems == null) + { + _logger.Warning($"Failed to deserialize the data received [scene: {sceneName}][obs request id: {message.RequestId}]"); + return; + } + + foreach (var sceneItem in sceneItems) + _manager.AddSourceId(sceneItem.SourceName, sceneItem.SceneItemId); + + requestData.ResponseValues = new Dictionary() + { + { "groups", sceneItems } + }; + break; + } case "Sleep": { if (!request.RequestData.TryGetValue("sleepMillis", out object? sleepMillis) || sleepMillis == null) { - _logger.Warning($"Failed to find the scene name that was requested [obs request id: {message.RequestId}]"); + _logger.Warning($"Failed to find the amount of time to sleep for [obs request id: {message.RequestId}]"); return; } _logger.Debug($"Received response from OBS for sleeping [sleep: {sleepMillis}][obs request id: {message.RequestId}]"); diff --git a/OBS/Socket/Manager/OBSManager.cs b/OBS/Socket/Manager/OBSManager.cs index 400fabb..5565b37 100644 --- a/OBS/Socket/Manager/OBSManager.cs +++ b/OBS/Socket/Manager/OBSManager.cs @@ -10,10 +10,10 @@ namespace TwitchChatTTS.OBS.Socket.Manager { public class OBSManager { - private IDictionary _requests; - private IDictionary> _sourceIds; - private IServiceProvider _serviceProvider; - private ILogger _logger; + private readonly IDictionary _requests; + private readonly IDictionary _sourceIds; + private readonly IServiceProvider _serviceProvider; + private readonly ILogger _logger; public OBSManager(IServiceProvider serviceProvider, ILogger logger) { @@ -21,41 +21,41 @@ namespace TwitchChatTTS.OBS.Socket.Manager _logger = logger; _requests = new ConcurrentDictionary(); - _sourceIds = new Dictionary>(); + _sourceIds = new Dictionary(); } - public void AddSourceId(string sceneName, string sourceName, long sourceId) + public void AddSourceId(string sourceName, long sourceId) { - if (!_sourceIds.TryGetValue(sceneName, out var scene)) - { - scene = new Dictionary(); - _sourceIds.Add(sceneName, scene); - } - - if (scene.ContainsKey(sourceName)) - scene[sourceName] = sourceId; + if (!_sourceIds.TryGetValue(sourceName, out _)) + _sourceIds.Add(sourceName, sourceId); else - scene.Add(sourceName, sourceId); + _sourceIds[sourceName] = sourceId; + _logger.Debug($"Added OBS scene item to cache [scene item: {sourceName}][scene item id: {sourceId}]"); + } + public void ClearCache() + { + _sourceIds.Clear(); } public async Task Send(IEnumerable messages) { string uid = GenerateUniqueIdentifier(); - _logger.Debug($"Sending OBS request batch of {messages.Count()} messages [obs request id: {uid}]."); + var list = messages.ToList(); + _logger.Debug($"Sending OBS request batch of {list.Count} messages [obs request batch id: {uid}]."); // Keep track of requests to know what we requested. - foreach (var message in messages) + foreach (var message in list) { message.RequestId = GenerateUniqueIdentifier(); var data = new RequestData(message, uid); _requests.Add(message.RequestId, data); } - _logger.Debug($"Generated uid for all OBS request messages in batch [obs request id: {uid}][obs request ids: {string.Join(", ", messages.Select(m => m.RequestType + "=" + m.RequestId))}]"); + _logger.Debug($"Generated uid for all OBS request messages in batch [obs request batch id: {uid}][obs request ids: {string.Join(", ", list.Select(m => m.RequestType + "=" + m.RequestId))}]"); var client = _serviceProvider.GetRequiredKeyedService>("obs"); - await client.Send(8, new RequestBatchMessage(uid, messages)); + await client.Send(8, new RequestBatchMessage(uid, list)); } public async Task Send(RequestMessage message, Action>? callback = null) @@ -121,7 +121,7 @@ namespace TwitchChatTTS.OBS.Socket.Manager transform.BoundsAlignment = a = (int)OBSAlignment.Center; else transform.Alignment = a = (int)OBSAlignment.Center; - + transform.PositionX = transform.PositionX + w / 2; transform.PositionY = transform.PositionY + h / 2; } @@ -144,63 +144,99 @@ namespace TwitchChatTTS.OBS.Socket.Manager var m3 = new RequestMessage("SetSceneItemTransform", string.Empty, new Dictionary() { { "sceneName", sceneName }, { "sceneItemId", sceneItemId }, { "sceneItemTransform", transform } }); await Send(m3); - _logger.Debug($"New transformation data [scene: {sceneName}][sceneItemName: {sceneItemName}][sceneItemId: {sceneItemId}][transform: {transformData}][obs request id: {m2.RequestId}]"); + _logger.Debug($"New transformation data [scene: {sceneName}][sceneItemName: {sceneItemName}][sceneItemId: {sceneItemId}][obs request id: {m3.RequestId}]"); }); }); } public async Task ToggleSceneItemVisibility(string sceneName, string sceneItemName) { - LogExceptions(async () => + await GetSceneItemById(sceneName, sceneItemName, async (sceneItemId) => { - await GetSceneItemById(sceneName, sceneItemName, async (sceneItemId) => + var m1 = new RequestMessage("GetSceneItemEnabled", string.Empty, new Dictionary() { { "sceneName", sceneName }, { "sceneItemId", sceneItemId } }); + await Send(m1, async (d) => { - var m1 = new RequestMessage("GetSceneItemEnabled", string.Empty, new Dictionary() { { "sceneName", sceneName }, { "sceneItemId", sceneItemId } }); - await Send(m1, async (d) => - { - if (d == null || !d.TryGetValue("sceneItemEnabled", out object? visible) || visible == null) - return; + if (d == null || !d.TryGetValue("sceneItemEnabled", out object? visible) || visible == null) + return; - var m2 = new RequestMessage("SetSceneItemEnabled", string.Empty, new Dictionary() { { "sceneName", sceneName }, { "sceneItemId", sceneItemId }, { "sceneItemEnabled", visible.ToString().ToLower() == "true" ? false : true } }); - await Send(m2); - }); + var m2 = new RequestMessage("SetSceneItemEnabled", string.Empty, new Dictionary() { { "sceneName", sceneName }, { "sceneItemId", sceneItemId }, { "sceneItemEnabled", visible.ToString().ToLower() == "true" ? false : true } }); + await Send(m2); }); - }, "Failed to toggle OBS scene item visibility."); + }); } public async Task UpdateSceneItemVisibility(string sceneName, string sceneItemName, bool isVisible) { - LogExceptions(async () => + await GetSceneItemById(sceneName, sceneItemName, async (sceneItemId) => { - await GetSceneItemById(sceneName, sceneItemName, async (sceneItemId) => - { - var m = new RequestMessage("SetSceneItemEnabled", string.Empty, new Dictionary() { { "sceneName", sceneName }, { "sceneItemId", sceneItemId }, { "sceneItemEnabled", isVisible } }); - await Send(m); - }); - }, "Failed to update OBS scene item visibility."); + var m = new RequestMessage("SetSceneItemEnabled", string.Empty, new Dictionary() { { "sceneName", sceneName }, { "sceneItemId", sceneItemId }, { "sceneItemEnabled", isVisible } }); + await Send(m); + }); } public async Task UpdateSceneItemIndex(string sceneName, string sceneItemName, int index) { - LogExceptions(async () => + await GetSceneItemById(sceneName, sceneItemName, async (sceneItemId) => { - await GetSceneItemById(sceneName, sceneItemName, async (sceneItemId) => - { - var m = new RequestMessage("SetSceneItemIndex", string.Empty, new Dictionary() { { "sceneName", sceneName }, { "sceneItemId", sceneItemId }, { "sceneItemIndex", index } }); - await Send(m); - }); - }, "Failed to update OBS scene item index."); + var m = new RequestMessage("SetSceneItemIndex", string.Empty, new Dictionary() { { "sceneName", sceneName }, { "sceneItemId", sceneItemId }, { "sceneItemIndex", index } }); + await Send(m); + }); + } + + public async Task GetGroupList(Action>? action) + { + var m = new RequestMessage("GetGroupList", string.Empty, new Dictionary()); + await Send(m, (d) => + { + if (d == null || !d.TryGetValue("groups", out object? value) || value == null) + return; + + var list = (IEnumerable)value; + _logger.Debug("Fetched the list of groups in OBS."); + if (list != null) + action?.Invoke(list); + }); + } + + public async Task GetGroupSceneItemList(string groupName, Action>? action) + { + var m = new RequestMessage("GetGroupSceneItemList", string.Empty, new Dictionary() { { "sceneName", groupName } }); + await Send(m, (d) => + { + if (d == null || !d.TryGetValue("sceneItems", out object? value) || value == null) + return; + + var list = (IEnumerable)value; + _logger.Debug($"Fetched the list of OBS scene items in a group [group: {groupName}]"); + if (list != null) + action?.Invoke(list); + }); + } + + public async Task GetGroupSceneItemList(IEnumerable groupNames) + { + var messages = groupNames.Select(group => new RequestMessage("GetGroupSceneItemList", string.Empty, new Dictionary() { { "sceneName", group } })); + await Send(messages); + _logger.Debug($"Fetched the list of OBS scene items in all groups [groups: {string.Join(", ", groupNames)}]"); } private async Task GetSceneItemById(string sceneName, string sceneItemName, Action action) { - var m1 = new RequestMessage("GetSceneItemId", string.Empty, new Dictionary() { { "sceneName", sceneName }, { "sourceName", sceneItemName } }); - await Send(m1, async (d) => + if (_sourceIds.TryGetValue(sceneItemName, out long sourceId)) + { + _logger.Debug($"Fetched scene item id from cache [scene: {sceneName}][scene item: {sceneItemName}][scene item id: {sourceId}]"); + action.Invoke(sourceId); + return; + } + + var m = new RequestMessage("GetSceneItemId", string.Empty, new Dictionary() { { "sceneName", sceneName }, { "sourceName", sceneItemName } }); + await Send(m, async (d) => { if (d == null || !d.TryGetValue("sceneItemId", out object? value) || value == null || !long.TryParse(value.ToString(), out long sceneItemId)) return; - _logger.Debug($"Fetched scene item id from OBS [scene: {sceneName}][scene item: {sceneItemName}][scene item id: {sceneItemId}][obs request id: {m1.RequestId}]"); + _logger.Debug($"Fetched scene item id from OBS [scene: {sceneName}][scene item: {sceneItemName}][scene item id: {sceneItemId}][obs request id: {m.RequestId}]"); + AddSourceId(sceneItemName, sceneItemId); action.Invoke(sceneItemId); }); } diff --git a/Seven/ChatterDatabase.cs b/Seven/ChatterDatabase.cs new file mode 100644 index 0000000..2b02538 --- /dev/null +++ b/Seven/ChatterDatabase.cs @@ -0,0 +1,43 @@ +namespace TwitchChatTTS.Seven +{ + public class ChatterDatabase + { + private readonly IDictionary _chatters; + //private readonly HashSet _chatterIds; + + + public ChatterDatabase() + { + _chatters = new Dictionary(); + //_chatterIds = new HashSet(); + } + + public void Add(string username, long chatterId) + { + // if (_chatterIds.TryGetValue(chatterId, out var _)) { + // // TODO: send message to update username for id. + // } else + // _chatterIds.Add(chatterId); + + if (_chatters.ContainsKey(username)) + _chatters[username] = chatterId; + else + _chatters.Add(username, chatterId); + } + + public void Clear() + { + _chatters.Clear(); + } + + public long? Get(string emoteName) + { + return _chatters.TryGetValue(emoteName, out var chatterId) ? chatterId : null; + } + + public void Remove(string emoteName) + { + _chatters.Remove(emoteName); + } + } +} \ No newline at end of file diff --git a/Seven/Emotes.cs b/Seven/EmoteDatabase.cs similarity index 93% rename from Seven/Emotes.cs rename to Seven/EmoteDatabase.cs index be0d68f..41a55aa 100644 --- a/Seven/Emotes.cs +++ b/Seven/EmoteDatabase.cs @@ -30,8 +30,7 @@ namespace TwitchChatTTS.Seven public void Remove(string emoteName) { - if (_emotes.ContainsKey(emoteName)) - _emotes.Remove(emoteName); + _emotes.Remove(emoteName); } } diff --git a/Seven/Socket/Handlers/EndOfStreamHandler.cs b/Seven/Socket/Handlers/EndOfStreamHandler.cs index b18f5a7..421fcdd 100644 --- a/Seven/Socket/Handlers/EndOfStreamHandler.cs +++ b/Seven/Socket/Handlers/EndOfStreamHandler.cs @@ -18,7 +18,7 @@ namespace TwitchChatTTS.Seven.Socket.Handlers public int OperationCode { get; } = 7; - public EndOfStreamHandler(ILogger logger, User user, IServiceProvider serviceProvider) + public EndOfStreamHandler(User user, IServiceProvider serviceProvider, ILogger logger) { _logger = logger; _user = user; @@ -76,7 +76,10 @@ namespace TwitchChatTTS.Seven.Socket.Handlers } if (string.IsNullOrWhiteSpace(_user.SevenEmoteSetId)) + { + _logger.Warning("Connected to 7tv websocket previously, but no emote set id was set."); return; + } var context = _serviceProvider.GetRequiredService(); if (_reconnectDelay[code] > 0) @@ -90,11 +93,11 @@ namespace TwitchChatTTS.Seven.Socket.Handlers if (context.SessionId != null) { await sender.Send(34, new ResumeMessage() { SessionId = context.SessionId }); - _logger.Information("Resumed connection to 7tv websocket."); + _logger.Debug("Resumed connection to 7tv websocket."); } else { - _logger.Information("Resumed connection to 7tv websocket on a different session."); + _logger.Debug("Resumed connection to 7tv websocket on a different session."); } } } diff --git a/Startup.cs b/Startup.cs index 96260cc..a8b1744 100644 --- a/Startup.cs +++ b/Startup.cs @@ -29,6 +29,8 @@ using Serilog; using Serilog.Events; using Serilog.Sinks.SystemConsole.Themes; using TwitchChatTTS.Twitch.Redemptions; +using TwitchChatTTS.Chat.Groups.Permissions; +using TwitchChatTTS.Chat.Groups; // dotnet publish -r linux-x64 -p:PublishSingleFile=true --self-contained true // dotnet publish -r win-x64 -p:PublishSingleFile=true --self-contained true @@ -53,7 +55,7 @@ var logger = new LoggerConfiguration() .MinimumLevel.Override("TwitchLib", LogEventLevel.Warning) .MinimumLevel.Override("mariuszgromada", LogEventLevel.Error) .Enrich.FromLogContext() - .WriteTo.File("logs/log-.log", restrictedToMinimumLevel: LogEventLevel.Debug, rollingInterval: RollingInterval.Day, retainedFileCountLimit: 7) + .WriteTo.File("logs/log-.log", restrictedToMinimumLevel: LogEventLevel.Debug, rollingInterval: RollingInterval.Day, retainedFileCountLimit: 3) .WriteTo.Console(restrictedToMinimumLevel: LogEventLevel.Information, theme: SystemConsoleTheme.Colored) .CreateLogger(); @@ -78,6 +80,8 @@ s.AddKeyedSingleton("command-refreshttsdata" s.AddKeyedSingleton("command-obs"); s.AddKeyedSingleton("command-tts"); s.AddKeyedSingleton("command-version"); +s.AddSingleton(); +s.AddSingleton(); s.AddSingleton(); s.AddSingleton(); diff --git a/TTS.cs b/TTS.cs index 0d4b48f..2ea4a61 100644 --- a/TTS.cs +++ b/TTS.cs @@ -11,18 +11,23 @@ using TwitchChatTTS.Seven; using TwitchLib.Client.Events; using TwitchChatTTS.Twitch.Redemptions; using org.mariuszgromada.math.mxparser; +using TwitchChatTTS.Hermes.Socket; +using TwitchChatTTS.Chat.Groups.Permissions; +using TwitchChatTTS.Chat.Groups; namespace TwitchChatTTS { public class TTS : IHostedService { public const int MAJOR_VERSION = 3; - public const int MINOR_VERSION = 6; + public const int MINOR_VERSION = 8; private readonly User _user; private readonly HermesApiClient _hermesApiClient; private readonly SevenApiClient _sevenApiClient; private readonly RedemptionManager _redemptionManager; + private readonly IChatterGroupManager _chatterGroupManager; + private readonly IGroupPermissionManager _permissionManager; private readonly Configuration _configuration; private readonly TTSPlayer _player; private readonly IServiceProvider _serviceProvider; @@ -33,6 +38,8 @@ namespace TwitchChatTTS HermesApiClient hermesApiClient, SevenApiClient sevenApiClient, RedemptionManager redemptionManager, + IChatterGroupManager chatterGroupManager, + IGroupPermissionManager permissionManager, Configuration configuration, TTSPlayer player, IServiceProvider serviceProvider, @@ -43,6 +50,8 @@ namespace TwitchChatTTS _hermesApiClient = hermesApiClient; _sevenApiClient = sevenApiClient; _redemptionManager = redemptionManager; + _chatterGroupManager = chatterGroupManager; + _permissionManager = permissionManager; _configuration = configuration; _player = player; _serviceProvider = serviceProvider; @@ -54,13 +63,18 @@ namespace TwitchChatTTS Console.Title = "TTS - Twitch Chat"; License.iConfirmCommercialUse("abcdef"); - if (string.IsNullOrWhiteSpace(_configuration.Hermes.Token)) { + if (string.IsNullOrWhiteSpace(_configuration.Hermes.Token)) + { _logger.Error("Hermes API token not set in the configuration file."); return; } - var hermesVersion = await _hermesApiClient.GetTTSVersion(); - if (hermesVersion.MajorVersion > TTS.MAJOR_VERSION || hermesVersion.MajorVersion == TTS.MAJOR_VERSION && hermesVersion.MinorVersion > TTS.MINOR_VERSION) + var hermesVersion = await _hermesApiClient.GetLatestTTSVersion(); + if (hermesVersion == null) + { + _logger.Warning("Failed to fetch latest TTS version. Skipping version check."); + } + else if (hermesVersion.MajorVersion > TTS.MAJOR_VERSION || hermesVersion.MajorVersion == TTS.MAJOR_VERSION && hermesVersion.MinorVersion > TTS.MINOR_VERSION) { _logger.Information($"A new update for TTS is avaiable! Version {hermesVersion.MajorVersion}.{hermesVersion.MinorVersion} is available at {hermesVersion.Download}"); var changes = hermesVersion.Changelog.Split("\n"); @@ -87,9 +101,11 @@ namespace TwitchChatTTS } var emoteSet = await _sevenApiClient.FetchChannelEmoteSet(_user.TwitchUserId.ToString()); + _user.SevenEmoteSetId = emoteSet.Id; + await InitializeEmotes(_sevenApiClient, emoteSet); await InitializeHermesWebsocket(); - await InitializeSevenTv(emoteSet.Id); + await InitializeSevenTv(); await InitializeObs(); AudioPlaybackEngine.Instance.AddOnMixerInputEnded((object? s, SampleProviderEventArgs e) => @@ -108,7 +124,7 @@ namespace TwitchChatTTS { if (cancellationToken.IsCancellationRequested) { - _logger.Warning("TTS Buffer -Cancellation requested."); + _logger.Warning("TTS Buffer - Cancellation requested."); return; } @@ -170,9 +186,12 @@ namespace TwitchChatTTS } _logger.Debug("Playing message via TTS: " + m.Message); - _player.Playing = m.Audio; + if (m.Audio != null) + { + _player.Playing = m.Audio; AudioPlaybackEngine.Instance.AddMixerInput(m.Audio); + } } catch (Exception e) { @@ -238,6 +257,39 @@ namespace TwitchChatTTS var redemptions = await hermes.FetchRedemptions(); _redemptionManager.Initialize(redemptions, redemptionActions.ToDictionary(a => a.Name, a => a)); _logger.Information($"Redemption Manager has been initialized with {redemptionActions.Count()} actions & {redemptions.Count()} redemptions."); + + _chatterGroupManager.Clear(); + _permissionManager.Clear(); + + var groups = await hermes.FetchGroups(); + var groupsById = groups.ToDictionary(g => g.Id, g => g); + foreach (var group in groups) + _chatterGroupManager.Add(group); + _logger.Information($"{groups.Count()} groups have been loaded."); + + var groupChatters = await hermes.FetchGroupChatters(); + _logger.Debug($"{groupChatters.Count()} group users have been fetched."); + + var permissions = await hermes.FetchGroupPermissions(); + foreach (var permission in permissions) + { + _logger.Debug($"Adding group permission [permission id: {permission.Id}][group id: {permission.GroupId}][path: {permission.Path}][allow: {permission.Allow?.ToString() ?? "null"}]"); + if (!groupsById.TryGetValue(permission.GroupId, out var group)) + { + _logger.Warning($"Failed to find group by id [permission id: {permission.Id}][group id: {permission.GroupId}][path: {permission.Path}]"); + continue; + } + + var path = $"{group.Name}.{permission.Path}"; + _permissionManager.Set(path, permission.Allow); + _logger.Debug($"Added group permission [id: {permission.Id}][group id: {permission.GroupId}][path: {permission.Path}]"); + } + _logger.Information($"{permissions.Count()} group permissions have been loaded."); + + foreach (var chatter in groupChatters) + if (groupsById.TryGetValue(chatter.GroupId, out var group)) + _chatterGroupManager.Add(chatter.ChatterId, group.Name); + _logger.Information($"Users in each group have been loaded."); } private async Task InitializeHermesWebsocket() @@ -246,13 +298,13 @@ namespace TwitchChatTTS { _logger.Information("Initializing hermes websocket client."); var hermesClient = _serviceProvider.GetRequiredKeyedService>("hermes"); - var url = "wss://hermes-ws.goblincaves.com"; + var url = $"wss://{HermesSocketClient.BASE_URL}"; _logger.Debug($"Attempting to connect to {url}"); await hermesClient.ConnectAsync(url); hermesClient.Connected = true; await hermesClient.Send(1, new HermesLoginMessage() { - ApiKey = _configuration.Hermes.Token, + ApiKey = _configuration.Hermes!.Token!, MajorVersion = TTS.MAJOR_VERSION, MinorVersion = TTS.MINOR_VERSION, }); @@ -263,18 +315,18 @@ namespace TwitchChatTTS } } - private async Task InitializeSevenTv(string emoteSetId) + private async Task InitializeSevenTv() { try { _logger.Information("Initializing 7tv websocket client."); var sevenClient = _serviceProvider.GetRequiredKeyedService>("7tv"); - if (string.IsNullOrWhiteSpace(emoteSetId)) + if (string.IsNullOrWhiteSpace(_user.SevenEmoteSetId)) { _logger.Warning("Could not fetch 7tv emotes."); return; } - var url = $"{SevenApiClient.WEBSOCKET_URL}@emote_set.*"; + var url = $"{SevenApiClient.WEBSOCKET_URL}@emote_set.*"; _logger.Debug($"Attempting to connect to {url}"); await sevenClient.ConnectAsync($"{url}"); } @@ -341,7 +393,7 @@ namespace TwitchChatTTS } catch (Exception ex) { - _logger.Error(ex, "Unable to send emote usage message."); + _logger.Error(ex, "Unable to either execute a command or to send emote usage message."); } }); diff --git a/Twitch/TwitchApiClient.cs b/Twitch/TwitchApiClient.cs index 58fdd53..5e54383 100644 --- a/Twitch/TwitchApiClient.cs +++ b/Twitch/TwitchApiClient.cs @@ -65,7 +65,7 @@ public class TwitchApiClient try { _logger.Debug($"Attempting to authorize Twitch API [id: {broadcasterId}]"); - var authorize = await _web.GetJson("https://hermes.goblincaves.com/api/account/reauthorize"); + var authorize = await _web.GetJson($"https://{HermesApiClient.BASE_URL}/api/account/reauthorize"); if (authorize != null && broadcasterId == authorize.BroadcasterId) { _token.AccessToken = authorize.AccessToken;