Added proper slave mode - additional clients after the first connection. Fixed a few issues. Updated to version 4.8.2.

This commit is contained in:
Tom
2025-03-06 16:05:15 +00:00
parent cbdca1c008
commit 622b359b12
17 changed files with 146 additions and 33 deletions

View File

@ -1,7 +1,6 @@
using HermesSocketLibrary.Socket.Data; using HermesSocketLibrary.Socket.Data;
using Serilog; using Serilog;
using TwitchChatTTS.Hermes.Socket; using TwitchChatTTS.Hermes.Socket;
using TwitchChatTTS.Twitch.Socket;
using TwitchChatTTS.Twitch.Socket.Messages; using TwitchChatTTS.Twitch.Socket.Messages;
using static TwitchChatTTS.Chat.Commands.TTSCommands; using static TwitchChatTTS.Chat.Commands.TTSCommands;
@ -40,9 +39,9 @@ namespace TwitchChatTTS.Chat.Commands
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient hermes) public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient hermes)
{ {
_logger.Information($"TTS Version: {TTS.MAJOR_VERSION}.{TTS.MINOR_VERSION}"); _logger.Information($"TTS Version: {TTS.MAJOR_VERSION}.{TTS.MINOR_VERSION}.{TTS.PATCH_VERSION}");
await hermes.SendLoggingMessage(HermesLoggingLevel.Info, $"{_user.TwitchUsername} [twitch id: {_user.TwitchUserId}] using version {TTS.MAJOR_VERSION}.{TTS.MINOR_VERSION}."); await hermes.SendLoggingMessage(HermesLoggingLevel.Info, $"{_user.TwitchUsername} [twitch id: {_user.TwitchUserId}] using version {TTS.MAJOR_VERSION}.{TTS.MINOR_VERSION}.{TTS.PATCH_VERSION}.");
} }
} }
} }

View File

@ -62,7 +62,7 @@ namespace TwitchChatTTS.Chat.Messaging
var emoteUsage = GetEmoteUsage(fragments); var emoteUsage = GetEmoteUsage(fragments);
var tasks = new List<Task>(); var tasks = new List<Task>();
if (_obs.Streaming && _configuration.Twitch?.Slave != true) if (_obs.Streaming && !_user.Slave)
{ {
if (emoteUsage.NewEmotes.Any()) if (emoteUsage.NewEmotes.Any())
tasks.Add(_hermes.SendEmoteDetails(emoteUsage.NewEmotes)); tasks.Add(_hermes.SendEmoteDetails(emoteUsage.NewEmotes));

View File

@ -15,7 +15,6 @@ namespace TwitchChatTTS
public class TwitchConfiguration { public class TwitchConfiguration {
public bool TtsWhenOffline; public bool TtsWhenOffline;
public bool Slave;
public string? WebsocketUrl; public string? WebsocketUrl;
public string? ApiUrl; public string? ApiUrl;
} }

View File

@ -0,0 +1,45 @@
using CommonSocketLibrary.Abstract;
using CommonSocketLibrary.Common;
using HermesSocketLibrary.Socket.Data;
using Serilog;
namespace TwitchChatTTS.Hermes.Socket.Handlers
{
public class LoggingHandler : IWebSocketHandler
{
private readonly ILogger _logger;
public int OperationCode { get; } = 5;
public LoggingHandler(ILogger logger)
{
_logger = logger;
}
public Task Execute<Data>(SocketClient<WebSocketMessage> sender, Data data)
{
if (data is not LoggingMessage message || message == null)
return Task.CompletedTask;
Action<Exception?, string> logging;
if (message.Level == HermesLoggingLevel.Trace)
logging = _logger.Verbose;
else if (message.Level == HermesLoggingLevel.Debug)
logging = _logger.Debug;
else if (message.Level == HermesLoggingLevel.Info)
logging = _logger.Information;
else if (message.Level == HermesLoggingLevel.Warn)
logging = _logger.Warning;
else if (message.Level == HermesLoggingLevel.Error)
logging = _logger.Error;
else if (message.Level == HermesLoggingLevel.Critical)
logging = _logger.Fatal;
else {
_logger.Warning("Failed to receive a logging level from client.");
return Task.CompletedTask;
}
logging.Invoke(message.Exception, message.Message);
return Task.CompletedTask;
}
}
}

View File

@ -43,6 +43,9 @@ namespace TwitchChatTTS.Hermes.Socket.Handlers
return; return;
} }
_user.Slave = message.Slave;
_logger.Information(_user.Slave ? "This client is not responsible for reacting to chat messages." : "This client is responsible for reacting to chat messages.");
_user.HermesUserId = message.UserId; _user.HermesUserId = message.UserId;
_user.HermesUsername = message.UserName; _user.HermesUsername = message.UserName;
_user.TwitchUsername = message.UserName; _user.TwitchUsername = message.UserName;

