Added groups & permissions. Fixed 7tv reconnection. Added more subcommands for refresh.
This commit is contained in:
parent
af3763a837
commit
9fb966474f
@ -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<WebSocketMessage> obsClient,
|
||||
[FromKeyedServices("hermes")] SocketClient<WebSocketMessage> 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<long>();
|
||||
sfxRegex = new Regex(@"\(([A-Za-z0-9_-]+)\)");
|
||||
}
|
||||
|
||||
@ -66,18 +75,28 @@ public class ChatMessageHandler
|
||||
var chatterId = long.Parse(m.UserId);
|
||||
var tasks = new List<Task>();
|
||||
|
||||
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<string>();
|
||||
var words = msg.Split(" ");
|
||||
var words = msg.Split(' ');
|
||||
var wordCounter = new Dictionary<string, int>();
|
||||
string filteredMsg = string.Empty;
|
||||
var newEmotes = new Dictionary<string, string>();
|
||||
@ -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,
|
||||
|
@ -14,6 +14,8 @@ namespace TwitchChatTTS.Chat.Commands
|
||||
private readonly SocketClient<WebSocketMessage> _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<bool> CheckPermissions(ChatMessage message, long broadcasterId)
|
||||
public override async Task<bool> CheckDefaultPermissions(ChatMessage message, long broadcasterId)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override async Task Execute(IList<string> args, ChatMessage message, long broadcasterId)
|
||||
{
|
||||
//var HermesClient = _serviceProvider.GetRequiredKeyedService<SocketClient<WebSocketMessage>>("hermes");
|
||||
if (_hermesClient == null)
|
||||
return;
|
||||
if (_user == null || _user.VoicesAvailable == null)
|
||||
|
@ -8,12 +8,15 @@ namespace TwitchChatTTS.Chat.Commands
|
||||
public string Name { get; }
|
||||
public string Description { get; }
|
||||
public IList<ChatCommandParameter> Parameters { get => _parameters.AsReadOnly(); }
|
||||
public bool DefaultPermissionsOverwrite { get; }
|
||||
|
||||
private IList<ChatCommandParameter> _parameters;
|
||||
|
||||
public ChatCommand(string name, string description)
|
||||
{
|
||||
Name = name;
|
||||
Description = description;
|
||||
DefaultPermissionsOverwrite = false;
|
||||
_parameters = new List<ChatCommandParameter>();
|
||||
}
|
||||
|
||||
@ -24,7 +27,7 @@ namespace TwitchChatTTS.Chat.Commands
|
||||
}
|
||||
}
|
||||
|
||||
public abstract Task<bool> CheckPermissions(ChatMessage message, long broadcasterId);
|
||||
public abstract Task<bool> CheckDefaultPermissions(ChatMessage message, long broadcasterId);
|
||||
public abstract Task Execute(IList<string> args, ChatMessage message, long broadcasterId);
|
||||
}
|
||||
}
|
@ -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<string, ChatCommand> _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<ChatCommandResult> Execute(string arg, ChatMessage message)
|
||||
public async Task<ChatCommandResult> Execute(string arg, ChatMessage message, IEnumerable<string> 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}]");
|
||||
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<string> groups, string path)
|
||||
{
|
||||
_logger.Debug($"Checking for permission [chatter id: {chatterId}][group: {string.Join(", ", groups)}][path: {path}]");
|
||||
return _permissionManager.CheckIfAllowed(groups, path);
|
||||
}
|
||||
}
|
||||
}
|
@ -30,7 +30,7 @@ namespace TwitchChatTTS.Chat.Commands
|
||||
AddParameter(unvalidatedParameter);
|
||||
}
|
||||
|
||||
public override async Task<bool> CheckPermissions(ChatMessage message, long broadcasterId)
|
||||
public override async Task<bool> CheckDefaultPermissions(ChatMessage message, long broadcasterId)
|
||||
{
|
||||
return message.IsModerator || message.IsBroadcaster;
|
||||
}
|
||||
|
@ -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<bool> CheckPermissions(ChatMessage message, long broadcasterId)
|
||||
public override async Task<bool> CheckDefaultPermissions(ChatMessage message, long broadcasterId)
|
||||
{
|
||||
return message.IsModerator || message.IsBroadcaster;
|
||||
}
|
||||
|
||||
public override async Task Execute(IList<string> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,8 @@ namespace TwitchChatTTS.Chat.Commands
|
||||
private readonly SocketClient<WebSocketMessage> _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<bool> CheckPermissions(ChatMessage message, long broadcasterId)
|
||||
public override async Task<bool> CheckDefaultPermissions(ChatMessage message, long broadcasterId)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ namespace TwitchChatTTS.Chat.Commands
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override async Task<bool> CheckPermissions(ChatMessage message, long broadcasterId)
|
||||
public override async Task<bool> CheckDefaultPermissions(ChatMessage message, long broadcasterId)
|
||||
{
|
||||
return message.IsModerator || message.IsVip || message.IsBroadcaster;
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ namespace TwitchChatTTS.Chat.Commands
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override async Task<bool> CheckPermissions(ChatMessage message, long broadcasterId)
|
||||
public override async Task<bool> CheckDefaultPermissions(ChatMessage message, long broadcasterId)
|
||||
{
|
||||
return message.IsModerator || message.IsVip || message.IsBroadcaster;
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ namespace TwitchChatTTS.Chat.Commands
|
||||
AddParameter(unvalidatedParameter);
|
||||
}
|
||||
|
||||
public override async Task<bool> CheckPermissions(ChatMessage message, long broadcasterId)
|
||||
public override async Task<bool> CheckDefaultPermissions(ChatMessage message, long broadcasterId)
|
||||
{
|
||||
return message.IsModerator || message.IsBroadcaster;
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ namespace TwitchChatTTS.Chat.Commands
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override async Task<bool> CheckPermissions(ChatMessage message, long broadcasterId)
|
||||
public override async Task<bool> CheckDefaultPermissions(ChatMessage message, long broadcasterId)
|
||||
{
|
||||
return message.IsBroadcaster;
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ namespace TwitchChatTTS.Chat.Commands
|
||||
AddParameter(ttsVoiceParameter);
|
||||
}
|
||||
|
||||
public override async Task<bool> CheckPermissions(ChatMessage message, long broadcasterId)
|
||||
public override async Task<bool> CheckDefaultPermissions(ChatMessage message, long broadcasterId)
|
||||
{
|
||||
return message.IsModerator || message.IsBroadcaster || message.IsSubscriber || message.Bits >= 100;
|
||||
}
|
||||
|
75
Chat/Groups/ChatterGroupManager.cs
Normal file
75
Chat/Groups/ChatterGroupManager.cs
Normal file
@ -0,0 +1,75 @@
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using Serilog;
|
||||
|
||||
namespace TwitchChatTTS.Chat.Groups
|
||||
{
|
||||
public class ChatterGroupManager : IChatterGroupManager
|
||||
{
|
||||
private readonly IDictionary<string, Group> _groups;
|
||||
private readonly IDictionary<long, ICollection<string>> _chatters;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
|
||||
public ChatterGroupManager(ILogger logger) {
|
||||
_logger = logger;
|
||||
_groups = new ConcurrentDictionary<string, Group>();
|
||||
_chatters = new ConcurrentDictionary<long, ICollection<string>>();
|
||||
}
|
||||
|
||||
public void Add(Group group) {
|
||||
_groups.Add(group.Name, group);
|
||||
}
|
||||
|
||||
public void Add(long chatter, string groupName) {
|
||||
_chatters.Add(chatter, new List<string>() { groupName });
|
||||
}
|
||||
|
||||
public void Add(long chatter, ICollection<string> 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<string> GetGroupNamesFor(long chatter) {
|
||||
if (_chatters.TryGetValue(chatter, out var groups))
|
||||
return groups.Select(g => _groups[g].Name);
|
||||
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
public int GetPriorityFor(long chatter) {
|
||||
if (!_chatters.TryGetValue(chatter, out var groups))
|
||||
return 0;
|
||||
|
||||
return GetPriorityFor(groups);
|
||||
}
|
||||
|
||||
public int GetPriorityFor(IEnumerable<string> 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;
|
||||
}
|
||||
}
|
||||
}
|
9
Chat/Groups/Group.cs
Normal file
9
Chat/Groups/Group.cs
Normal file
@ -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; }
|
||||
}
|
||||
}
|
8
Chat/Groups/GroupChatter.cs
Normal file
8
Chat/Groups/GroupChatter.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace TwitchChatTTS.Chat.Groups
|
||||
{
|
||||
public class GroupChatter
|
||||
{
|
||||
public string GroupId { get; set; }
|
||||
public long ChatterId { get; set;}
|
||||
}
|
||||
}
|
15
Chat/Groups/IChatterGroupManager.cs
Normal file
15
Chat/Groups/IChatterGroupManager.cs
Normal file
@ -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<string> groupIds);
|
||||
void Clear();
|
||||
Group? Get(string groupId);
|
||||
IEnumerable<string> GetGroupNamesFor(long chatter);
|
||||
int GetPriorityFor(long chatter);
|
||||
int GetPriorityFor(IEnumerable<string> groupIds);
|
||||
bool Remove(long chatter, string groupId);
|
||||
}
|
||||
}
|
10
Chat/Groups/Permissions/GroupPermission.cs
Normal file
10
Chat/Groups/Permissions/GroupPermission.cs
Normal file
@ -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; }
|
||||
}
|
||||
}
|
145
Chat/Groups/Permissions/GroupPermissionManager.cs
Normal file
145
Chat/Groups/Permissions/GroupPermissionManager.cs
Normal file
@ -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<string> 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<PermissionNode>? Children { get => _children == null ? null : new ReadOnlyCollection<PermissionNode>(_children); }
|
||||
|
||||
private bool? _allow;
|
||||
private PermissionNode? _parent;
|
||||
private IList<PermissionNode>? _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<PermissionNode>();
|
||||
_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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
Chat/Groups/Permissions/IGroupPermissionManager.cs
Normal file
11
Chat/Groups/Permissions/IGroupPermissionManager.cs
Normal file
@ -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<string> groups, string path);
|
||||
void Clear();
|
||||
bool Remove(string path);
|
||||
}
|
||||
}
|
@ -11,8 +11,8 @@ public class TTSPlayer
|
||||
|
||||
public TTSPlayer()
|
||||
{
|
||||
_messages = new PriorityQueue<TTSMessage, int>();
|
||||
_buffer = new PriorityQueue<TTSMessage, int>();
|
||||
_messages = new PriorityQueue<TTSMessage, int>(new DescendingOrder());
|
||||
_buffer = new PriorityQueue<TTSMessage, int>(new DescendingOrder());
|
||||
_mutex = new Mutex();
|
||||
_mutex2 = new Mutex();
|
||||
}
|
||||
@ -104,6 +104,10 @@ public class TTSPlayer
|
||||
{
|
||||
return _messages.Count == 0;
|
||||
}
|
||||
|
||||
private class DescendingOrder : IComparer<int> {
|
||||
public int Compare(int x, int y) => y.CompareTo(x);
|
||||
}
|
||||
}
|
||||
|
||||
public class TTSMessage
|
||||
|
@ -4,11 +4,15 @@ 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)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(configuration.Hermes?.Token))
|
||||
@ -24,15 +28,14 @@ public class HermesApiClient
|
||||
_web.AddHeader("x-api-key", configuration.Hermes.Token);
|
||||
}
|
||||
|
||||
public async Task<TTSVersion> GetTTSVersion()
|
||||
public async Task<TTSVersion?> GetLatestTTSVersion()
|
||||
{
|
||||
var version = await _web.GetJson<TTSVersion>("https://hermes.goblincaves.com/api/info/version");
|
||||
return version;
|
||||
return await _web.GetJson<TTSVersion>($"https://{BASE_URL}/api/info/version");
|
||||
}
|
||||
|
||||
public async Task<Account> FetchHermesAccountDetails()
|
||||
{
|
||||
var account = await _web.GetJson<Account>("https://hermes.goblincaves.com/api/account");
|
||||
var account = await _web.GetJson<Account>($"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<TwitchBotToken> FetchTwitchBotToken()
|
||||
{
|
||||
var token = await _web.GetJson<TwitchBotToken>("https://hermes.goblincaves.com/api/token/bot");
|
||||
var token = await _web.GetJson<TwitchBotToken>($"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<IEnumerable<TTSUsernameFilter>> FetchTTSUsernameFilters()
|
||||
{
|
||||
var filters = await _web.GetJson<IEnumerable<TTSUsernameFilter>>("https://hermes.goblincaves.com/api/settings/tts/filter/users");
|
||||
var filters = await _web.GetJson<IEnumerable<TTSUsernameFilter>>($"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<string> FetchTTSDefaultVoice()
|
||||
{
|
||||
var data = await _web.GetJson<string>("https://hermes.goblincaves.com/api/settings/tts/default");
|
||||
var data = await _web.GetJson<string>($"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<IEnumerable<TTSChatterSelectedVoice>> FetchTTSChatterSelectedVoices()
|
||||
{
|
||||
var voices = await _web.GetJson<IEnumerable<TTSChatterSelectedVoice>>("https://hermes.goblincaves.com/api/settings/tts/selected");
|
||||
var voices = await _web.GetJson<IEnumerable<TTSChatterSelectedVoice>>($"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<IEnumerable<string>> FetchTTSEnabledVoices()
|
||||
{
|
||||
var voices = await _web.GetJson<IEnumerable<string>>("https://hermes.goblincaves.com/api/settings/tts");
|
||||
var voices = await _web.GetJson<IEnumerable<string>>($"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<IEnumerable<TTSWordFilter>> FetchTTSWordFilters()
|
||||
{
|
||||
var filters = await _web.GetJson<IEnumerable<TTSWordFilter>>("https://hermes.goblincaves.com/api/settings/tts/filter/words");
|
||||
var filters = await _web.GetJson<IEnumerable<TTSWordFilter>>($"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<IEnumerable<Redemption>> FetchRedemptions()
|
||||
{
|
||||
var redemptions = await _web.GetJson<IEnumerable<Redemption>>("https://hermes.goblincaves.com/api/settings/redemptions", new JsonSerializerOptions()
|
||||
var redemptions = await _web.GetJson<IEnumerable<Redemption>>($"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<IEnumerable<Group>> FetchGroups()
|
||||
{
|
||||
var groups = await _web.GetJson<IEnumerable<Group>>($"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<IEnumerable<GroupChatter>> FetchGroupChatters()
|
||||
{
|
||||
var chatters = await _web.GetJson<IEnumerable<GroupChatter>>($"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<IEnumerable<GroupPermission>> FetchGroupPermissions()
|
||||
{
|
||||
var permissions = await _web.GetJson<IEnumerable<GroupPermission>>($"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<IEnumerable<RedeemableAction>> FetchRedeemableActions()
|
||||
{
|
||||
var actions = await _web.GetJson<IEnumerable<RedeemableAction>>("https://hermes.goblincaves.com/api/settings/redemptions/actions");
|
||||
var actions = await _web.GetJson<IEnumerable<RedeemableAction>>($"https://{BASE_URL}/api/settings/redemptions/actions");
|
||||
if (actions == null)
|
||||
throw new Exception("Failed to fetch redeemable actions from Hermes.");
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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<WebSocketClient, IWebSocketHandler> 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)
|
||||
|
10
OBS/Socket/Data/OBSSceneItem.cs
Normal file
10
OBS/Socket/Data/OBSSceneItem.cs
Normal file
@ -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; }
|
||||
}
|
||||
}
|
@ -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<object> Requests { get; set;}
|
||||
public IEnumerable<object> Requests { get; set; }
|
||||
|
||||
public RequestBatchMessage(string id, IEnumerable<object> 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,
|
||||
|
@ -1,3 +1,5 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace TwitchChatTTS.OBS.Socket.Data
|
||||
{
|
||||
public class RequestResponseMessage
|
||||
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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<RequestResponseMessage>(results[i].ToString(), new JsonSerializerOptions()
|
||||
var response = JsonSerializer.Deserialize<RequestResponseMessage>(results[i].ToString()!, new JsonSerializerOptions()
|
||||
{
|
||||
PropertyNameCaseInsensitive = false,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
|
@ -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<string, object>
|
||||
{
|
||||
{ "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<string, object>
|
||||
{
|
||||
{ "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<string, object>
|
||||
{
|
||||
{ "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<IEnumerable<string>>(value.ToString());
|
||||
_logger.Debug($"Fetched OBS groups [obs request id: {message.RequestId}]");
|
||||
requestData.ResponseValues = new Dictionary<string, object>()
|
||||
{
|
||||
{ "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<IEnumerable<OBSSceneItem>>(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<string, object>()
|
||||
{
|
||||
{ "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}]");
|
||||
|
@ -10,10 +10,10 @@ namespace TwitchChatTTS.OBS.Socket.Manager
|
||||
{
|
||||
public class OBSManager
|
||||
{
|
||||
private IDictionary<string, RequestData> _requests;
|
||||
private IDictionary<string, IDictionary<string, long>> _sourceIds;
|
||||
private IServiceProvider _serviceProvider;
|
||||
private ILogger _logger;
|
||||
private readonly IDictionary<string, RequestData> _requests;
|
||||
private readonly IDictionary<string, long> _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<string, RequestData>();
|
||||
_sourceIds = new Dictionary<string, IDictionary<string, long>>();
|
||||
_sourceIds = new Dictionary<string, long>();
|
||||
}
|
||||
|
||||
|
||||
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<string, long>();
|
||||
_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<RequestMessage> 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<SocketClient<WebSocketMessage>>("obs");
|
||||
await client.Send(8, new RequestBatchMessage(uid, messages));
|
||||
await client.Send(8, new RequestBatchMessage(uid, list));
|
||||
}
|
||||
|
||||
public async Task Send(RequestMessage message, Action<Dictionary<string, object>>? callback = null)
|
||||
@ -144,14 +144,12 @@ namespace TwitchChatTTS.OBS.Socket.Manager
|
||||
|
||||
var m3 = new RequestMessage("SetSceneItemTransform", string.Empty, new Dictionary<string, object>() { { "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) =>
|
||||
{
|
||||
@ -165,42 +163,80 @@ namespace TwitchChatTTS.OBS.Socket.Manager
|
||||
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) =>
|
||||
{
|
||||
var m = new RequestMessage("SetSceneItemEnabled", string.Empty, new Dictionary<string, object>() { { "sceneName", sceneName }, { "sceneItemId", sceneItemId }, { "sceneItemEnabled", isVisible } });
|
||||
await Send(m);
|
||||
});
|
||||
}, "Failed to update OBS scene item visibility.");
|
||||
}
|
||||
|
||||
public async Task UpdateSceneItemIndex(string sceneName, string sceneItemName, int index)
|
||||
{
|
||||
LogExceptions(async () =>
|
||||
{
|
||||
await GetSceneItemById(sceneName, sceneItemName, async (sceneItemId) =>
|
||||
{
|
||||
var m = new RequestMessage("SetSceneItemIndex", string.Empty, new Dictionary<string, object>() { { "sceneName", sceneName }, { "sceneItemId", sceneItemId }, { "sceneItemIndex", index } });
|
||||
await Send(m);
|
||||
});
|
||||
}, "Failed to update OBS scene item index.");
|
||||
}
|
||||
|
||||
public async Task GetGroupList(Action<IEnumerable<string>>? action)
|
||||
{
|
||||
var m = new RequestMessage("GetGroupList", string.Empty, new Dictionary<string, object>());
|
||||
await Send(m, (d) =>
|
||||
{
|
||||
if (d == null || !d.TryGetValue("groups", out object? value) || value == null)
|
||||
return;
|
||||
|
||||
var list = (IEnumerable<string>)value;
|
||||
_logger.Debug("Fetched the list of groups in OBS.");
|
||||
if (list != null)
|
||||
action?.Invoke(list);
|
||||
});
|
||||
}
|
||||
|
||||
public async Task GetGroupSceneItemList(string groupName, Action<IEnumerable<OBSSceneItem>>? action)
|
||||
{
|
||||
var m = new RequestMessage("GetGroupSceneItemList", string.Empty, new Dictionary<string, object>() { { "sceneName", groupName } });
|
||||
await Send(m, (d) =>
|
||||
{
|
||||
if (d == null || !d.TryGetValue("sceneItems", out object? value) || value == null)
|
||||
return;
|
||||
|
||||
var list = (IEnumerable<OBSSceneItem>)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<string> groupNames)
|
||||
{
|
||||
var messages = groupNames.Select(group => new RequestMessage("GetGroupSceneItemList", string.Empty, new Dictionary<string, object>() { { "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<long> action)
|
||||
{
|
||||
var m1 = new RequestMessage("GetSceneItemId", string.Empty, new Dictionary<string, object>() { { "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<string, object>() { { "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);
|
||||
});
|
||||
}
|
||||
|
43
Seven/ChatterDatabase.cs
Normal file
43
Seven/ChatterDatabase.cs
Normal file
@ -0,0 +1,43 @@
|
||||
namespace TwitchChatTTS.Seven
|
||||
{
|
||||
public class ChatterDatabase
|
||||
{
|
||||
private readonly IDictionary<string, long> _chatters;
|
||||
//private readonly HashSet<long> _chatterIds;
|
||||
|
||||
|
||||
public ChatterDatabase()
|
||||
{
|
||||
_chatters = new Dictionary<string, long>();
|
||||
//_chatterIds = new HashSet<long>();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -30,7 +30,6 @@ namespace TwitchChatTTS.Seven
|
||||
|
||||
public void Remove(string emoteName)
|
||||
{
|
||||
if (_emotes.ContainsKey(emoteName))
|
||||
_emotes.Remove(emoteName);
|
||||
}
|
||||
}
|
@ -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<ReconnectContext>();
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<ChatCommand, RefreshTTSDataCommand>("command-refreshttsdata"
|
||||
s.AddKeyedSingleton<ChatCommand, OBSCommand>("command-obs");
|
||||
s.AddKeyedSingleton<ChatCommand, TTSCommand>("command-tts");
|
||||
s.AddKeyedSingleton<ChatCommand, VersionCommand>("command-version");
|
||||
s.AddSingleton<IChatterGroupManager, ChatterGroupManager>();
|
||||
s.AddSingleton<IGroupPermissionManager, GroupPermissionManager>();
|
||||
s.AddSingleton<ChatCommandManager>();
|
||||
|
||||
s.AddSingleton<TTSPlayer>();
|
||||
|
78
TTS.cs
78
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,10 +186,13 @@ 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)
|
||||
{
|
||||
_logger.Error(e, "Failed to play a TTS audio message");
|
||||
@ -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<SocketClient<WebSocketMessage>>("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<SocketClient<WebSocketMessage>>("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.*<object_id={emoteSetId}>";
|
||||
var url = $"{SevenApiClient.WEBSOCKET_URL}@emote_set.*<object_id={_user.SevenEmoteSetId}>";
|
||||
_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.");
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -65,7 +65,7 @@ public class TwitchApiClient
|
||||
try
|
||||
{
|
||||
_logger.Debug($"Attempting to authorize Twitch API [id: {broadcasterId}]");
|
||||
var authorize = await _web.GetJson<TwitchBotAuth>("https://hermes.goblincaves.com/api/account/reauthorize");
|
||||
var authorize = await _web.GetJson<TwitchBotAuth>($"https://{HermesApiClient.BASE_URL}/api/account/reauthorize");
|
||||
if (authorize != null && broadcasterId == authorize.BroadcasterId)
|
||||
{
|
||||
_token.AccessToken = authorize.AccessToken;
|
||||
|
Loading…
Reference in New Issue
Block a user