Compare commits
8 Commits
48ac5c4fa0
...
master
Author | SHA1 | Date | |
---|---|---|---|
fb04f4003f | |||
eddd9e6403 | |||
622b359b12 | |||
cbdca1c008 | |||
86fc6bc24d | |||
c4e651ff7f | |||
6e6f20b097 | |||
03d24b0905 |
@ -102,7 +102,7 @@ namespace TwitchChatTTS.Chat.Commands.Limits
|
||||
private IDictionary<T, UserUsageData> _usages { get; }
|
||||
private IList<UsagePolicyNode<T>> _children { get; }
|
||||
private ILogger _logger;
|
||||
private object _lock { get; }
|
||||
private ReaderWriterLockSlim _rwls { get; }
|
||||
|
||||
public UsagePolicyNode(string name, UsagePolicyLimit? data, UsagePolicyNode<T>? parent, ILogger logger, bool root = false)
|
||||
{
|
||||
@ -114,100 +114,149 @@ namespace TwitchChatTTS.Chat.Commands.Limits
|
||||
_usages = new Dictionary<T, UserUsageData>();
|
||||
_children = new List<UsagePolicyNode<T>>();
|
||||
_logger = logger;
|
||||
_lock = new object();
|
||||
_rwls = new ReaderWriterLockSlim();
|
||||
}
|
||||
|
||||
|
||||
public UsagePolicyNode<T>? Get(IEnumerable<string> path)
|
||||
{
|
||||
if (!path.Any())
|
||||
return this;
|
||||
_rwls.EnterReadLock();
|
||||
try
|
||||
{
|
||||
if (!path.Any())
|
||||
return this;
|
||||
|
||||
var nextName = path.First();
|
||||
var next = _children.FirstOrDefault(c => c.Name == nextName);
|
||||
if (next == null)
|
||||
return this;
|
||||
return next.Get(path.Skip(1));
|
||||
var nextName = path.First();
|
||||
var next = _children.FirstOrDefault(c => c.Name == nextName);
|
||||
if (next == null)
|
||||
return this;
|
||||
return next.Get(path.Skip(1));
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
public UsagePolicyNode<T>? Remove(IEnumerable<string> path)
|
||||
{
|
||||
if (!path.Any())
|
||||
_rwls.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
if (_parent == null)
|
||||
throw new InvalidOperationException("Cannot remove root node");
|
||||
if (!path.Any())
|
||||
{
|
||||
if (_parent == null)
|
||||
throw new InvalidOperationException("Cannot remove root node");
|
||||
|
||||
_parent._children.Remove(this);
|
||||
return this;
|
||||
_parent._children.Remove(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
var nextName = path.First();
|
||||
var next = _children.FirstOrDefault(c => c.Name == nextName);
|
||||
_logger.Debug($"internal remove node [is null: {next == null}][path: {string.Join('.', path)}]");
|
||||
if (next == null)
|
||||
return null;
|
||||
return next.Remove(path.Skip(1));
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitWriteLock();
|
||||
}
|
||||
|
||||
var nextName = path.First();
|
||||
var next = _children.FirstOrDefault(c => c.Name == nextName);
|
||||
_logger.Debug($"internal remove node [is null: {next == null}][path: {string.Join('.', path)}]");
|
||||
if (next == null)
|
||||
return null;
|
||||
return next.Remove(path.Skip(1));
|
||||
}
|
||||
|
||||
public void Set(IEnumerable<string> path, int count, TimeSpan span)
|
||||
{
|
||||
if (!path.Any())
|
||||
_rwls.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
Limit = new UsagePolicyLimit(count, span);
|
||||
return;
|
||||
}
|
||||
if (!path.Any())
|
||||
{
|
||||
Limit = new UsagePolicyLimit(count, span);
|
||||
return;
|
||||
}
|
||||
|
||||
var nextName = path.First();
|
||||
var next = _children.FirstOrDefault(c => c.Name == nextName);
|
||||
_logger.Debug($"internal set node [is null: {next == null}][path: {string.Join('.', path)}]");
|
||||
if (next == null)
|
||||
{
|
||||
next = new UsagePolicyNode<T>(nextName, null, this, _logger);
|
||||
_children.Add(next);
|
||||
var nextName = path.First();
|
||||
var next = _children.FirstOrDefault(c => c.Name == nextName);
|
||||
_logger.Debug($"internal set node [is null: {next == null}][path: {string.Join('.', path)}]");
|
||||
if (next == null)
|
||||
{
|
||||
next = new UsagePolicyNode<T>(nextName, null, this, _logger);
|
||||
_children.Add(next);
|
||||
}
|
||||
next.Set(path.Skip(1), count, span);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitWriteLock();
|
||||
}
|
||||
next.Set(path.Skip(1), count, span);
|
||||
}
|
||||
|
||||
public bool TryUse(T key, DateTime timestamp)
|
||||
{
|
||||
if (_parent == null)
|
||||
return false;
|
||||
if (Limit == null || Limit.Count <= 0)
|
||||
return _parent.TryUse(key, timestamp);
|
||||
|
||||
UserUsageData? usage;
|
||||
lock (_lock)
|
||||
_rwls.EnterUpgradeableReadLock();
|
||||
try
|
||||
{
|
||||
if (_parent == null)
|
||||
return false;
|
||||
if (Limit == null || Limit.Count <= 0)
|
||||
return _parent.TryUse(key, timestamp);
|
||||
|
||||
UserUsageData? usage;
|
||||
if (!_usages.TryGetValue(key, out usage))
|
||||
{
|
||||
usage = new UserUsageData(Limit.Count, 1 % Limit.Count);
|
||||
usage.Uses[0] = timestamp;
|
||||
_usages.Add(key, usage);
|
||||
_rwls.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
usage = new UserUsageData(Limit.Count, 1 % Limit.Count);
|
||||
usage.Uses[0] = timestamp;
|
||||
_usages.Add(key, usage);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitWriteLock();
|
||||
}
|
||||
_logger.Debug($"internal use node create");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (usage.Uses.Length != Limit.Count)
|
||||
{
|
||||
var sizeDiff = Math.Max(0, usage.Uses.Length - Limit.Count);
|
||||
var temp = usage.Uses.Skip(sizeDiff);
|
||||
var tempSize = usage.Uses.Length - sizeDiff;
|
||||
usage.Uses = temp.Union(new DateTime[Math.Max(0, Limit.Count - tempSize)]).ToArray();
|
||||
_rwls.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
var sizeDiff = Math.Max(0, usage.Uses.Length - Limit.Count);
|
||||
var temp = usage.Uses.Skip(sizeDiff);
|
||||
var tempSize = usage.Uses.Length - sizeDiff;
|
||||
usage.Uses = temp.Union(new DateTime[Math.Max(0, Limit.Count - tempSize)]).ToArray();
|
||||
finally
|
||||
{
|
||||
_rwls.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt on parent node if policy has been abused.
|
||||
if (timestamp - usage.Uses[usage.Index] < Limit.Span)
|
||||
{
|
||||
_logger.Debug($"internal use node spam [span: {(timestamp - usage.Uses[usage.Index]).TotalMilliseconds}][index: {usage.Index}]");
|
||||
return _parent.TryUse(key, timestamp);
|
||||
}
|
||||
|
||||
_logger.Debug($"internal use node normal [span: {(timestamp - usage.Uses[usage.Index]).TotalMilliseconds}][index: {usage.Index}]");
|
||||
_rwls.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
usage.Uses[usage.Index] = timestamp;
|
||||
usage.Index = (usage.Index + 1) % Limit.Count;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt on parent node if policy has been abused.
|
||||
if (timestamp - usage.Uses[usage.Index] < Limit.Span)
|
||||
finally
|
||||
{
|
||||
_logger.Debug($"internal use node spam [span: {(timestamp - usage.Uses[usage.Index]).TotalMilliseconds}][index: {usage.Index}]");
|
||||
return _parent.TryUse(key, timestamp);
|
||||
}
|
||||
|
||||
_logger.Debug($"internal use node normal [span: {(timestamp - usage.Uses[usage.Index]).TotalMilliseconds}][index: {usage.Index}]");
|
||||
lock (_lock)
|
||||
{
|
||||
usage.Uses[usage.Index] = timestamp;
|
||||
usage.Index = (usage.Index + 1) % Limit.Count;
|
||||
_rwls.ExitWriteLock();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -1,7 +1,6 @@
|
||||
using HermesSocketLibrary.Socket.Data;
|
||||
using Serilog;
|
||||
using TwitchChatTTS.Hermes.Socket;
|
||||
using TwitchChatTTS.Twitch.Socket;
|
||||
using TwitchChatTTS.Twitch.Socket.Messages;
|
||||
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)
|
||||
{
|
||||
_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}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ namespace TwitchChatTTS.Chat.Emotes
|
||||
public class EmoteDatabase : IEmoteDatabase
|
||||
{
|
||||
private readonly IDictionary<string, string> _emotes;
|
||||
public IDictionary<string, string> Emotes { get => _emotes.AsReadOnly(); }
|
||||
|
||||
public EmoteDatabase()
|
||||
{
|
||||
|
@ -27,8 +27,10 @@ namespace TwitchChatTTS.Chat.Groups
|
||||
public void Add(long chatterId, string groupId)
|
||||
{
|
||||
if (_chatters.TryGetValue(chatterId, out var list))
|
||||
{
|
||||
if (!list.Contains(groupId))
|
||||
list.Add(groupId);
|
||||
}
|
||||
else
|
||||
_chatters.Add(chatterId, new List<string>() { groupId });
|
||||
}
|
||||
@ -37,8 +39,9 @@ namespace TwitchChatTTS.Chat.Groups
|
||||
{
|
||||
if (_chatters.TryGetValue(chatter, out var list))
|
||||
{
|
||||
foreach (var group in groupIds)
|
||||
list.Add(group);
|
||||
foreach (var groupId in groupIds)
|
||||
if (!list.Contains(groupId))
|
||||
list.Add(groupId);
|
||||
}
|
||||
else
|
||||
_chatters.Add(chatter, groupIds);
|
||||
@ -85,7 +88,7 @@ namespace TwitchChatTTS.Chat.Groups
|
||||
|
||||
public void Modify(Group group)
|
||||
{
|
||||
_groups[group.Name] = group;
|
||||
_groups[group.Id] = group;
|
||||
}
|
||||
|
||||
public bool Remove(string groupId)
|
||||
@ -104,10 +107,10 @@ namespace TwitchChatTTS.Chat.Groups
|
||||
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}]");
|
||||
_logger.Debug($"Removed chatter from group [chatter id: {chatterId}][group name: {_groups[groupId].Name}][group id: {groupId}]");
|
||||
return true;
|
||||
}
|
||||
_logger.Debug($"Failed to remove chatter from group [chatter id: {chatterId}][group name: {_groups[groupId]}][group id: {groupId}]");
|
||||
_logger.Debug($"Failed to remove chatter from group [chatter id: {chatterId}][group name: {_groups[groupId].Name}][group id: {groupId}]");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ namespace TwitchChatTTS.Chat.Messaging
|
||||
|
||||
var emoteUsage = GetEmoteUsage(fragments);
|
||||
var tasks = new List<Task>();
|
||||
if (_obs.Streaming && _configuration.Twitch?.Slave != true)
|
||||
if (_obs.Streaming && !_user.Slave)
|
||||
{
|
||||
if (emoteUsage.NewEmotes.Any())
|
||||
tasks.Add(_hermes.SendEmoteDetails(emoteUsage.NewEmotes));
|
||||
@ -84,6 +84,7 @@ namespace TwitchChatTTS.Chat.Messaging
|
||||
var msg = FilterMessage(fragments, reply);
|
||||
string voiceSelected = chatterId == null ? _user.DefaultTTSVoice : GetSelectedVoiceFor(chatterId.Value);
|
||||
var messages = GetPartialTTSMessages(msg, voiceSelected).ToList();
|
||||
_logger.Debug("TTS messages separated as: " + string.Join(" || ", messages.Select(m => m.Message ?? "<" + m.File + ">")));
|
||||
var groupedMessage = new TTSGroupedMessage(broadcasterId, chatterId, messageId, messages, DateTime.UtcNow, priority);
|
||||
_player.Add(groupedMessage, groupedMessage.Priority);
|
||||
|
||||
@ -223,7 +224,7 @@ namespace TwitchChatTTS.Chat.Messaging
|
||||
}];
|
||||
}
|
||||
|
||||
return matches.Cast<Match>().SelectMany(match =>
|
||||
var messages = matches.Cast<Match>().SelectMany(match =>
|
||||
{
|
||||
var m = match.Groups["message"].Value;
|
||||
if (string.IsNullOrWhiteSpace(m))
|
||||
@ -233,6 +234,13 @@ namespace TwitchChatTTS.Chat.Messaging
|
||||
voiceSelected = voiceSelected[0].ToString().ToUpper() + voiceSelected.Substring(1).ToLower();
|
||||
return HandlePartialMessage(voiceSelected, m);
|
||||
});
|
||||
string beforeMatch = message.Substring(0, matches.First().Index);
|
||||
if (!string.IsNullOrEmpty(beforeMatch))
|
||||
messages = HandlePartialMessage(defaultVoice, beforeMatch).Union(messages);
|
||||
|
||||
_logger.Debug("TTS message matches: " + string.Join(" || ", matches.Select(m => "(" + m.Length + " / " + m.Index + "): " + m.Value + " ")));
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
private string GetSelectedVoiceFor(long chatterId)
|
||||
|
@ -15,7 +15,6 @@ namespace TwitchChatTTS
|
||||
|
||||
public class TwitchConfiguration {
|
||||
public bool TtsWhenOffline;
|
||||
public bool Slave;
|
||||
public string? WebsocketUrl;
|
||||
public string? ApiUrl;
|
||||
}
|
||||
|
45
Hermes/Socket/Handlers/LoggingHandler.cs
Normal file
45
Hermes/Socket/Handlers/LoggingHandler.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -43,6 +43,9 @@ namespace TwitchChatTTS.Hermes.Socket.Handlers
|
||||
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.HermesUsername = message.UserName;
|
||||
_user.TwitchUsername = message.UserName;
|
||||
@ -51,21 +54,26 @@ namespace TwitchChatTTS.Hermes.Socket.Handlers
|
||||
_user.DefaultTTSVoice = message.DefaultTTSVoice;
|
||||
_user.VoicesAvailable = new ConcurrentDictionary<string, string>(message.TTSVoicesAvailable);
|
||||
_user.VoicesEnabled = new HashSet<string>(message.EnabledTTSVoices);
|
||||
_user.TwitchConnection = message.Connections.FirstOrDefault(c => c.Default && c.Type == "twitch");
|
||||
_user.NightbotConnection = message.Connections.FirstOrDefault(c => c.Default && c.Type == "nightbot");
|
||||
|
||||
_bus.Send(this, "twitch id", _user.TwitchUserId);
|
||||
_user.TwitchConnection = message.Connections.FirstOrDefault(c => c.Default && c.Type == "twitch") ?? message.Connections.FirstOrDefault(c => c.Type == "twitch");
|
||||
_user.NightbotConnection = message.Connections.FirstOrDefault(c => c.Default && c.Type == "nightbot") ?? message.Connections.FirstOrDefault(c => c.Type == "nightbot");
|
||||
if (_user.TwitchConnection != null)
|
||||
{
|
||||
_logger.Information("Twitch connection: " + _user.TwitchConnection.Name + " / " + _user.TwitchConnection.AccessToken);
|
||||
}
|
||||
|
||||
var filters = message.WordFilters.Where(f => f.Search != null && f.Replace != null).ToList();
|
||||
foreach (var filter in filters)
|
||||
{
|
||||
try
|
||||
{
|
||||
var re = new Regex(filter.Search, ((RegexOptions) filter.Flag) | RegexOptions.Compiled);
|
||||
var re = new Regex(filter.Search, ((RegexOptions)filter.Flag) | RegexOptions.Compiled);
|
||||
re.Match(string.Empty);
|
||||
filter.Regex = re;
|
||||
}
|
||||
catch (Exception) { }
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.Warning($"Failed to create a regular expression for a TTS filter [filter id: {filter.Search}]");
|
||||
}
|
||||
}
|
||||
_user.RegexFilters = filters;
|
||||
|
||||
@ -93,6 +101,7 @@ namespace TwitchChatTTS.Hermes.Socket.Handlers
|
||||
}
|
||||
|
||||
_logger.Information("TTS is now ready.");
|
||||
_bus.Send(this, "tts_connected", _user);
|
||||
client.Ready = true;
|
||||
}
|
||||
}
|
||||
|
30
Hermes/Socket/Handlers/SlaveHandler.cs
Normal file
30
Hermes/Socket/Handlers/SlaveHandler.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -28,7 +28,7 @@ namespace TwitchChatTTS.Hermes.Socket
|
||||
public string? UserId { get; set; }
|
||||
private readonly System.Timers.Timer _heartbeatTimer;
|
||||
private readonly IBackoff _backoff;
|
||||
private readonly object _lock;
|
||||
private readonly ReaderWriterLockSlim _rwls;
|
||||
|
||||
public bool Connected { get; set; }
|
||||
public bool LoggedIn { get; set; }
|
||||
@ -62,45 +62,55 @@ namespace TwitchChatTTS.Hermes.Socket
|
||||
LastHeartbeatReceived = LastHeartbeatSent = DateTime.UtcNow;
|
||||
URL = $"wss://{BASE_URL}";
|
||||
|
||||
_lock = new object();
|
||||
_rwls = new ReaderWriterLockSlim();
|
||||
|
||||
var ttsCreateUserVoice = _bus.GetTopic("tts.user.voice.create");
|
||||
ttsCreateUserVoice.Subscribe(async data => await Send(3, new RequestMessage()
|
||||
{
|
||||
Type = "create_tts_user",
|
||||
Data = (IDictionary<string, object>) data.Value!
|
||||
Data = (IDictionary<string, object>)data.Value!
|
||||
}));
|
||||
|
||||
var ttsUpdateUserVoice = _bus.GetTopic("tts.user.voice.update");
|
||||
ttsUpdateUserVoice.Subscribe(async data => await Send(3, new RequestMessage()
|
||||
{
|
||||
Type = "update_tts_user",
|
||||
Data = (IDictionary<string, object>) data.Value!
|
||||
Data = (IDictionary<string, object>)data.Value!
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
public override async Task Connect()
|
||||
{
|
||||
lock (_lock)
|
||||
_rwls.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
if (Connected)
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Debug($"Attempting to connect to {URL}");
|
||||
await ConnectAsync(URL);
|
||||
_logger.Debug($"Attempting to connect to {URL}");
|
||||
await ConnectAsync(URL);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Disconnect()
|
||||
{
|
||||
lock (_lock)
|
||||
_rwls.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
if (!Connected)
|
||||
return;
|
||||
}
|
||||
|
||||
await DisconnectAsync(new SocketDisconnectionEventArgs("Normal disconnection", "Disconnection was executed"));
|
||||
await DisconnectAsync(new SocketDisconnectionEventArgs("Normal disconnection", "Disconnection was executed"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CreateTTSVoice(string voiceName)
|
||||
@ -247,17 +257,15 @@ namespace TwitchChatTTS.Hermes.Socket
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_logger.Information("Initializing Hermes websocket client.");
|
||||
_logger.Information("Initializing Tom to Speech websocket client.");
|
||||
|
||||
OnConnected += async (sender, e) =>
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (Connected)
|
||||
return;
|
||||
Connected = true;
|
||||
}
|
||||
_logger.Information("Hermes websocket client connected.");
|
||||
if (Connected)
|
||||
return;
|
||||
Connected = true;
|
||||
|
||||
_logger.Information("Tom to Speech websocket client connected.");
|
||||
|
||||
_heartbeatTimer.Enabled = true;
|
||||
LastHeartbeatReceived = DateTime.UtcNow;
|
||||
@ -267,21 +275,21 @@ namespace TwitchChatTTS.Hermes.Socket
|
||||
ApiKey = _configuration.Hermes!.Token!,
|
||||
MajorVersion = TTS.MAJOR_VERSION,
|
||||
MinorVersion = TTS.MINOR_VERSION,
|
||||
PatchVersion = TTS.PATCH_VERSION,
|
||||
});
|
||||
};
|
||||
|
||||
OnDisconnected += async (sender, e) =>
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (!Connected)
|
||||
return;
|
||||
Connected = false;
|
||||
}
|
||||
if (!Connected)
|
||||
return;
|
||||
|
||||
Connected = false;
|
||||
LoggedIn = false;
|
||||
Ready = false;
|
||||
_logger.Warning("Hermes websocket client disconnected.");
|
||||
_user.Slave = true;
|
||||
|
||||
_logger.Warning("Tom to Speech websocket client disconnected.");
|
||||
|
||||
_heartbeatTimer.Enabled = false;
|
||||
await Reconnect(_backoff);
|
||||
@ -396,7 +404,7 @@ namespace TwitchChatTTS.Hermes.Socket
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Failed to send a heartbeat back to the Hermes websocket server.");
|
||||
_logger.Error(ex, "Failed to send a heartbeat back to the Tom to Speech websocket server.");
|
||||
}
|
||||
}
|
||||
else if (signalTime - LastHeartbeatReceived > TimeSpan.FromSeconds(120))
|
||||
@ -407,7 +415,7 @@ namespace TwitchChatTTS.Hermes.Socket
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Failed to disconnect from Hermes websocket server.");
|
||||
_logger.Error(ex, "Failed to disconnect from Tom to Speech websocket server.");
|
||||
Ready = false;
|
||||
LoggedIn = false;
|
||||
Connected = false;
|
||||
@ -421,13 +429,21 @@ namespace TwitchChatTTS.Hermes.Socket
|
||||
|
||||
public new async Task Send<T>(int opcode, T message)
|
||||
{
|
||||
if (!Connected)
|
||||
_rwls.EnterReadLock();
|
||||
try
|
||||
{
|
||||
_logger.Warning("Hermes websocket client is not connected. Not sending a message.");
|
||||
return;
|
||||
}
|
||||
if (!Connected)
|
||||
{
|
||||
_logger.Warning("Tom to Speech websocket client is not connected. Not sending a message.");
|
||||
return;
|
||||
}
|
||||
|
||||
await base.Send(opcode, message);
|
||||
await base.Send(opcode, message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitReadLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
52
Hermes/Socket/Requests/CreateGroupPermissionAck.cs
Normal file
52
Hermes/Socket/Requests/CreateGroupPermissionAck.cs
Normal file
@ -0,0 +1,52 @@
|
||||
using System.Text.Json;
|
||||
using HermesSocketLibrary.Requests.Messages;
|
||||
using Serilog;
|
||||
using TwitchChatTTS.Chat.Groups;
|
||||
using TwitchChatTTS.Chat.Groups.Permissions;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class CreateGroupPermissionAck : IRequestAck
|
||||
{
|
||||
public string Name => "create_group_permission";
|
||||
private readonly IChatterGroupManager _groups;
|
||||
private readonly IGroupPermissionManager _permissions;
|
||||
private readonly JsonSerializerOptions _options;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public CreateGroupPermissionAck(IChatterGroupManager groups, IGroupPermissionManager permissions, JsonSerializerOptions options, ILogger logger)
|
||||
{
|
||||
_groups = groups;
|
||||
_permissions = permissions;
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
{
|
||||
_logger.Warning($"Group JSON data is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var permission = JsonSerializer.Deserialize<GroupPermission>(json, _options);
|
||||
if (permission == null)
|
||||
{
|
||||
_logger.Warning($"Permission data is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var group = _groups.Get(permission.GroupId.ToString());
|
||||
if (group == null)
|
||||
{
|
||||
_logger.Warning($"Group id does not exist [group id: {permission.GroupId}][permission id: {permission.Id}]");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Debug($"Adding permission to group [permission id: {permission.Id}][group id: {permission.GroupId}][path: {permission.Path}][state: {permission.Allow?.ToString() ?? "Inherited"}]");
|
||||
_permissions.Set(permission.Path, permission.Allow);
|
||||
_logger.Information($"Permission has been added to group [path: {permission.Path}][state: {permission.Allow?.ToString() ?? "Inherited"}][group name: {group.Name}]");
|
||||
}
|
||||
}
|
||||
}
|
@ -48,7 +48,7 @@ namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.Warning($"Failed to generate a Regular Expression using '{filter.Search}' [filter id: {filter.Id}]");
|
||||
_logger.Warning($"Failed to create a regular expression for a TTS filter [filter id: {filter.Search}]");
|
||||
}
|
||||
|
||||
_logger.Debug($"Filter data [filter id: {filter.Id}][search: {filter.Search}][replace: {filter.Replace}]");
|
||||
|
@ -33,13 +33,7 @@ namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
return;
|
||||
}
|
||||
|
||||
var userId = requestData["user"].ToString();
|
||||
var voiceId = requestData["voice"].ToString();
|
||||
if (string.IsNullOrEmpty(userId))
|
||||
{
|
||||
_logger.Warning("User Id is invalid.");
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrEmpty(voiceId))
|
||||
{
|
||||
_logger.Warning("Voice Id is invalid.");
|
||||
@ -52,7 +46,7 @@ namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
}
|
||||
|
||||
_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}].");
|
||||
}
|
||||
}
|
||||
}
|
@ -33,16 +33,16 @@ namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
return;
|
||||
}
|
||||
|
||||
var exists = _groups.Get(groupId);
|
||||
if (exists == null)
|
||||
var group = _groups.Get(groupId);
|
||||
if (group == null)
|
||||
{
|
||||
_logger.Warning($"Group id does not exist [group id: {exists}]");
|
||||
_logger.Warning($"Group id does not exist [group id: {group}]");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Debug($"Removing group [group id: {exists.Id}][group name: {exists.Name}][group priority: {exists.Priority}]");
|
||||
_groups.Remove(exists.Id);
|
||||
_logger.Information($"Group has been updated [group id: {exists.Id}]");
|
||||
_logger.Debug($"Removing group [group id: {group.Id}][group name: {group.Name}][group priority: {group.Priority}]");
|
||||
_groups.Remove(group.Id);
|
||||
_logger.Information($"Group has been updated [group id: {group.Id}]");
|
||||
}
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@ namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
return;
|
||||
}
|
||||
|
||||
if (long.TryParse(requestData["chatter"].ToString(), out var chatterId))
|
||||
if (!long.TryParse(requestData["chatter"].ToString(), out var chatterId))
|
||||
{
|
||||
_logger.Warning($"Chatter Id is invalid [chatter id: {chatterId}]");
|
||||
return;
|
||||
|
64
Hermes/Socket/Requests/DeleteGroupPermissionAck.cs
Normal file
64
Hermes/Socket/Requests/DeleteGroupPermissionAck.cs
Normal file
@ -0,0 +1,64 @@
|
||||
using System.Text.Json;
|
||||
using HermesSocketLibrary.Requests.Messages;
|
||||
using Serilog;
|
||||
using TwitchChatTTS.Chat.Groups;
|
||||
using TwitchChatTTS.Chat.Groups.Permissions;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class DeleteGroupPermissionAck : IRequestAck
|
||||
{
|
||||
public string Name => "delete_group_permission";
|
||||
private readonly IChatterGroupManager _groups;
|
||||
private readonly IGroupPermissionManager _permissions;
|
||||
private readonly JsonSerializerOptions _options;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public DeleteGroupPermissionAck(IChatterGroupManager groups, IGroupPermissionManager permissions, JsonSerializerOptions options, ILogger logger)
|
||||
{
|
||||
_groups = groups;
|
||||
_permissions = permissions;
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
if (requestData == null)
|
||||
{
|
||||
_logger.Warning("Request data is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!requestData.TryGetValue("id", out var permissionId))
|
||||
{
|
||||
_logger.Warning($"Permission Id could not be found.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
{
|
||||
_logger.Warning($"Group JSON data is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var permission = JsonSerializer.Deserialize<GroupPermission>(json, _options);
|
||||
if (permission == null)
|
||||
{
|
||||
_logger.Warning($"Permission data is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var group = _groups.Get(permission.GroupId.ToString());
|
||||
if (group == null)
|
||||
{
|
||||
_logger.Warning($"Group id does not exist [group id: {permission.GroupId}][permission id: {permission.Id}]");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Debug($"Removing permission from group [permission id: {permission.Id}][group id: {permission.GroupId}][path: {permission.Path}][state: {permission.Allow?.ToString() ?? "Inherited"}]");
|
||||
_permissions.Remove(permissionId.ToString()!);
|
||||
_logger.Information($"Permission has been removed from group [path: {permission.Path}][state: {permission.Allow?.ToString() ?? "Inherited"}][group name: {group.Name}]");
|
||||
}
|
||||
}
|
||||
}
|
@ -46,12 +46,15 @@ namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
|
||||
var groupsById = groupInfo.Groups.ToDictionary(g => g.Id, g => g);
|
||||
foreach (var group in groupInfo.Groups)
|
||||
{
|
||||
_logger.Debug($"Adding group [group id: {group.Id}][name: {group.Name}][priority: {group.Priority}]");
|
||||
_groups.Add(group);
|
||||
}
|
||||
|
||||
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"}]");
|
||||
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}]");
|
||||
continue;
|
||||
|
@ -33,11 +33,14 @@ namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
try
|
||||
{
|
||||
var re = new Regex(filter.Search, ((RegexOptions) filter.Flag) | RegexOptions.Compiled);
|
||||
var re = new Regex(filter.Search, ((RegexOptions)filter.Flag) | RegexOptions.Compiled);
|
||||
re.Match(string.Empty);
|
||||
filter.Regex = re;
|
||||
}
|
||||
catch (Exception) { }
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.Warning($"Failed to create a regular expression for a TTS filter [filter id: {filter.Search}]");
|
||||
}
|
||||
}
|
||||
_user.RegexFilters = filters;
|
||||
_logger.Information($"TTS word filters [count: {_user.RegexFilters.Count}] have been refreshed.");
|
||||
|
52
Hermes/Socket/Requests/UpdateGroupPermissionAck.cs
Normal file
52
Hermes/Socket/Requests/UpdateGroupPermissionAck.cs
Normal file
@ -0,0 +1,52 @@
|
||||
using System.Text.Json;
|
||||
using HermesSocketLibrary.Requests.Messages;
|
||||
using Serilog;
|
||||
using TwitchChatTTS.Chat.Groups;
|
||||
using TwitchChatTTS.Chat.Groups.Permissions;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class UpdateGroupPermissionAck : IRequestAck
|
||||
{
|
||||
public string Name => "update_group_permission";
|
||||
private readonly IChatterGroupManager _groups;
|
||||
private readonly IGroupPermissionManager _permissions;
|
||||
private readonly JsonSerializerOptions _options;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public UpdateGroupPermissionAck(IChatterGroupManager groups, IGroupPermissionManager permissions, JsonSerializerOptions options, ILogger logger)
|
||||
{
|
||||
_groups = groups;
|
||||
_permissions = permissions;
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
{
|
||||
_logger.Warning($"Group JSON data is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var permission = JsonSerializer.Deserialize<GroupPermission>(json, _options);
|
||||
if (permission == null)
|
||||
{
|
||||
_logger.Warning($"Permission data is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var group = _groups.Get(permission.GroupId.ToString());
|
||||
if (group == null)
|
||||
{
|
||||
_logger.Warning($"Group id does not exist [group id: {permission.GroupId}][permission id: {permission.Id}]");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Debug($"Updating permission to group [permission id: {permission.Id}][group id: {permission.GroupId}][path: {permission.Path}][state: {permission.Allow?.ToString() ?? "Inherited"}]");
|
||||
_permissions.Set(permission.Path, permission.Allow);
|
||||
_logger.Information($"Permission on group has been updated [path: {permission.Path}][state: {permission.Allow?.ToString() ?? "Inherited"}][group name: {group.Name}]");
|
||||
}
|
||||
}
|
||||
}
|
@ -52,7 +52,10 @@ namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
re.Match(string.Empty);
|
||||
current.Regex = re;
|
||||
}
|
||||
catch (Exception) { }
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.Warning($"Failed to create a regular expression for a TTS filter [filter id: {filter.Search}]");
|
||||
}
|
||||
|
||||
_logger.Information($"Filter has been updated [filter id: {filter.Id}]");
|
||||
}
|
||||
|
@ -33,13 +33,7 @@ namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
return;
|
||||
}
|
||||
|
||||
var userId = requestData["user"].ToString();
|
||||
var voiceId = requestData["voice"].ToString();
|
||||
if (string.IsNullOrEmpty(userId))
|
||||
{
|
||||
_logger.Warning("User Id is invalid.");
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrEmpty(voiceId))
|
||||
{
|
||||
_logger.Warning("Voice Id is invalid.");
|
||||
@ -52,7 +46,7 @@ namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
}
|
||||
|
||||
_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}]");
|
||||
}
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ namespace TwitchChatTTS.Hermes
|
||||
{
|
||||
public int MajorVersion { get; set; }
|
||||
public int MinorVersion { get; set; }
|
||||
public int? PatchVersion { get; set; }
|
||||
public required string Download { get; set; }
|
||||
public required string Changelog { get; set; }
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ namespace TwitchChatTTS.OBS.Socket.Data
|
||||
{
|
||||
public required string ObsWebSocketVersion { get; set; }
|
||||
public int RpcVersion { get; set; }
|
||||
public required AuthenticationMessage Authentication { get; set; }
|
||||
public AuthenticationMessage? Authentication { get; set; }
|
||||
}
|
||||
|
||||
public class AuthenticationMessage {
|
||||
|
@ -9,25 +9,28 @@ namespace TwitchChatTTS.Seven.Socket.Handlers
|
||||
{
|
||||
public class DispatchHandler : IWebSocketHandler
|
||||
{
|
||||
public int OperationCode { get; } = 0;
|
||||
|
||||
private readonly ILogger _logger;
|
||||
private readonly IEmoteDatabase _emotes;
|
||||
private readonly object _lock = new object();
|
||||
public int OperationCode { get; } = 0;
|
||||
private readonly Mutex _lock;
|
||||
|
||||
public DispatchHandler(IEmoteDatabase emotes, ILogger logger)
|
||||
{
|
||||
_emotes = emotes;
|
||||
_logger = logger;
|
||||
_lock = new Mutex();
|
||||
}
|
||||
|
||||
public Task Execute<Data>(SocketClient<WebSocketMessage> sender, Data data)
|
||||
{
|
||||
if (data is not DispatchMessage message || message == null)
|
||||
if (data is not DispatchMessage message || message == null || message.Body == null)
|
||||
return Task.CompletedTask;
|
||||
ApplyChanges(message?.Body?.Pulled, cf => cf.OldValue, true);
|
||||
ApplyChanges(message?.Body?.Pushed, cf => cf.Value, false);
|
||||
ApplyChanges(message?.Body?.Removed, cf => cf.OldValue, true);
|
||||
ApplyChanges(message?.Body?.Updated, cf => cf.OldValue, false, cf => cf.Value);
|
||||
|
||||
ApplyChanges(message.Body.Pulled, cf => cf.OldValue, true);
|
||||
ApplyChanges(message.Body.Pushed, cf => cf.Value, false);
|
||||
ApplyChanges(message.Body.Removed, cf => cf.OldValue, true);
|
||||
ApplyChanges(message.Body.Updated, cf => cf.OldValue, false, cf => cf.Value);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@ -42,7 +45,7 @@ namespace TwitchChatTTS.Seven.Socket.Handlers
|
||||
if (value == null)
|
||||
continue;
|
||||
|
||||
var o = JsonSerializer.Deserialize<EmoteField>(value.ToString(), new JsonSerializerOptions()
|
||||
var o = JsonSerializer.Deserialize<EmoteField>(value.ToString()!, new JsonSerializerOptions()
|
||||
{
|
||||
PropertyNameCaseInsensitive = false,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
|
||||
@ -50,8 +53,9 @@ namespace TwitchChatTTS.Seven.Socket.Handlers
|
||||
if (o == null)
|
||||
continue;
|
||||
|
||||
lock (_lock)
|
||||
try
|
||||
{
|
||||
_lock.WaitOne();
|
||||
if (removing)
|
||||
{
|
||||
if (_emotes.Get(o.Name) != o.Id)
|
||||
@ -71,8 +75,10 @@ namespace TwitchChatTTS.Seven.Socket.Handlers
|
||||
}
|
||||
_emotes.Remove(o.Name);
|
||||
var update = updater(val);
|
||||
if (update == null)
|
||||
continue;
|
||||
|
||||
var u = JsonSerializer.Deserialize<EmoteField>(update.ToString(), new JsonSerializerOptions()
|
||||
var u = JsonSerializer.Deserialize<EmoteField>(update.ToString()!, new JsonSerializerOptions()
|
||||
{
|
||||
PropertyNameCaseInsensitive = false,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
|
||||
@ -94,6 +100,10 @@ namespace TwitchChatTTS.Seven.Socket.Handlers
|
||||
_logger.Information($"Added 7tv emote [name: {o.Name}][id: {o.Id}]");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ReleaseMutex();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
using System.Net.WebSockets;
|
||||
using CommonSocketLibrary.Abstract;
|
||||
using CommonSocketLibrary.Common;
|
||||
using TwitchChatTTS.Seven.Socket.Data;
|
||||
|
@ -89,6 +89,7 @@ s.AddTransient<ICommandFactory, CommandFactory>();
|
||||
s.AddSingleton<RequestAckManager>();
|
||||
s.AddTransient<IRequestAck, CreateGroupAck>();
|
||||
s.AddTransient<IRequestAck, CreateGroupChatterAck>();
|
||||
s.AddTransient<IRequestAck, CreateGroupPermissionAck>();
|
||||
s.AddTransient<IRequestAck, CreatePolicyAck>();
|
||||
s.AddTransient<IRequestAck, CreateRedeemableActionAck>();
|
||||
s.AddTransient<IRequestAck, CreateRedemptionAck>();
|
||||
@ -97,6 +98,7 @@ s.AddTransient<IRequestAck, CreateTTSUserAck>();
|
||||
s.AddTransient<IRequestAck, CreateTTSVoiceAck>();
|
||||
s.AddTransient<IRequestAck, DeleteGroupAck>();
|
||||
s.AddTransient<IRequestAck, DeleteGroupChatterAck>();
|
||||
s.AddTransient<IRequestAck, DeleteGroupPermissionAck>();
|
||||
s.AddTransient<IRequestAck, DeletePolicyAck>();
|
||||
s.AddTransient<IRequestAck, DeleteRedeemableActionAck>();
|
||||
s.AddTransient<IRequestAck, DeleteRedemptionAck>();
|
||||
@ -116,6 +118,7 @@ s.AddTransient<IRequestAck, GetTTSVoicesAck>();
|
||||
s.AddTransient<IRequestAck, GetTTSWordFiltersAck>();
|
||||
s.AddTransient<IRequestAck, UpdateGroupAck>();
|
||||
s.AddTransient<IRequestAck, UpdateGroupChatterAck>();
|
||||
s.AddTransient<IRequestAck, UpdateGroupPermissionAck>();
|
||||
s.AddTransient<IRequestAck, UpdateDefaultTTSVoiceAck>();
|
||||
s.AddTransient<IRequestAck, UpdatePolicyAck>();
|
||||
s.AddTransient<IRequestAck, UpdateRedeemableActionAck>();
|
||||
@ -181,7 +184,6 @@ s.AddKeyedTransient<SocketClient<TwitchWebsocketMessage>, TwitchWebsocketClient>
|
||||
{
|
||||
var factory = sp.GetRequiredService<ITwitchConnectionManager>();
|
||||
var client = factory.GetWorkingClient();
|
||||
client.Connect().Wait();
|
||||
return client;
|
||||
});
|
||||
s.AddKeyedTransient<SocketClient<TwitchWebsocketMessage>, TwitchWebsocketClient>("twitch-create");
|
||||
@ -210,6 +212,8 @@ s.AddKeyedSingleton<IBackoff>("hermes", new ExponentialBackoff(1000, 15 * 1000))
|
||||
s.AddKeyedSingleton<IWebSocketHandler, HeartbeatHandler>("hermes");
|
||||
s.AddKeyedSingleton<IWebSocketHandler, LoginAckHandler>("hermes");
|
||||
s.AddKeyedSingleton<IWebSocketHandler, RequestAckHandler>("hermes");
|
||||
s.AddKeyedSingleton<IWebSocketHandler, LoggingHandler>("hermes");
|
||||
s.AddKeyedSingleton<IWebSocketHandler, SlaveHandler>("hermes");
|
||||
|
||||
s.AddKeyedSingleton<MessageTypeManager<IWebSocketHandler>, HermesMessageTypeManager>("hermes");
|
||||
s.AddKeyedSingleton<SocketClient<WebSocketMessage>, HermesSocketClient>("hermes");
|
||||
|
119
TTS.cs
119
TTS.cs
@ -1,7 +1,6 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Serilog;
|
||||
using NAudio.Wave.SampleProviders;
|
||||
using org.mariuszgromada.math.mxparser;
|
||||
using TwitchChatTTS.Hermes.Socket;
|
||||
using TwitchChatTTS.Seven.Socket;
|
||||
@ -13,17 +12,18 @@ using TwitchChatTTS.Twitch.Socket.Messages;
|
||||
using TwitchChatTTS.Twitch.Socket;
|
||||
using TwitchChatTTS.Chat.Commands;
|
||||
using System.Text;
|
||||
using TwitchChatTTS.Chat.Speech;
|
||||
using TwitchChatTTS.Veadotube;
|
||||
using TwitchChatTTS.Bus;
|
||||
using System.Reactive.Linq;
|
||||
using System.Net.WebSockets;
|
||||
|
||||
namespace TwitchChatTTS
|
||||
{
|
||||
public class TTS : IHostedService
|
||||
{
|
||||
public const int MAJOR_VERSION = 4;
|
||||
public const int MINOR_VERSION = 6;
|
||||
public const int MINOR_VERSION = 8;
|
||||
public const int PATCH_VERSION = 2;
|
||||
|
||||
private readonly User _user;
|
||||
private readonly HermesApiClient _hermesApiClient;
|
||||
@ -36,8 +36,6 @@ namespace TwitchChatTTS
|
||||
private readonly ICommandFactory _commandFactory;
|
||||
private readonly ICommandManager _commandManager;
|
||||
private readonly IEmoteDatabase _emotes;
|
||||
private readonly TTSPlayer _player;
|
||||
private readonly AudioPlaybackEngine _playback;
|
||||
private readonly ServiceBusCentral _bus;
|
||||
private readonly Configuration _configuration;
|
||||
private readonly ILogger _logger;
|
||||
@ -54,8 +52,6 @@ namespace TwitchChatTTS
|
||||
ICommandFactory commandFactory,
|
||||
ICommandManager commandManager,
|
||||
IEmoteDatabase emotes,
|
||||
TTSPlayer player,
|
||||
AudioPlaybackEngine playback,
|
||||
ServiceBusCentral bus,
|
||||
Configuration configuration,
|
||||
ILogger logger
|
||||
@ -72,8 +68,6 @@ namespace TwitchChatTTS
|
||||
_commandFactory = commandFactory;
|
||||
_commandManager = commandManager;
|
||||
_emotes = emotes;
|
||||
_player = player;
|
||||
_playback = playback;
|
||||
_bus = bus;
|
||||
_configuration = configuration;
|
||||
_logger = logger;
|
||||
@ -84,10 +78,13 @@ namespace TwitchChatTTS
|
||||
Console.Title = "TTS - Twitch Chat";
|
||||
Console.OutputEncoding = Encoding.UTF8;
|
||||
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))
|
||||
{
|
||||
_logger.Error("Hermes API token not set in the configuration file.");
|
||||
_logger.Error("Tom to Speech API token not set in the yml file.");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -99,9 +96,9 @@ namespace TwitchChatTTS
|
||||
_logger.Error("Failed to fetch latest TTS version. Something went wrong.");
|
||||
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");
|
||||
if (changes != null && changes.Any())
|
||||
_logger.Information("Changelog:\n - " + string.Join("\n - ", changes) + "\n\n");
|
||||
@ -113,56 +110,75 @@ namespace TwitchChatTTS
|
||||
_logger.Warning("Failed to check for version updates.");
|
||||
}
|
||||
|
||||
var disposables = new List<IDisposable>();
|
||||
|
||||
// 7tv
|
||||
var twitchTopic = _bus.GetTopic("twitch id");
|
||||
twitchTopic.FirstAsync().Subscribe(async (data) =>
|
||||
var connected = _bus.GetTopic("tts_connected");
|
||||
disposables.Add(connected.FirstAsync().Subscribe(async (data) =>
|
||||
{
|
||||
var twitchId = data.Value?.ToString();
|
||||
if (twitchId == null)
|
||||
if (data.Value is not User user)
|
||||
{
|
||||
_logger.Warning("Something went wrong. Unable to fetch 7tv data.");
|
||||
return;
|
||||
}
|
||||
if (user.TwitchUserId == default)
|
||||
{
|
||||
_logger.Warning("Unable to fetch 7tv data. If this is wrong, ensure your Tom to Speech token is valid.");
|
||||
return;
|
||||
}
|
||||
|
||||
var emoteSet = await _sevenApiClient.FetchChannelEmoteSet(_user.TwitchUserId);
|
||||
if (emoteSet != null)
|
||||
{
|
||||
_user.SevenEmoteSetId = emoteSet.Id;
|
||||
_logger.Debug($"Fetched the 7tv emote set id [emote set id: {emoteSet.Id}]");
|
||||
}
|
||||
|
||||
await InitializeEmotes(_sevenApiClient, emoteSet);
|
||||
await InitializeSevenTv();
|
||||
});
|
||||
}));
|
||||
|
||||
await InitializeHermesWebsocket();
|
||||
|
||||
_playback.AddOnMixerInputEnded((object? s, SampleProviderEventArgs e) =>
|
||||
disposables.Add(connected.FirstAsync().Subscribe(async (data) =>
|
||||
{
|
||||
if (_player.Playing?.Audio == e.SampleProvider)
|
||||
if (data.Value is not User user)
|
||||
{
|
||||
_player.Playing = null;
|
||||
_logger.Warning("Something went wrong. Not connecting to Twitch.");
|
||||
return;
|
||||
}
|
||||
if (user.TwitchUserId == default)
|
||||
{
|
||||
_logger.Warning("Not connecting to Twitch. If this is wrong, ensure your Tom to Speech token is valid.");
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
_veado.Initialize();
|
||||
await _veado.Connect();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Warning(e, "Failed to connect to Veado websocket server.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _twitch.Connect();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(e, "Failed to connect to Twitch websocket server.");
|
||||
await Task.Delay(TimeSpan.FromSeconds(30));
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
await _twitch.Connect();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(e, "Failed to connect to Twitch websocket server.");
|
||||
}
|
||||
}));
|
||||
|
||||
_commandManager.Update(_commandFactory);
|
||||
|
||||
await InitializeVeadotube();
|
||||
await InitializeHermesWebsocket();
|
||||
await InitializeObs();
|
||||
|
||||
// Check if user has successfully connected.
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
if (_user.TwitchUserId == default)
|
||||
{
|
||||
_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());
|
||||
});
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
@ -181,6 +197,10 @@ namespace TwitchChatTTS
|
||||
_hermes.Initialize();
|
||||
await _hermes.Connect();
|
||||
}
|
||||
catch (WebSocketException e) when (e.Message.Contains("The server returned status code '502'"))
|
||||
{
|
||||
_logger.Error("Could not connect to Tom to Speech server.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(e, "Connecting to hermes failed. Skipping hermes websockets.");
|
||||
@ -213,6 +233,19 @@ namespace TwitchChatTTS
|
||||
}
|
||||
}
|
||||
|
||||
private async Task InitializeVeadotube()
|
||||
{
|
||||
try
|
||||
{
|
||||
_veado.Initialize();
|
||||
await _veado.Connect();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Warning(e, "Failed to connect to Veado websocket server.");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task InitializeEmotes(SevenApiClient sevenapi, EmoteSet? channelEmotes)
|
||||
{
|
||||
var globalEmotes = await sevenapi.FetchGlobalSevenEmotes();
|
||||
|
@ -30,6 +30,14 @@ namespace TwitchChatTTS
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_playback.AddOnMixerInputEnded((object? s, SampleProviderEventArgs e) =>
|
||||
{
|
||||
if (_player.Playing?.Audio == e.SampleProvider)
|
||||
{
|
||||
_player.Playing = null;
|
||||
}
|
||||
});
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
while (true)
|
||||
|
@ -27,7 +27,7 @@ namespace TwitchChatTTS.Twitch.Redemptions
|
||||
private readonly AudioPlaybackEngine _playback;
|
||||
private readonly ILogger _logger;
|
||||
private readonly Random _random;
|
||||
private readonly object _lock;
|
||||
private readonly ReaderWriterLockSlim _rwls;
|
||||
|
||||
|
||||
public RedemptionManager(
|
||||
@ -50,7 +50,7 @@ namespace TwitchChatTTS.Twitch.Redemptions
|
||||
_playback = playback;
|
||||
_logger = logger;
|
||||
_random = new Random();
|
||||
_lock = new object();
|
||||
_rwls = new ReaderWriterLockSlim();
|
||||
|
||||
var topic = _bus.GetTopic("redemptions_initiation");
|
||||
topic.Subscribe(data =>
|
||||
@ -99,7 +99,9 @@ namespace TwitchChatTTS.Twitch.Redemptions
|
||||
{
|
||||
_redemptions.Add(redemption.Id, redemption);
|
||||
_logger.Debug($"Added redemption to redemption manager [redemption id: {redemption.Id}]");
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
_redemptions[redemption.Id] = redemption;
|
||||
_logger.Debug($"Updated redemption to redemption manager [redemption id: {redemption.Id}]");
|
||||
}
|
||||
@ -108,16 +110,15 @@ namespace TwitchChatTTS.Twitch.Redemptions
|
||||
|
||||
private void Add(string twitchRedemptionId, string redemptionId)
|
||||
{
|
||||
lock (_lock)
|
||||
_rwls.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
if (!_redeems.TryGetValue(twitchRedemptionId, out var redeems))
|
||||
_redeems.Add(twitchRedemptionId, redeems = new List<string>());
|
||||
|
||||
var item = _redemptions.TryGetValue(redemptionId, out var r) ? r : null;
|
||||
if (item == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var redemptions = redeems.Select(r => _redemptions.TryGetValue(r, out var rr) ? rr : null);
|
||||
bool added = false;
|
||||
@ -136,12 +137,17 @@ namespace TwitchChatTTS.Twitch.Redemptions
|
||||
if (!added)
|
||||
redeems.Add(redemptionId);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitWriteLock();
|
||||
}
|
||||
_logger.Debug($"Added redemption action [redemption id: {redemptionId}][twitch redemption id: {twitchRedemptionId}]");
|
||||
}
|
||||
|
||||
private void Add(string twitchRedemptionId, Redemption item)
|
||||
{
|
||||
lock (_lock)
|
||||
_rwls.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
if (!_redeems.TryGetValue(twitchRedemptionId, out var redemptionNames))
|
||||
_redeems.Add(twitchRedemptionId, redemptionNames = new List<string>());
|
||||
@ -163,6 +169,10 @@ namespace TwitchChatTTS.Twitch.Redemptions
|
||||
if (!added)
|
||||
redemptionNames.Add(item.Id);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitWriteLock();
|
||||
}
|
||||
_logger.Debug($"Added redemption action [redemption id: {item.Id}][twitch redemption id: {twitchRedemptionId}]");
|
||||
}
|
||||
|
||||
@ -263,6 +273,11 @@ namespace TwitchChatTTS.Twitch.Redemptions
|
||||
break;
|
||||
case "SPECIFIC_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;
|
||||
bool specific = action.Type == "SPECIFIC_TTS_VOICE";
|
||||
|
||||
@ -327,18 +342,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}]");
|
||||
break;
|
||||
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();
|
||||
break;
|
||||
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();
|
||||
break;
|
||||
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();
|
||||
break;
|
||||
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();
|
||||
break;
|
||||
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();
|
||||
break;
|
||||
case "VEADOTUBE_SET_STATE":
|
||||
@ -387,7 +427,8 @@ namespace TwitchChatTTS.Twitch.Redemptions
|
||||
|
||||
public IEnumerable<RedeemableAction> Get(string twitchRedemptionId)
|
||||
{
|
||||
lock (_lock)
|
||||
_rwls.EnterReadLock();
|
||||
try
|
||||
{
|
||||
if (_redeems.TryGetValue(twitchRedemptionId, out var redemptionIds))
|
||||
return redemptionIds.Select(r => _redemptions.TryGetValue(r, out var redemption) ? redemption : null)
|
||||
@ -395,15 +436,19 @@ namespace TwitchChatTTS.Twitch.Redemptions
|
||||
.Select(r => _actions.TryGetValue(r!.ActionName, out var action) ? action : null)
|
||||
.Where(a => a != null)!;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitReadLock();
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_logger.Debug($"Redemption manager is about to initialize [redemption count: {_redemptions.Count()}][action count: {_actions.Count}]");
|
||||
|
||||
lock (_lock)
|
||||
_rwls.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
_logger.Debug($"Redemption manager is about to initialize [redemption count: {_redemptions.Count()}][action count: {_actions.Count}]");
|
||||
_redeems.Clear();
|
||||
|
||||
var ordered = _redemptions.Select(r => r.Value).Where(r => r != null).OrderBy(r => r.Order);
|
||||
@ -431,18 +476,31 @@ namespace TwitchChatTTS.Twitch.Redemptions
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitWriteLock();
|
||||
}
|
||||
|
||||
_logger.Debug("All redemptions added. Redemption Manager is ready.");
|
||||
}
|
||||
|
||||
public bool RemoveAction(string actionName)
|
||||
{
|
||||
return _actions.Remove(actionName);
|
||||
_rwls.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
return _actions.Remove(actionName);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public bool RemoveRedemption(string redemptionId)
|
||||
{
|
||||
lock (_lock)
|
||||
_rwls.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
if (!_redemptions.TryGetValue(redemptionId, out var redemption))
|
||||
{
|
||||
@ -458,6 +516,10 @@ namespace TwitchChatTTS.Twitch.Redemptions
|
||||
return true;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitWriteLock();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -475,7 +537,8 @@ namespace TwitchChatTTS.Twitch.Redemptions
|
||||
|
||||
public bool Update(Redemption redemption)
|
||||
{
|
||||
lock (_lock)
|
||||
_rwls.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
if (_redemptions.TryGetValue(redemption.Id, out var r))
|
||||
{
|
||||
@ -516,6 +579,10 @@ namespace TwitchChatTTS.Twitch.Redemptions
|
||||
return true;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitWriteLock();
|
||||
}
|
||||
|
||||
_logger.Warning($"Cannot find redemption by name [redemption id: {redemption.Id}][redemption action: {redemption.ActionName}]");
|
||||
return false;
|
||||
@ -523,12 +590,20 @@ namespace TwitchChatTTS.Twitch.Redemptions
|
||||
|
||||
public bool Update(RedeemableAction action)
|
||||
{
|
||||
if (_actions.TryGetValue(action.Name, out var a))
|
||||
_rwls.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
a.Type = action.Type;
|
||||
a.Data = action.Data;
|
||||
_logger.Debug($"Updated redeemable action in redemption manager [action name: {action.Name}]");
|
||||
return true;
|
||||
if (_actions.TryGetValue(action.Name, out var a))
|
||||
{
|
||||
a.Type = action.Type;
|
||||
a.Data = action.Data;
|
||||
_logger.Debug($"Updated redeemable action in redemption manager [action name: {action.Name}]");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitWriteLock();
|
||||
}
|
||||
|
||||
_logger.Warning($"Cannot find redeemable action by name [action name: {action.Name}]");
|
||||
|
@ -14,12 +14,12 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
|
||||
public string Name => "channel.chat.message";
|
||||
|
||||
private readonly IChatMessageReader _reader;
|
||||
private readonly User _user;
|
||||
private readonly ICommandManager _commands;
|
||||
private readonly IGroupPermissionManager _permissionManager;
|
||||
private readonly IUsagePolicy<long> _permissionPolicy;
|
||||
private readonly IChatterGroupManager _chatterGroupManager;
|
||||
private readonly ServiceBusCentral _bus;
|
||||
private readonly User _user;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
|
||||
@ -35,12 +35,12 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
|
||||
)
|
||||
{
|
||||
_reader = reader;
|
||||
_user = user;
|
||||
_commands = commands;
|
||||
_permissionManager = permissionManager;
|
||||
_permissionPolicy = permissionPolicy;
|
||||
_chatterGroupManager = chatterGroupManager;
|
||||
_bus = bus;
|
||||
_user = user;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@ -58,13 +58,13 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
|
||||
var groups = GetGroups(message.Badges, chatterId);
|
||||
var bits = GetTotalBits(fragments);
|
||||
|
||||
if (message.ChannelPointsCustomRewardId == null)
|
||||
if (message.ChannelPointsCustomRewardId == null && !_user.Slave)
|
||||
{
|
||||
var commandResult = await CheckForChatCommand(message.Message.Text, message, groups);
|
||||
if (commandResult != ChatCommandResult.Unknown)
|
||||
return;
|
||||
}
|
||||
else
|
||||
else if (message.ChannelPointsCustomRewardId != null)
|
||||
{
|
||||
_bus.Send(this, "chat_message_redemption", message);
|
||||
}
|
||||
@ -136,7 +136,7 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
|
||||
|
||||
private bool HasPermission(long chatterId, IEnumerable<string> groups, string permissionPath)
|
||||
{
|
||||
return chatterId == _user.OwnerId ? true : _permissionManager.CheckIfAllowed(groups, permissionPath) == true;
|
||||
return chatterId == _user.OwnerId || _permissionManager.CheckIfAllowed(groups, permissionPath) == true;
|
||||
}
|
||||
}
|
||||
}
|
@ -29,18 +29,9 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
|
||||
return;
|
||||
}
|
||||
|
||||
int waited = 0;
|
||||
while ((_user.TwitchUserId <= 0 || _user.TwitchConnection == null) && ++waited < 5)
|
||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||
|
||||
if (_user.TwitchConnection == null)
|
||||
{
|
||||
_logger.Error("Ensure you have linked either your Twitch account or TTS' bot to your TTS account. Twitch client will not be connecting.");
|
||||
return;
|
||||
}
|
||||
|
||||
_api.Initialize(_user.TwitchConnection.ClientId, _user.TwitchConnection.AccessToken);
|
||||
var span = _user.TwitchConnection.ExpiresAt - DateTime.Now;
|
||||
var twitchConnection = _user.TwitchConnection!;
|
||||
_api.Initialize(twitchConnection.ClientId, twitchConnection.AccessToken);
|
||||
var span = twitchConnection.ExpiresAt - DateTime.Now;
|
||||
var timeLeft = span.Days >= 2 ? span.Days + " days" : (span.Hours >= 2 ? span.Hours + " hours" : span.Minutes + " minutes");
|
||||
if (span.Days >= 3)
|
||||
_logger.Information($"Twitch connection has {timeLeft} before it is revoked.");
|
||||
@ -48,7 +39,7 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
|
||||
_logger.Warning($"Twitch connection has {timeLeft} before it is revoked. Refreshing the token is soon required.");
|
||||
else
|
||||
{
|
||||
_logger.Error("Twitch connection has its permissions revoked. Refresh the token. Twith client will not be connecting.");
|
||||
_logger.Error($"Twitch connection has its permissions revoked. Refresh the token. Twitch client will not be connecting. [expired at: {twitchConnection.ExpiresAt}]");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -18,21 +18,22 @@ namespace TwitchChatTTS.Twitch.Socket
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private readonly object _lock;
|
||||
private readonly Mutex _mutex;
|
||||
|
||||
public TwitchConnectionManager(IServiceProvider serviceProvider, ILogger logger)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_logger = logger;
|
||||
|
||||
_lock = new object();
|
||||
_mutex = new Mutex();
|
||||
}
|
||||
|
||||
|
||||
public TwitchWebsocketClient GetBackupClient()
|
||||
{
|
||||
lock (_lock)
|
||||
try
|
||||
{
|
||||
_mutex.WaitOne();
|
||||
if (_identified == null)
|
||||
throw new InvalidOperationException("Cannot get backup Twitch client yet. Waiting for identification.");
|
||||
if (_backup != null)
|
||||
@ -40,12 +41,17 @@ namespace TwitchChatTTS.Twitch.Socket
|
||||
|
||||
return CreateNewClient();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_mutex.ReleaseMutex();
|
||||
}
|
||||
}
|
||||
|
||||
public TwitchWebsocketClient GetWorkingClient()
|
||||
{
|
||||
lock (_lock)
|
||||
try
|
||||
{
|
||||
_mutex.WaitOne();
|
||||
if (_identified == null)
|
||||
{
|
||||
return CreateNewClient();
|
||||
@ -53,6 +59,10 @@ namespace TwitchChatTTS.Twitch.Socket
|
||||
|
||||
return _identified;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_mutex.ReleaseMutex();
|
||||
}
|
||||
}
|
||||
|
||||
private TwitchWebsocketClient CreateNewClient()
|
||||
@ -74,8 +84,9 @@ namespace TwitchChatTTS.Twitch.Socket
|
||||
private async Task OnDisconnection(TwitchWebsocketClient client)
|
||||
{
|
||||
bool reconnecting = false;
|
||||
lock (_lock)
|
||||
try
|
||||
{
|
||||
_mutex.WaitOne();
|
||||
if (_identified?.UID == client.UID)
|
||||
{
|
||||
_logger.Debug($"Identified Twitch client has disconnected [client: {client.UID}][main: {_identified.UID}][backup: {_backup?.UID}]");
|
||||
@ -92,19 +103,25 @@ namespace TwitchChatTTS.Twitch.Socket
|
||||
else
|
||||
_logger.Warning($"Twitch client disconnected from unknown source [client: {client.UID}][main: {_identified?.UID}][backup: {_backup?.UID}]");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_mutex.ReleaseMutex();
|
||||
}
|
||||
|
||||
if (reconnecting)
|
||||
{
|
||||
var newClient = GetWorkingClient();
|
||||
await newClient.Reconnect();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private async Task OnIdentified(TwitchWebsocketClient client)
|
||||
{
|
||||
bool clientDisconnect = false;
|
||||
lock (_lock)
|
||||
try
|
||||
{
|
||||
_mutex.WaitOne();
|
||||
if (_identified == null || _identified.ReceivedReconnecting)
|
||||
{
|
||||
if (_backup != null && _backup.UID == client.UID)
|
||||
@ -125,10 +142,14 @@ namespace TwitchChatTTS.Twitch.Socket
|
||||
_logger.Warning($"Twitch client has been identified, but isn't main or backup [client: {client.UID}][main: {_identified.UID}][backup: {_backup?.UID}]");
|
||||
clientDisconnect = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (clientDisconnect)
|
||||
await client.DisconnectAsync(new SocketDisconnectionEventArgs("Closed", "No need for a tertiary client."));
|
||||
if (clientDisconnect)
|
||||
client.DisconnectAsync(new SocketDisconnectionEventArgs("Closed", "No need for a tertiary client.")).Wait();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_mutex.ReleaseMutex();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -17,15 +17,11 @@ namespace TwitchChatTTS.Twitch.Socket
|
||||
private readonly IDictionary<string, string> _subscriptions;
|
||||
private readonly IBackoff _backoff;
|
||||
private readonly Configuration _configuration;
|
||||
private bool _disconnected;
|
||||
private readonly object _lock;
|
||||
|
||||
public event EventHandler<EventArgs>? OnIdentified;
|
||||
|
||||
public string UID { get; }
|
||||
public string URL;
|
||||
public bool Connected { get; private set; }
|
||||
public bool Identified { get; private set; }
|
||||
public string? SessionId { get; private set; }
|
||||
public bool ReceivedReconnecting { get; set; }
|
||||
public bool TwitchReconnected { get; set; }
|
||||
@ -46,13 +42,14 @@ namespace TwitchChatTTS.Twitch.Socket
|
||||
_backoff = backoff;
|
||||
_configuration = configuration;
|
||||
_subscriptions = new Dictionary<string, string>();
|
||||
_lock = new object();
|
||||
|
||||
_messageTypes = new Dictionary<string, Type>();
|
||||
_messageTypes.Add("session_keepalive", typeof(object));
|
||||
_messageTypes.Add("session_welcome", typeof(SessionWelcomeMessage));
|
||||
_messageTypes.Add("session_reconnect", typeof(SessionWelcomeMessage));
|
||||
_messageTypes.Add("notification", typeof(NotificationMessage));
|
||||
_messageTypes = new Dictionary<string, Type>
|
||||
{
|
||||
{ "session_keepalive", typeof(object) },
|
||||
{ "session_welcome", typeof(SessionWelcomeMessage) },
|
||||
{ "session_reconnect", typeof(SessionWelcomeMessage) },
|
||||
{ "notification", typeof(NotificationMessage) }
|
||||
};
|
||||
|
||||
UID = Guid.NewGuid().ToString("D");
|
||||
|
||||
@ -88,25 +85,12 @@ namespace TwitchChatTTS.Twitch.Socket
|
||||
_logger.Information($"Initializing Twitch websocket client.");
|
||||
OnConnected += (sender, e) =>
|
||||
{
|
||||
Connected = true;
|
||||
_logger.Information("Twitch websocket client connected.");
|
||||
_disconnected = false;
|
||||
};
|
||||
|
||||
OnDisconnected += (sender, e) =>
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_disconnected)
|
||||
return;
|
||||
|
||||
_disconnected = true;
|
||||
}
|
||||
|
||||
_logger.Information($"Twitch websocket client disconnected [status: {e.Status}][reason: {e.Reason}][client: {UID}]");
|
||||
|
||||
Connected = false;
|
||||
Identified = false;
|
||||
};
|
||||
}
|
||||
|
||||
@ -126,7 +110,6 @@ namespace TwitchChatTTS.Twitch.Socket
|
||||
|
||||
public void Identify(string sessionId)
|
||||
{
|
||||
Identified = true;
|
||||
SessionId = sessionId;
|
||||
OnIdentified?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
Reference in New Issue
Block a user