View File

@ -0,0 +1,30 @@
using CommonSocketLibrary.Abstract;
using CommonSocketLibrary.Common;
using HermesSocketLibrary.Socket.Data;
using Serilog;
namespace TwitchChatTTS.Hermes.Socket.Handlers
{
public class SlaveHandler : IWebSocketHandler
{
private readonly User _user;
private readonly ILogger _logger;
public int OperationCode { get; } = 9;
public SlaveHandler(User user, ILogger logger)
{
_user = user;
_logger = logger;
}
public Task Execute<Data>(SocketClient<WebSocketMessage> sender, Data data)
{
if (data is not SlaveMessage message || message == null)
return Task.CompletedTask;
_user.Slave = message.Slave;
_logger.Information(_user.Slave ? "Total chat message ownership was revoked." : "This client is now responsible for reacting to chat messages. Potential chat messages were missed while changing ownership.");
return Task.CompletedTask;
}
}
}

View File

@ -68,14 +68,14 @@ namespace TwitchChatTTS.Hermes.Socket
ttsCreateUserVoice.Subscribe(async data => await Send(3, new RequestMessage() ttsCreateUserVoice.Subscribe(async data => await Send(3, new RequestMessage()
{ {
Type = "create_tts_user", Type = "create_tts_user",
Data = (IDictionary<string, object>) data.Value! Data = (IDictionary<string, object>)data.Value!
})); }));
var ttsUpdateUserVoice = _bus.GetTopic("tts.user.voice.update"); var ttsUpdateUserVoice = _bus.GetTopic("tts.user.voice.update");
ttsUpdateUserVoice.Subscribe(async data => await Send(3, new RequestMessage() ttsUpdateUserVoice.Subscribe(async data => await Send(3, new RequestMessage()
{ {
Type = "update_tts_user", Type = "update_tts_user",
Data = (IDictionary<string, object>) data.Value! Data = (IDictionary<string, object>)data.Value!
})); }));
} }
@ -267,6 +267,7 @@ namespace TwitchChatTTS.Hermes.Socket
ApiKey = _configuration.Hermes!.Token!, ApiKey = _configuration.Hermes!.Token!,
MajorVersion = TTS.MAJOR_VERSION, MajorVersion = TTS.MAJOR_VERSION,
MinorVersion = TTS.MINOR_VERSION, MinorVersion = TTS.MINOR_VERSION,
PatchVersion = TTS.PATCH_VERSION,
}); });
}; };
@ -276,11 +277,13 @@ namespace TwitchChatTTS.Hermes.Socket
{ {
if (!Connected) if (!Connected)
return; return;
Connected = false; Connected = false;
LoggedIn = false;
Ready = false;
_user.Slave = true;
} }
LoggedIn = false;
Ready = false;
_logger.Warning("Tom to Speech websocket client disconnected."); _logger.Warning("Tom to Speech websocket client disconnected.");
_heartbeatTimer.Enabled = false; _heartbeatTimer.Enabled = false;

View File

@ -33,13 +33,7 @@ namespace TwitchChatTTS.Hermes.Socket.Requests
return; return;
} }
var userId = requestData["user"].ToString();
var voiceId = requestData["voice"].ToString(); var voiceId = requestData["voice"].ToString();
if (string.IsNullOrEmpty(userId))
{
_logger.Warning("User Id is invalid.");
return;
}
if (string.IsNullOrEmpty(voiceId)) if (string.IsNullOrEmpty(voiceId))
{ {
_logger.Warning("Voice Id is invalid."); _logger.Warning("Voice Id is invalid.");
@ -52,7 +46,7 @@ namespace TwitchChatTTS.Hermes.Socket.Requests
} }
_user.VoicesSelected.Add(chatterId, voiceId); _user.VoicesSelected.Add(chatterId, voiceId);
_logger.Information($"Created a new TTS user [user id: {userId}][voice id: {voiceId}][voice name: {voiceName}]."); _logger.Information($"Created a new TTS user [chatter id: {_user.TwitchUserId}][chatter id: {chatterId}][voice id: {voiceId}][voice name: {voiceName}].");
} }
} }
} }

View File

@ -54,7 +54,7 @@ namespace TwitchChatTTS.Hermes.Socket.Requests
foreach (var permission in groupInfo.GroupPermissions) foreach (var permission in groupInfo.GroupPermissions)
{ {
_logger.Debug($"Adding group permission [permission id: {permission.Id}][group id: {permission.GroupId}][path: {permission.Path}][allow: {permission.Allow?.ToString() ?? "null"}]"); _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)) if (!groupsById.TryGetValue(permission.GroupId.ToString(), out var group))
{ {
_logger.Warning($"Failed to find group by id [permission id: {permission.Id}][group id: {permission.GroupId}][path: {permission.Path}]"); _logger.Warning($"Failed to find group by id [permission id: {permission.Id}][group id: {permission.GroupId}][path: {permission.Path}]");
continue; continue;

View File

@ -33,13 +33,7 @@ namespace TwitchChatTTS.Hermes.Socket.Requests
return; return;
} }
var userId = requestData["user"].ToString();
var voiceId = requestData["voice"].ToString(); var voiceId = requestData["voice"].ToString();
if (string.IsNullOrEmpty(userId))
{
_logger.Warning("User Id is invalid.");
return;
}
if (string.IsNullOrEmpty(voiceId)) if (string.IsNullOrEmpty(voiceId))
{ {
_logger.Warning("Voice Id is invalid."); _logger.Warning("Voice Id is invalid.");
@ -52,7 +46,7 @@ namespace TwitchChatTTS.Hermes.Socket.Requests
} }
_user.VoicesSelected[chatterId] = voiceId; _user.VoicesSelected[chatterId] = voiceId;
_logger.Information($"Updated a TTS user's voice [user id: {userId}][voice: {voiceId}][voice name: {voiceName}]"); _logger.Information($"Updated a TTS user's voice [user id: {_user.TwitchUserId}][voice: {voiceId}][voice name: {voiceName}]");
} }
} }
} }

View File

@ -4,6 +4,7 @@ namespace TwitchChatTTS.Hermes
{ {
public int MajorVersion { get; set; } public int MajorVersion { get; set; }
public int MinorVersion { get; set; } public int MinorVersion { get; set; }
public int? PatchVersion { get; set; }
public required string Download { get; set; } public required string Download { get; set; }
public required string Changelog { get; set; } public required string Changelog { get; set; }
} }

View File

@ -4,7 +4,7 @@ namespace TwitchChatTTS.OBS.Socket.Data
{ {
public required string ObsWebSocketVersion { get; set; } public required string ObsWebSocketVersion { get; set; }
public int RpcVersion { get; set; } public int RpcVersion { get; set; }
public required AuthenticationMessage Authentication { get; set; } public AuthenticationMessage? Authentication { get; set; }
} }
public class AuthenticationMessage { public class AuthenticationMessage {

View File

@ -209,6 +209,8 @@ s.AddKeyedSingleton<IBackoff>("hermes", new ExponentialBackoff(1000, 15 * 1000))
s.AddKeyedSingleton<IWebSocketHandler, HeartbeatHandler>("hermes"); s.AddKeyedSingleton<IWebSocketHandler, HeartbeatHandler>("hermes");
s.AddKeyedSingleton<IWebSocketHandler, LoginAckHandler>("hermes"); s.AddKeyedSingleton<IWebSocketHandler, LoginAckHandler>("hermes");
s.AddKeyedSingleton<IWebSocketHandler, RequestAckHandler>("hermes"); s.AddKeyedSingleton<IWebSocketHandler, RequestAckHandler>("hermes");
s.AddKeyedSingleton<IWebSocketHandler, LoggingHandler>("hermes");
s.AddKeyedSingleton<IWebSocketHandler, SlaveHandler>("hermes");
s.AddKeyedSingleton<MessageTypeManager<IWebSocketHandler>, HermesMessageTypeManager>("hermes"); s.AddKeyedSingleton<MessageTypeManager<IWebSocketHandler>, HermesMessageTypeManager>("hermes");
s.AddKeyedSingleton<SocketClient<WebSocketMessage>, HermesSocketClient>("hermes"); s.AddKeyedSingleton<SocketClient<WebSocketMessage>, HermesSocketClient>("hermes");

15
TTS.cs
View File

@ -22,7 +22,8 @@ namespace TwitchChatTTS
public class TTS : IHostedService public class TTS : IHostedService
{ {
public const int MAJOR_VERSION = 4; public const int MAJOR_VERSION = 4;
public const int MINOR_VERSION = 7; public const int MINOR_VERSION = 8;
public const int PATCH_VERSION = 2;
private readonly User _user; private readonly User _user;
private readonly HermesApiClient _hermesApiClient; private readonly HermesApiClient _hermesApiClient;
@ -77,6 +78,9 @@ namespace TwitchChatTTS
Console.Title = "TTS - Twitch Chat"; Console.Title = "TTS - Twitch Chat";
Console.OutputEncoding = Encoding.UTF8; Console.OutputEncoding = Encoding.UTF8;
License.iConfirmCommercialUse("abcdef"); License.iConfirmCommercialUse("abcdef");
_user.Slave = true;
_logger.Information($"This is running on version {MAJOR_VERSION}.{MINOR_VERSION}.{PATCH_VERSION}.");
if (string.IsNullOrWhiteSpace(_configuration.Hermes?.Token)) if (string.IsNullOrWhiteSpace(_configuration.Hermes?.Token))
{ {
@ -92,9 +96,9 @@ namespace TwitchChatTTS
_logger.Error("Failed to fetch latest TTS version. Something went wrong."); _logger.Error("Failed to fetch latest TTS version. Something went wrong.");
return; return;
} }
if (hermesVersion.MajorVersion > TTS.MAJOR_VERSION || hermesVersion.MajorVersion == TTS.MAJOR_VERSION && hermesVersion.MinorVersion > TTS.MINOR_VERSION) if (hermesVersion.MajorVersion > TTS.MAJOR_VERSION || hermesVersion.MajorVersion == TTS.MAJOR_VERSION && (hermesVersion.MinorVersion > TTS.MINOR_VERSION || hermesVersion.MinorVersion == TTS.MINOR_VERSION && (hermesVersion.PatchVersion == null || hermesVersion.PatchVersion > TTS.PATCH_VERSION)))
{ {
_logger.Information($"A new update for TTS is avaiable! Version {hermesVersion.MajorVersion}.{hermesVersion.MinorVersion} is available at {hermesVersion.Download}"); _logger.Information($"A new update for TTS is avaiable! Version {hermesVersion.MajorVersion}.{hermesVersion.MinorVersion}.{hermesVersion.PatchVersion} is available at {hermesVersion.Download}");
var changes = hermesVersion.Changelog.Split("\n"); var changes = hermesVersion.Changelog.Split("\n");
if (changes != null && changes.Any()) if (changes != null && changes.Any())
_logger.Information("Changelog:\n - " + string.Join("\n - ", changes) + "\n\n"); _logger.Information("Changelog:\n - " + string.Join("\n - ", changes) + "\n\n");
@ -168,7 +172,12 @@ namespace TwitchChatTTS
{ {
await Task.Delay(TimeSpan.FromSeconds(5)); await Task.Delay(TimeSpan.FromSeconds(5));
if (_user.TwitchUserId == default) if (_user.TwitchUserId == default)
{
_logger.Warning("Ensure your Tom to Speech token in the tts.config.yml file is valid."); _logger.Warning("Ensure your Tom to Speech token in the tts.config.yml file is valid.");
_logger.Warning("Re-open the application once you have made sure the token is valid.");
}
disposables.ForEach(d => d.Dispose());
}); });
} }

View File

@ -99,7 +99,9 @@ namespace TwitchChatTTS.Twitch.Redemptions
{ {
_redemptions.Add(redemption.Id, redemption); _redemptions.Add(redemption.Id, redemption);
_logger.Debug($"Added redemption to redemption manager [redemption id: {redemption.Id}]"); _logger.Debug($"Added redemption to redemption manager [redemption id: {redemption.Id}]");
} else { }
else
{
_redemptions[redemption.Id] = redemption; _redemptions[redemption.Id] = redemption;
_logger.Debug($"Updated redemption to redemption manager [redemption id: {redemption.Id}]"); _logger.Debug($"Updated redemption to redemption manager [redemption id: {redemption.Id}]");
} }
@ -263,6 +265,11 @@ namespace TwitchChatTTS.Twitch.Redemptions
break; break;
case "SPECIFIC_TTS_VOICE": case "SPECIFIC_TTS_VOICE":
case "RANDOM_TTS_VOICE": case "RANDOM_TTS_VOICE":
if (_user.Slave)
{
_logger.Debug($"Ignoring channel redemption due to being a slave client [chatter id: {senderId}][source: redemption][chatter: {senderDisplayName}][chatter id: {senderId}]");
break;
}
string voiceId = string.Empty; string voiceId = string.Empty;
bool specific = action.Type == "SPECIFIC_TTS_VOICE"; bool specific = action.Type == "SPECIFIC_TTS_VOICE";
@ -327,18 +334,43 @@ namespace TwitchChatTTS.Twitch.Redemptions
_logger.Debug($"Played an audio file for channel point redeem [file: {action.Data["file_path"]}][chatter: {senderDisplayName}][chatter id: {senderId}]"); _logger.Debug($"Played an audio file for channel point redeem [file: {action.Data["file_path"]}][chatter: {senderDisplayName}][chatter id: {senderId}]");
break; break;
case "NIGHTBOT_PLAY": case "NIGHTBOT_PLAY":
if (_user.Slave)
{
_logger.Debug($"Ignoring channel redemption due to being a slave client [chatter id: {senderId}][source: redemption][chatter: {senderDisplayName}][chatter id: {senderId}]");
break;
}
await _nightbot.Play(); await _nightbot.Play();
break; break;
case "NIGHTBOT_PAUSE": case "NIGHTBOT_PAUSE":
if (_user.Slave)
{
_logger.Debug($"Ignoring channel redemption due to being a slave client [chatter id: {senderId}][source: redemption][chatter: {senderDisplayName}][chatter id: {senderId}]");
break;
}
await _nightbot.Pause(); await _nightbot.Pause();
break; break;
case "NIGHTBOT_SKIP": case "NIGHTBOT_SKIP":
if (_user.Slave)
{
_logger.Debug($"Ignoring channel redemption due to being a slave client [chatter id: {senderId}][source: redemption][chatter: {senderDisplayName}][chatter id: {senderId}]");
break;
}
await _nightbot.Skip(); await _nightbot.Skip();
break; break;
case "NIGHTBOT_CLEAR_PLAYLIST": case "NIGHTBOT_CLEAR_PLAYLIST":
if (_user.Slave)
{
_logger.Debug($"Ignoring channel redemption due to being a slave client [chatter id: {senderId}][source: redemption][chatter: {senderDisplayName}][chatter id: {senderId}]");
break;
}
await _nightbot.ClearPlaylist(); await _nightbot.ClearPlaylist();
break; break;
case "NIGHTBOT_CLEAR_QUEUE": case "NIGHTBOT_CLEAR_QUEUE":
if (_user.Slave)
{
_logger.Debug($"Ignoring channel redemption due to being a slave client [chatter id: {senderId}][source: redemption][chatter: {senderDisplayName}][chatter id: {senderId}]");
break;
}
await _nightbot.ClearQueue(); await _nightbot.ClearQueue();
break; break;
case "VEADOTUBE_SET_STATE": case "VEADOTUBE_SET_STATE":

View File

@ -14,12 +14,12 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
public string Name => "channel.chat.message"; public string Name => "channel.chat.message";
private readonly IChatMessageReader _reader; private readonly IChatMessageReader _reader;
private readonly User _user;
private readonly ICommandManager _commands; private readonly ICommandManager _commands;
private readonly IGroupPermissionManager _permissionManager; private readonly IGroupPermissionManager _permissionManager;
private readonly IUsagePolicy<long> _permissionPolicy; private readonly IUsagePolicy<long> _permissionPolicy;
private readonly IChatterGroupManager _chatterGroupManager; private readonly IChatterGroupManager _chatterGroupManager;
private readonly ServiceBusCentral _bus; private readonly ServiceBusCentral _bus;
private readonly User _user;
private readonly ILogger _logger; private readonly ILogger _logger;
@ -35,12 +35,12 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
) )
{ {
_reader = reader; _reader = reader;
_user = user;
_commands = commands; _commands = commands;
_permissionManager = permissionManager; _permissionManager = permissionManager;
_permissionPolicy = permissionPolicy; _permissionPolicy = permissionPolicy;
_chatterGroupManager = chatterGroupManager; _chatterGroupManager = chatterGroupManager;
_bus = bus; _bus = bus;
_user = user;
_logger = logger; _logger = logger;
} }
@ -58,13 +58,13 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
var groups = GetGroups(message.Badges, chatterId); var groups = GetGroups(message.Badges, chatterId);
var bits = GetTotalBits(fragments); var bits = GetTotalBits(fragments);
if (message.ChannelPointsCustomRewardId == null) if (message.ChannelPointsCustomRewardId == null && !_user.Slave)
{ {
var commandResult = await CheckForChatCommand(message.Message.Text, message, groups); var commandResult = await CheckForChatCommand(message.Message.Text, message, groups);
if (commandResult != ChatCommandResult.Unknown) if (commandResult != ChatCommandResult.Unknown)
return; return;
} }
else else if (message.ChannelPointsCustomRewardId != null)
{ {
_bus.Send(this, "chat_message_redemption", message); _bus.Send(this, "chat_message_redemption", message);
} }

View File

@ -33,6 +33,8 @@ namespace TwitchChatTTS
[JsonIgnore] [JsonIgnore]
public Regex? VoiceNameRegex { get; set; } public Regex? VoiceNameRegex { get; set; }
public required bool Slave { get; set; }
private IDictionary<string, string> _voicesAvailable; private IDictionary<string, string> _voicesAvailable;
private HashSet<string> _voicesEnabled; private HashSet<string> _voicesEnabled;