Compare commits
42 Commits
f47685a17d
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| aa89578297 | |||
| fb04f4003f | |||
| eddd9e6403 | |||
| 622b359b12 | |||
| cbdca1c008 | |||
| 86fc6bc24d | |||
| c4e651ff7f | |||
| 6e6f20b097 | |||
| 03d24b0905 | |||
| 48ac5c4fa0 | |||
| d13cd71ac0 | |||
| 5067ffe119 | |||
| 9a17ad16b3 | |||
| c21890b55d | |||
| 3b24208acc | |||
| c373af5281 | |||
| 9f884f71ae | |||
| a49e52a6bb | |||
| aed0421843 | |||
| 5e33d594d2 | |||
| b8d0e8cfd8 | |||
| f3d7c33b83 | |||
| b8de9532e2 | |||
| 5fc1b5f942 | |||
| b74b1d70f3 | |||
| 86590f1c7f | |||
| 4099322ce2 | |||
| 75fa154546 | |||
| b724cd00eb | |||
| 64cb0c1f6d | |||
| 77b37f04b6 | |||
| d74b132c0f | |||
| 4f5dd8f24e | |||
| db1d57c218 | |||
| 850c09cfff | |||
| ea0550e99f | |||
| 893cd6f192 | |||
| b35183249b | |||
| 48dd6858a1 | |||
| 0932c1c38e | |||
| 66f2bf7ec6 | |||
| fe2eb86a08 |
10
Bus/Data/RedemptionInitiation.cs
Normal file
10
Bus/Data/RedemptionInitiation.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using HermesSocketLibrary.Requests.Messages;
|
||||
|
||||
namespace TwitchChatTTS.Bus.Data
|
||||
{
|
||||
public class RedemptionInitiation
|
||||
{
|
||||
public required IEnumerable<Redemption> Redemptions { get; set; }
|
||||
public required IDictionary<string, RedeemableAction> Actions { get; set; }
|
||||
}
|
||||
}
|
||||
83
Bus/ServiceBusCentral.cs
Normal file
83
Bus/ServiceBusCentral.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using System.Collections.Immutable;
|
||||
using Serilog;
|
||||
|
||||
namespace TwitchChatTTS.Bus
|
||||
{
|
||||
public class ServiceBusCentral
|
||||
{
|
||||
private readonly IDictionary<string, ServiceBusObservable> _topics;
|
||||
private readonly IDictionary<string, ISet<IObserver<ServiceBusData>>> _receivers;
|
||||
private readonly ILogger _logger;
|
||||
private readonly object _lock;
|
||||
|
||||
public ServiceBusCentral(ILogger logger)
|
||||
{
|
||||
_topics = new Dictionary<string, ServiceBusObservable>();
|
||||
_receivers = new Dictionary<string, ISet<IObserver<ServiceBusData>>>();
|
||||
_logger = logger;
|
||||
_lock = new object();
|
||||
}
|
||||
|
||||
public void Add(string topic, IObserver<ServiceBusData> observer)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (!_receivers.TryGetValue(topic, out var observers))
|
||||
{
|
||||
observers = new HashSet<IObserver<ServiceBusData>>();
|
||||
_receivers.Add(topic, observers);
|
||||
}
|
||||
observers.Add(observer);
|
||||
}
|
||||
}
|
||||
|
||||
public ServiceBusObservable GetTopic(string topic)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (!_topics.TryGetValue(topic, out var bus))
|
||||
{
|
||||
bus = new ServiceBusObservable(topic, this, _logger);
|
||||
_topics.Add(topic, bus);
|
||||
}
|
||||
return bus;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IObserver<ServiceBusData>> GetObservers(string topic)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_receivers.TryGetValue(topic, out var observers))
|
||||
return observers.ToImmutableArray();
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
public bool RemoveObserver(string topic, IObserver<ServiceBusData> observer)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_receivers.TryGetValue(topic, out var observers))
|
||||
return observers.Remove(observer);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Send(object sender, string topic, object value)
|
||||
{
|
||||
var observers = GetObservers(topic);
|
||||
foreach (var consumer in observers)
|
||||
{
|
||||
try
|
||||
{
|
||||
consumer.OnNext(new ServiceBusData(sender, topic, value));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Failed to execute observer on send.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Bus/ServiceBusData.cs
Normal file
18
Bus/ServiceBusData.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace TwitchChatTTS.Bus
|
||||
{
|
||||
public class ServiceBusData
|
||||
{
|
||||
public string Topic { get; }
|
||||
public object? Sender { get; }
|
||||
public object? Value { get; }
|
||||
public DateTime Timestamp { get; }
|
||||
|
||||
public ServiceBusData(object sender, string topic, object value)
|
||||
{
|
||||
Topic = topic;
|
||||
Sender = sender;
|
||||
Value = value;
|
||||
Timestamp = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
48
Bus/ServiceBusObservable.cs
Normal file
48
Bus/ServiceBusObservable.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System.Reactive;
|
||||
using Serilog;
|
||||
|
||||
namespace TwitchChatTTS.Bus
|
||||
{
|
||||
public class ServiceBusObservable : ObservableBase<ServiceBusData>
|
||||
{
|
||||
private readonly string _topic;
|
||||
private readonly ServiceBusCentral _central;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public ServiceBusObservable(string topic, ServiceBusCentral central, ILogger logger)
|
||||
{
|
||||
_topic = topic;
|
||||
_central = central;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
protected override IDisposable SubscribeCore(IObserver<ServiceBusData> observer)
|
||||
{
|
||||
_central.Add(_topic, observer);
|
||||
return new ServiceBusUnsubscriber(_topic, _central, observer);
|
||||
}
|
||||
|
||||
public IDisposable Subscribe(Action<ServiceBusData> action) {
|
||||
return Subscribe(new ServiceBusObserver(action, _logger));
|
||||
}
|
||||
|
||||
private sealed class ServiceBusUnsubscriber : IDisposable
|
||||
{
|
||||
private readonly string _topic;
|
||||
private readonly ServiceBusCentral _central;
|
||||
private readonly IObserver<ServiceBusData> _receiver;
|
||||
|
||||
public ServiceBusUnsubscriber(string topic, ServiceBusCentral central, IObserver<ServiceBusData> receiver)
|
||||
{
|
||||
_topic = topic;
|
||||
_central = central;
|
||||
_receiver = receiver;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_central.RemoveObserver(_topic, _receiver);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
31
Bus/ServiceBusObserver.cs
Normal file
31
Bus/ServiceBusObserver.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Reactive;
|
||||
using Serilog;
|
||||
|
||||
namespace TwitchChatTTS.Bus
|
||||
{
|
||||
public class ServiceBusObserver : ObserverBase<ServiceBusData>
|
||||
{
|
||||
private readonly Action<ServiceBusData> _action;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public ServiceBusObserver(Action<ServiceBusData> action, ILogger logger)
|
||||
{
|
||||
_action = action;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
protected override void OnCompletedCore()
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnErrorCore(Exception error)
|
||||
{
|
||||
_logger.Error(error, "Error occurred.");
|
||||
}
|
||||
|
||||
protected override void OnNextCore(ServiceBusData value)
|
||||
{
|
||||
_action.Invoke(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,9 +14,8 @@ namespace TwitchChatTTS.Chat.Commands
|
||||
public class CommandManager : ICommandManager
|
||||
{
|
||||
private readonly User _user;
|
||||
private ICommandSelector _commandSelector;
|
||||
private ICommandSelector? _commandSelector;
|
||||
private readonly HermesSocketClient _hermes;
|
||||
//private readonly TwitchWebsocketClient _twitch;
|
||||
private readonly IGroupPermissionManager _permissionManager;
|
||||
private readonly IUsagePolicy<long> _permissionPolicy;
|
||||
private readonly ILogger _logger;
|
||||
@@ -26,7 +25,6 @@ namespace TwitchChatTTS.Chat.Commands
|
||||
public CommandManager(
|
||||
User user,
|
||||
[FromKeyedServices("hermes")] SocketClient<WebSocketMessage> hermes,
|
||||
//[FromKeyedServices("twitch")] SocketClient<TwitchWebsocketMessage> twitch,
|
||||
IGroupPermissionManager permissionManager,
|
||||
IUsagePolicy<long> limitManager,
|
||||
ILogger logger
|
||||
@@ -34,7 +32,6 @@ namespace TwitchChatTTS.Chat.Commands
|
||||
{
|
||||
_user = user;
|
||||
_hermes = (hermes as HermesSocketClient)!;
|
||||
//_twitch = (twitch as TwitchWebsocketClient)!;
|
||||
_permissionManager = permissionManager;
|
||||
_permissionPolicy = limitManager;
|
||||
_logger = logger;
|
||||
@@ -43,7 +40,7 @@ namespace TwitchChatTTS.Chat.Commands
|
||||
|
||||
public async Task<ChatCommandResult> Execute(string arg, ChannelChatMessage message, IEnumerable<string> groups)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(arg))
|
||||
if (string.IsNullOrWhiteSpace(arg) || _commandSelector == null)
|
||||
return ChatCommandResult.Unknown;
|
||||
|
||||
arg = arg.Trim();
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace TwitchChatTTS.Chat.Commands.Limits
|
||||
public UsagePolicy(ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_root = new UsagePolicyNode<K>(string.Empty, null, null, logger);
|
||||
_root = new UsagePolicyNode<K>(string.Empty, null, null, logger, root: true);
|
||||
}
|
||||
|
||||
|
||||
@@ -102,111 +102,162 @@ 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)
|
||||
public UsagePolicyNode(string name, UsagePolicyLimit? data, UsagePolicyNode<T>? parent, ILogger logger, bool root = false)
|
||||
{
|
||||
//ArgumentException.ThrowIfNullOrWhiteSpace(name, nameof(name));
|
||||
if (!root)
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(name, nameof(name));
|
||||
Name = name;
|
||||
Limit = data;
|
||||
_parent = parent;
|
||||
_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.ExitUpgradeableReadLock();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -107,7 +107,7 @@ namespace TwitchChatTTS.Chat.Commands
|
||||
_logger.Information("Cleared Nightbot queue.");
|
||||
}
|
||||
}
|
||||
catch (HttpRequestException e)
|
||||
catch (HttpRequestException)
|
||||
{
|
||||
_logger.Warning("Ensure your Nightbot account is linked to your TTS account.");
|
||||
}
|
||||
|
||||
@@ -104,10 +104,11 @@ namespace TwitchChatTTS.Chat.Commands
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient hermes)
|
||||
public Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient hermes)
|
||||
{
|
||||
_obsManager.ClearCache();
|
||||
_logger.Information("Cleared the cache used for OBS.");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,15 +52,16 @@ namespace TwitchChatTTS.Chat.Commands
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient hermes)
|
||||
public Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient hermes)
|
||||
{
|
||||
if (_player.Playing == null)
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
|
||||
_playback.RemoveMixerInput(_player.Playing.Audio!);
|
||||
_player.Playing = null;
|
||||
|
||||
_logger.Information("Skipped current tts.");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,17 +80,18 @@ namespace TwitchChatTTS.Chat.Commands
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient hermes)
|
||||
public Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient hermes)
|
||||
{
|
||||
_player.RemoveAll();
|
||||
|
||||
if (_player.Playing == null)
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
|
||||
_playback.RemoveMixerInput(_player.Playing.Audio!);
|
||||
_player.Playing = null;
|
||||
|
||||
_logger.Information("Skipped all queued and playing tts.");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
@@ -36,20 +35,20 @@ namespace TwitchChatTTS.Chat.Emotes
|
||||
|
||||
public class EmoteSet
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public required string Id { get; set; }
|
||||
public required string Name { get; set; }
|
||||
public int Flags { get; set; }
|
||||
public bool Immutable { get; set; }
|
||||
public bool Privileged { get; set; }
|
||||
public IList<Emote> Emotes { get; set; }
|
||||
public required IList<Emote> Emotes { get; set; }
|
||||
public int EmoteCount { get; set; }
|
||||
public int Capacity { get; set; }
|
||||
}
|
||||
|
||||
public class Emote
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public required string Id { get; set; }
|
||||
public required string Name { get; set; }
|
||||
public int Flags { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using HermesSocketLibrary.Requests.Messages;
|
||||
using Serilog;
|
||||
|
||||
@@ -9,84 +7,199 @@ namespace TwitchChatTTS.Chat.Groups
|
||||
{
|
||||
private readonly IDictionary<string, Group> _groups;
|
||||
private readonly IDictionary<long, ICollection<string>> _chatters;
|
||||
private readonly ReaderWriterLockSlim _rwls;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
|
||||
public ChatterGroupManager(ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_groups = new ConcurrentDictionary<string, Group>();
|
||||
_chatters = new ConcurrentDictionary<long, ICollection<string>>();
|
||||
_groups = new Dictionary<string, Group>();
|
||||
_chatters = new Dictionary<long, ICollection<string>>();
|
||||
_rwls = new ReaderWriterLockSlim();
|
||||
}
|
||||
|
||||
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))
|
||||
_rwls.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
foreach (var group in groupNames)
|
||||
list.Add(group);
|
||||
_groups.Add(group.Id, group);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(long chatterId, string groupId)
|
||||
{
|
||||
_rwls.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
if (_chatters.TryGetValue(chatterId, out var list))
|
||||
{
|
||||
if (!list.Contains(groupId))
|
||||
list.Add(groupId);
|
||||
}
|
||||
else
|
||||
_chatters.Add(chatterId, new List<string>() { groupId });
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(long chatter, ICollection<string> groupIds)
|
||||
{
|
||||
_rwls.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
if (_chatters.TryGetValue(chatter, out var list))
|
||||
{
|
||||
foreach (var groupId in groupIds)
|
||||
if (!list.Contains(groupId))
|
||||
list.Add(groupId);
|
||||
}
|
||||
else
|
||||
_chatters.Add(chatter, groupIds);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitWriteLock();
|
||||
}
|
||||
else
|
||||
_chatters.Add(chatter, groupNames);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_groups.Clear();
|
||||
_chatters.Clear();
|
||||
_rwls.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
_groups.Clear();
|
||||
_chatters.Clear();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public Group? Get(string groupName)
|
||||
public Group? Get(string groupId)
|
||||
{
|
||||
if (_groups.TryGetValue(groupName, out var group))
|
||||
return group;
|
||||
return null;
|
||||
_rwls.EnterReadLock();
|
||||
try
|
||||
{
|
||||
if (_groups.TryGetValue(groupId, out var group))
|
||||
return group;
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetGroupNamesFor(long chatter)
|
||||
{
|
||||
if (_chatters.TryGetValue(chatter, out var groups))
|
||||
return groups.Select(g => _groups[g].Name);
|
||||
_rwls.EnterReadLock();
|
||||
try
|
||||
{
|
||||
if (_chatters.TryGetValue(chatter, out var groups))
|
||||
return groups.Select(g => _groups.TryGetValue(g, out var group) ? group.Name : null)
|
||||
.Where(g => g != null)
|
||||
.Cast<string>();
|
||||
|
||||
return Array.Empty<string>();
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
public int GetPriorityFor(long chatter)
|
||||
{
|
||||
if (!_chatters.TryGetValue(chatter, out var groups))
|
||||
return 0;
|
||||
_rwls.EnterReadLock();
|
||||
try
|
||||
{
|
||||
if (!_chatters.TryGetValue(chatter, out var groups))
|
||||
return 0;
|
||||
|
||||
return GetPriorityFor(groups);
|
||||
return GetPriorityFor(groups);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
public int GetPriorityFor(IEnumerable<string> groupNames)
|
||||
public int GetPriorityFor(IEnumerable<string> groupIds)
|
||||
{
|
||||
var values = groupNames.Select(g => _groups.TryGetValue(g, out var group) ? group : null).Where(g => g != null);
|
||||
if (values.Any())
|
||||
return values.Max(g => g.Priority);
|
||||
return 0;
|
||||
_rwls.EnterReadLock();
|
||||
try
|
||||
{
|
||||
var values = groupIds.Select(g => _groups.TryGetValue(g, out var group) ? group : null).Where(g => g != null);
|
||||
if (values.Any())
|
||||
return values.Max(g => g!.Priority);
|
||||
return 0;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
public void Modify(Group group)
|
||||
{
|
||||
_rwls.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
_groups[group.Id] = group;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(string groupId)
|
||||
{
|
||||
_rwls.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
if (_groups.Remove(groupId))
|
||||
{
|
||||
foreach (var entry in _chatters)
|
||||
entry.Value.Remove(groupId);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(long chatterId, string groupId)
|
||||
{
|
||||
if (_chatters.TryGetValue(chatterId, out var groups))
|
||||
_rwls.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
groups.Remove(groupId);
|
||||
_logger.Debug($"Removed chatter from group [chatter id: {chatterId}][group name: {_groups[groupId]}][group id: {groupId}]");
|
||||
return true;
|
||||
if (_chatters.TryGetValue(chatterId, out var groups))
|
||||
{
|
||||
groups.Remove(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].Name}][group id: {groupId}]");
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitReadLock();
|
||||
}
|
||||
_logger.Debug($"Failed to remove chatter from group [chatter id: {chatterId}][group name: {_groups[groupId]}][group id: {groupId}]");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ namespace TwitchChatTTS.Chat.Groups
|
||||
IEnumerable<string> GetGroupNamesFor(long chatter);
|
||||
int GetPriorityFor(long chatter);
|
||||
int GetPriorityFor(IEnumerable<string> groupIds);
|
||||
void Modify(Group group);
|
||||
bool Remove(string groupId);
|
||||
bool Remove(long chatter, string groupId);
|
||||
}
|
||||
}
|
||||
@@ -5,33 +5,51 @@ namespace TwitchChatTTS.Chat.Groups.Permissions
|
||||
{
|
||||
public class GroupPermissionManager : IGroupPermissionManager
|
||||
{
|
||||
private PermissionNode _root;
|
||||
private ILogger _logger;
|
||||
private readonly PermissionNode _root;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ReaderWriterLockSlim _rwls;
|
||||
|
||||
|
||||
public GroupPermissionManager(ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_root = new PermissionNode(string.Empty, null, null);
|
||||
_rwls = new ReaderWriterLockSlim();
|
||||
}
|
||||
|
||||
|
||||
public bool? CheckIfAllowed(string path)
|
||||
{
|
||||
var res = Get(path)!.Allow;
|
||||
_logger.Debug($"Permission Node GET {path} = {res?.ToString() ?? "null"}");
|
||||
return res;
|
||||
_rwls.EnterReadLock();
|
||||
try
|
||||
{
|
||||
var res = Get(path)!.Allow;
|
||||
_logger.Debug($"Permission Node GET {path} = {res?.ToString() ?? "null"}");
|
||||
return res;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
public bool? CheckIfDirectAllowed(string path)
|
||||
{
|
||||
var node = Get(path, nullIfMissing: true);
|
||||
if (node == null)
|
||||
return null;
|
||||
_rwls.EnterReadLock();
|
||||
try
|
||||
{
|
||||
var node = Get(path, nullIfMissing: true);
|
||||
if (node == null)
|
||||
return null;
|
||||
|
||||
var res = node.DirectAllow;
|
||||
_logger.Debug($"Permission Node GET {path} = {res?.ToString() ?? "null"} [direct]");
|
||||
return res;
|
||||
var res = node.DirectAllow;
|
||||
_logger.Debug($"Permission Node GET {path} = {res?.ToString() ?? "null"} [direct]");
|
||||
return res;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
public bool? CheckIfAllowed(IEnumerable<string> groups, string path)
|
||||
@@ -64,31 +82,63 @@ namespace TwitchChatTTS.Chat.Groups.Permissions
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_root.Clear();
|
||||
_rwls.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
_root.Clear();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
_rwls.EnterUpgradeableReadLock();
|
||||
try
|
||||
{
|
||||
node.Parent.Remove(last);
|
||||
_logger.Debug($"Permission Node REMOVE priv {path}");
|
||||
return true;
|
||||
var node = Get(path);
|
||||
if (node == null || node.Parent == null)
|
||||
return false;
|
||||
|
||||
_rwls.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
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;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitUpgradeableReadLock();
|
||||
}
|
||||
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"}");
|
||||
_rwls.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
var node = Get(path, true);
|
||||
node!.Allow = allow;
|
||||
_logger.Debug($"Permission Node ADD {path} = {allow?.ToString() ?? "null"}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
private PermissionNode? Get(string path, bool edit = false, bool nullIfMissing = false)
|
||||
|
||||
@@ -62,15 +62,15 @@ namespace TwitchChatTTS.Chat.Messaging
|
||||
|
||||
var emoteUsage = GetEmoteUsage(fragments);
|
||||
var tasks = new List<Task>();
|
||||
if (_obs.Streaming)
|
||||
if ((!_obs.Connected || _obs.Streaming) && !_user.Slave)
|
||||
{
|
||||
if (emoteUsage.NewEmotes.Any())
|
||||
tasks.Add(_hermes.SendEmoteDetails(emoteUsage.NewEmotes));
|
||||
if (emoteUsage.EmotesUsed.Any() && messageId != null && chatterId != null)
|
||||
tasks.Add(_hermes.SendEmoteUsage(messageId, chatterId.Value, emoteUsage.EmotesUsed));
|
||||
if (!string.IsNullOrEmpty(chatterLogin) && chatterId != null && !_user.Chatters.Contains(chatterId.Value))
|
||||
if (chatterId.HasValue && !_user.Chatters.Contains(chatterId.Value))
|
||||
{
|
||||
tasks.Add(_hermes.SendChatterDetails(chatterId.Value, chatterLogin));
|
||||
tasks.Add(_hermes.SendChatterDetails(chatterId.Value, chatterLogin!));
|
||||
_user.Chatters.Add(chatterId.Value);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -5,24 +5,17 @@ namespace TwitchChatTTS.Chat.Observers
|
||||
public class TTSPublisher : IObservable<TTSGroupedMessage>
|
||||
{
|
||||
private readonly HashSet<IObserver<TTSGroupedMessage>> _observers;
|
||||
private readonly HashSet<TTSGroupedMessage> _messages;
|
||||
|
||||
|
||||
public TTSPublisher()
|
||||
{
|
||||
_observers = new();
|
||||
_messages = new();
|
||||
}
|
||||
|
||||
|
||||
public IDisposable Subscribe(IObserver<TTSGroupedMessage> observer)
|
||||
{
|
||||
if (_observers.Add(observer))
|
||||
{
|
||||
foreach (var item in _messages)
|
||||
observer.OnNext(item);
|
||||
}
|
||||
|
||||
_observers.Add(observer);
|
||||
return new Unsubscriber<TTSGroupedMessage>(_observers, observer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ namespace TwitchChatTTS
|
||||
}
|
||||
|
||||
public class TwitchConfiguration {
|
||||
public IEnumerable<string>? Channels;
|
||||
public bool TtsWhenOffline;
|
||||
public string? WebsocketUrl;
|
||||
public string? ApiUrl;
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
public class Account {
|
||||
public string Id { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string Role { get; set; }
|
||||
public string BroadcasterId { get; set; }
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
namespace TwitchChatTTS.Hermes
|
||||
{
|
||||
public interface ICustomDataManager
|
||||
{
|
||||
void Add(string key, object value, string type);
|
||||
void Change(string key, object value);
|
||||
void Delete(string key);
|
||||
object? Get(string key);
|
||||
}
|
||||
|
||||
public class CustomDataManager : ICustomDataManager
|
||||
{
|
||||
private IDictionary<string, DataInfo> _data;
|
||||
|
||||
public CustomDataManager()
|
||||
{
|
||||
_data = new Dictionary<string, DataInfo>();
|
||||
}
|
||||
|
||||
|
||||
public void Add(string key, object value, string type)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Change(string key, object value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Delete(string key)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public object? Get(string key)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
// type: text (string), whole number (int), number (double), boolean, formula (string, data type of number)
|
||||
public struct DataInfo
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Type { get; set; }
|
||||
public object Value { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -31,15 +31,4 @@ public class HermesApiClient
|
||||
{
|
||||
return await _web.GetJson<TTSVersion>($"https://{BASE_URL}/api/info/version");
|
||||
}
|
||||
|
||||
public async Task<Account> FetchHermesAccountDetails()
|
||||
{
|
||||
var account = await _web.GetJson<Account>($"https://{BASE_URL}/api/account", new JsonSerializerOptions()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
});
|
||||
if (account == null || account.Id == null || account.Username == null)
|
||||
throw new NullReferenceException("Invalid value found while fetching for hermes account data.");
|
||||
return account;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text.RegularExpressions;
|
||||
using CommonSocketLibrary.Abstract;
|
||||
using CommonSocketLibrary.Common;
|
||||
using HermesSocketLibrary.Socket.Data;
|
||||
using Serilog;
|
||||
using TwitchChatTTS.Bus;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Handlers
|
||||
{
|
||||
@@ -10,13 +12,15 @@ namespace TwitchChatTTS.Hermes.Socket.Handlers
|
||||
{
|
||||
private readonly User _user;
|
||||
private readonly NightbotApiClient _nightbot;
|
||||
private readonly ServiceBusCentral _bus;
|
||||
private readonly ILogger _logger;
|
||||
public int OperationCode { get; } = 2;
|
||||
|
||||
public LoginAckHandler(User user, NightbotApiClient nightbot, ILogger logger)
|
||||
public LoginAckHandler(User user, NightbotApiClient nightbot, ServiceBusCentral bus, ILogger logger)
|
||||
{
|
||||
_user = user;
|
||||
_nightbot = nightbot;
|
||||
_bus = bus;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -29,8 +33,7 @@ namespace TwitchChatTTS.Hermes.Socket.Handlers
|
||||
|
||||
if (message.AnotherClient)
|
||||
{
|
||||
if (client.LoggedIn)
|
||||
_logger.Warning($"Another client has connected to the same account via {(message.WebLogin ? "web login" : "application")}.");
|
||||
_logger.Warning($"Another client has connected to the same account via {(message.WebLogin ? "web login" : "application")}.");
|
||||
return;
|
||||
}
|
||||
if (client.LoggedIn)
|
||||
@@ -39,24 +42,38 @@ namespace TwitchChatTTS.Hermes.Socket.Handlers
|
||||
return;
|
||||
}
|
||||
|
||||
_user.HermesUserId = message.UserId;
|
||||
_user.OwnerId = message.OwnerId;
|
||||
_user.DefaultTTSVoice = message.DefaultTTSVoice;
|
||||
_user.VoicesAvailable = 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");
|
||||
_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.");
|
||||
|
||||
var filters = message.WordFilters.Where(f => f.Search != null && f.Replace != null).ToArray();
|
||||
_user.HermesUserId = message.UserId;
|
||||
_user.HermesUsername = message.UserName;
|
||||
_user.TwitchUsername = message.UserName;
|
||||
_user.TwitchUserId = long.Parse(message.ProviderAccountId);
|
||||
_user.OwnerId = message.OwnerId;
|
||||
_user.StreamElementsOverlayKey = message.StreamElementsOverlayKey;
|
||||
_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") ?? 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.Debug("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.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;
|
||||
|
||||
@@ -85,6 +102,8 @@ namespace TwitchChatTTS.Hermes.Socket.Handlers
|
||||
|
||||
_logger.Information("TTS is now ready.");
|
||||
client.Ready = true;
|
||||
|
||||
_bus.Send(this, "tts_connected", _user);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,450 +1,52 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using CommonSocketLibrary.Abstract;
|
||||
using CommonSocketLibrary.Common;
|
||||
using HermesSocketLibrary.Requests.Callbacks;
|
||||
using HermesSocketLibrary.Requests.Messages;
|
||||
using HermesSocketLibrary.Socket.Data;
|
||||
using HermesSocketServer.Models;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Serilog;
|
||||
using TwitchChatTTS.Chat.Commands.Limits;
|
||||
using TwitchChatTTS.Chat.Emotes;
|
||||
using TwitchChatTTS.Chat.Groups;
|
||||
using TwitchChatTTS.Chat.Groups.Permissions;
|
||||
using TwitchChatTTS.Twitch.Redemptions;
|
||||
using TwitchChatTTS.Hermes.Socket.Requests;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Handlers
|
||||
{
|
||||
public class RequestAckHandler : IWebSocketHandler
|
||||
{
|
||||
private User _user;
|
||||
private readonly ICallbackManager<HermesRequestData> _callbackManager;
|
||||
private readonly IChatterGroupManager _groups;
|
||||
private readonly IUsagePolicy<long> _policies;
|
||||
private readonly TwitchApiClient _twitch;
|
||||
private readonly NightbotApiClient _nightbot;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly JsonSerializerOptions _options;
|
||||
private readonly RequestAckManager _manager;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private readonly object _voicesAvailableLock = new object();
|
||||
|
||||
public int OperationCode { get; } = 4;
|
||||
|
||||
|
||||
public RequestAckHandler(
|
||||
ICallbackManager<HermesRequestData> callbackManager,
|
||||
IChatterGroupManager groups,
|
||||
IUsagePolicy<long> policies,
|
||||
TwitchApiClient twitch,
|
||||
NightbotApiClient nightbot,
|
||||
IServiceProvider serviceProvider,
|
||||
User user,
|
||||
JsonSerializerOptions options,
|
||||
RequestAckManager manager,
|
||||
ILogger logger
|
||||
)
|
||||
{
|
||||
_callbackManager = callbackManager;
|
||||
_groups = groups;
|
||||
_policies = policies;
|
||||
_twitch = twitch;
|
||||
_nightbot = nightbot;
|
||||
_serviceProvider = serviceProvider;
|
||||
_user = user;
|
||||
_options = options;
|
||||
_manager = manager;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task Execute<Data>(SocketClient<WebSocketMessage> sender, Data data)
|
||||
public Task Execute<Data>(SocketClient<WebSocketMessage> sender, Data data)
|
||||
{
|
||||
if (data is not RequestAckMessage message || message == null)
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
if (message.Request == null)
|
||||
{
|
||||
_logger.Warning("Received a Hermes request message without a proper request.");
|
||||
return;
|
||||
}
|
||||
|
||||
HermesRequestData? hermesRequestData = null;
|
||||
if (!string.IsNullOrEmpty(message.Request.RequestId))
|
||||
{
|
||||
hermesRequestData = _callbackManager.Take(message.Request.RequestId);
|
||||
if (hermesRequestData == null)
|
||||
_logger.Warning($"Could not find callback for request [request id: {message.Request.RequestId}][type: {message.Request.Type}]");
|
||||
else if (hermesRequestData.Data == null)
|
||||
hermesRequestData.Data = new Dictionary<string, object>();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
_logger.Debug($"Received a Hermes request message [type: {message.Request.Type}][data: {string.Join(',', message.Request.Data?.Select(entry => entry.Key + '=' + entry.Value) ?? Array.Empty<string>())}]");
|
||||
if (message.Request.Type == "get_tts_voices")
|
||||
var json = message.Data?.ToString();
|
||||
if (message.Request.Type == null)
|
||||
{
|
||||
var voices = JsonSerializer.Deserialize<IEnumerable<VoiceDetails>>(message.Data.ToString(), _options);
|
||||
if (voices == null)
|
||||
return;
|
||||
|
||||
lock (_voicesAvailableLock)
|
||||
{
|
||||
_user.VoicesAvailable = voices.ToDictionary(e => e.Id, e => e.Name);
|
||||
}
|
||||
_logger.Information("Updated all available voices for TTS.");
|
||||
_logger.Warning("Request type is null. Unknown what acknowlegement this is for.");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
else if (message.Request.Type == "create_tts_user")
|
||||
if (!string.IsNullOrWhiteSpace(message.Error))
|
||||
{
|
||||
if (!long.TryParse(message.Request.Data["chatter"].ToString(), out long chatterId))
|
||||
{
|
||||
_logger.Warning($"Failed to parse chatter id [chatter id: {message.Request.Data["chatter"]}]");
|
||||
return;
|
||||
}
|
||||
string userId = message.Request.Data["user"].ToString();
|
||||
string voiceId = message.Request.Data["voice"].ToString();
|
||||
|
||||
_user.VoicesSelected.Add(chatterId, voiceId);
|
||||
_logger.Information($"Added new TTS voice [voice: {voiceId}] for user [user id: {userId}]");
|
||||
}
|
||||
else if (message.Request.Type == "update_tts_user")
|
||||
{
|
||||
if (!long.TryParse(message.Request.Data["chatter"].ToString(), out long chatterId))
|
||||
{
|
||||
_logger.Warning($"Failed to parse chatter id [chatter id: {message.Request.Data["chatter"]}]");
|
||||
return;
|
||||
}
|
||||
string userId = message.Request.Data["user"].ToString();
|
||||
string voiceId = message.Request.Data["voice"].ToString();
|
||||
|
||||
_user.VoicesSelected[chatterId] = voiceId;
|
||||
_logger.Information($"Updated TTS voice [voice: {voiceId}] for user [user id: {userId}]");
|
||||
}
|
||||
else if (message.Request.Type == "create_tts_voice")
|
||||
{
|
||||
string? voice = message.Request.Data["voice"].ToString();
|
||||
string? voiceId = message.Data.ToString();
|
||||
if (voice == null || voiceId == null)
|
||||
return;
|
||||
|
||||
lock (_voicesAvailableLock)
|
||||
{
|
||||
var list = _user.VoicesAvailable.ToDictionary(k => k.Key, v => v.Value);
|
||||
list.Add(voiceId, voice);
|
||||
_user.VoicesAvailable = list;
|
||||
}
|
||||
_logger.Information($"Created new tts voice [voice: {voice}][id: {voiceId}].");
|
||||
}
|
||||
else if (message.Request.Type == "delete_tts_voice")
|
||||
{
|
||||
var voice = message.Request.Data["voice"].ToString();
|
||||
if (!_user.VoicesAvailable.TryGetValue(voice, out string? voiceName) || voiceName == null)
|
||||
return;
|
||||
|
||||
lock (_voicesAvailableLock)
|
||||
{
|
||||
var dict = _user.VoicesAvailable.ToDictionary(k => k.Key, v => v.Value);
|
||||
dict.Remove(voice);
|
||||
_user.VoicesAvailable.Remove(voice);
|
||||
}
|
||||
_logger.Information($"Deleted a voice [voice: {voiceName}]");
|
||||
}
|
||||
else if (message.Request.Type == "update_tts_voice")
|
||||
{
|
||||
string voiceId = message.Request.Data["idd"].ToString();
|
||||
string voice = message.Request.Data["voice"].ToString();
|
||||
|
||||
if (!_user.VoicesAvailable.TryGetValue(voiceId, out string? voiceName) || voiceName == null)
|
||||
return;
|
||||
|
||||
_user.VoicesAvailable[voiceId] = voice;
|
||||
_logger.Information($"Updated TTS voice [voice: {voice}][id: {voiceId}]");
|
||||
}
|
||||
else if (message.Request.Type == "get_connections")
|
||||
{
|
||||
var connections = JsonSerializer.Deserialize<IEnumerable<Connection>>(message.Data?.ToString(), _options);
|
||||
if (connections == null)
|
||||
{
|
||||
_logger.Error("Null value was given when attempting to fetch connections.");
|
||||
_logger.Debug(message.Data?.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
_user.TwitchConnection = connections.FirstOrDefault(c => c.Type == "twitch" && c.Default);
|
||||
_user.NightbotConnection = connections.FirstOrDefault(c => c.Type == "nightbot" && c.Default);
|
||||
|
||||
if (_user.TwitchConnection != null)
|
||||
_twitch.Initialize(_user.TwitchConnection.ClientId, _user.TwitchConnection.AccessToken);
|
||||
if (_user.NightbotConnection != null)
|
||||
_nightbot.Initialize(_user.NightbotConnection.ClientId, _user.NightbotConnection.AccessToken);
|
||||
|
||||
_logger.Information($"Fetched connections from TTS account [count: {connections.Count()}][twitch: {_user.TwitchConnection != null}][nightbot: {_user.NightbotConnection != null}]");
|
||||
}
|
||||
else if (message.Request.Type == "get_tts_users")
|
||||
{
|
||||
var users = JsonSerializer.Deserialize<IDictionary<long, string>>(message.Data.ToString(), _options);
|
||||
if (users == null)
|
||||
return;
|
||||
|
||||
var temp = new ConcurrentDictionary<long, string>();
|
||||
foreach (var entry in users)
|
||||
temp.TryAdd(entry.Key, entry.Value);
|
||||
_user.VoicesSelected = temp;
|
||||
_logger.Information($"Updated {temp.Count()} chatters' selected voice.");
|
||||
}
|
||||
else if (message.Request.Type == "get_chatter_ids")
|
||||
{
|
||||
var chatters = JsonSerializer.Deserialize<IEnumerable<long>>(message.Data.ToString(), _options);
|
||||
if (chatters == null)
|
||||
return;
|
||||
|
||||
_user.Chatters = [.. chatters];
|
||||
_logger.Information($"Fetched {chatters.Count()} chatters' id.");
|
||||
}
|
||||
else if (message.Request.Type == "get_emotes")
|
||||
{
|
||||
var emotes = JsonSerializer.Deserialize<IEnumerable<EmoteInfo>>(message.Data.ToString(), _options);
|
||||
if (emotes == null)
|
||||
return;
|
||||
|
||||
var emoteDb = _serviceProvider.GetRequiredService<IEmoteDatabase>();
|
||||
var count = 0;
|
||||
var duplicateNames = 0;
|
||||
foreach (var emote in emotes)
|
||||
{
|
||||
if (emoteDb.Get(emote.Name) == null)
|
||||
{
|
||||
emoteDb.Add(emote.Name, emote.Id);
|
||||
count++;
|
||||
}
|
||||
else
|
||||
duplicateNames++;
|
||||
}
|
||||
_logger.Information($"Fetched {count} emotes from various sources.");
|
||||
if (duplicateNames > 0)
|
||||
_logger.Warning($"Found {duplicateNames} emotes with duplicate names.");
|
||||
}
|
||||
else if (message.Request.Type == "get_enabled_tts_voices")
|
||||
{
|
||||
var enabledTTSVoices = JsonSerializer.Deserialize<IEnumerable<string>>(message.Data.ToString(), _options);
|
||||
if (enabledTTSVoices == null)
|
||||
{
|
||||
_logger.Error("Failed to load enabled tts voices.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_user.VoicesEnabled == null)
|
||||
_user.VoicesEnabled = enabledTTSVoices.ToHashSet();
|
||||
else
|
||||
_user.VoicesEnabled.Clear();
|
||||
foreach (var voice in enabledTTSVoices)
|
||||
_user.VoicesEnabled.Add(voice);
|
||||
_logger.Information($"TTS voices [count: {_user.VoicesEnabled.Count}] have been enabled.");
|
||||
}
|
||||
else if (message.Request.Type == "get_permissions")
|
||||
{
|
||||
var groupInfo = JsonSerializer.Deserialize<GroupInfo>(message.Data.ToString(), _options);
|
||||
if (groupInfo == null)
|
||||
{
|
||||
_logger.Error("Failed to load groups & permissions.");
|
||||
return;
|
||||
}
|
||||
|
||||
var chatterGroupManager = _serviceProvider.GetRequiredService<IChatterGroupManager>();
|
||||
var permissionManager = _serviceProvider.GetRequiredService<IGroupPermissionManager>();
|
||||
|
||||
permissionManager.Clear();
|
||||
chatterGroupManager.Clear();
|
||||
|
||||
var groupsById = groupInfo.Groups.ToDictionary(g => g.Id, g => g);
|
||||
foreach (var group in groupInfo.Groups)
|
||||
chatterGroupManager.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))
|
||||
{
|
||||
_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($"Groups [count: {groupInfo.Groups.Count()}] & Permissions [count: {groupInfo.GroupPermissions.Count()}] have been loaded.");
|
||||
|
||||
foreach (var chatter in groupInfo.GroupChatters)
|
||||
if (groupsById.TryGetValue(chatter.GroupId, out var group))
|
||||
chatterGroupManager.Add(chatter.ChatterId, group.Name);
|
||||
_logger.Information($"Users in each group [count: {groupInfo.GroupChatters.Count()}] have been loaded.");
|
||||
}
|
||||
else if (message.Request.Type == "get_tts_word_filters")
|
||||
{
|
||||
var wordFilters = JsonSerializer.Deserialize<IEnumerable<TTSWordFilter>>(message.Data.ToString(), _options);
|
||||
if (wordFilters == null)
|
||||
{
|
||||
_logger.Error("Failed to load word filters.");
|
||||
return;
|
||||
}
|
||||
|
||||
var filters = wordFilters.Where(f => f.Search != null && f.Replace != null).ToArray();
|
||||
foreach (var filter in filters)
|
||||
{
|
||||
try
|
||||
{
|
||||
var re = new Regex(filter.Search!, RegexOptions.Compiled);
|
||||
re.Match(string.Empty);
|
||||
filter.Regex = re;
|
||||
}
|
||||
catch (Exception) { }
|
||||
}
|
||||
_user.RegexFilters = filters;
|
||||
_logger.Information($"TTS word filters [count: {_user.RegexFilters.Count()}] have been refreshed.");
|
||||
}
|
||||
else if (message.Request.Type == "update_tts_voice_state")
|
||||
{
|
||||
string voiceId = message.Request.Data?["voice"].ToString()!;
|
||||
bool state = message.Request.Data?["state"].ToString()!.ToLower() == "true";
|
||||
|
||||
if (!_user.VoicesAvailable.TryGetValue(voiceId, out string? voiceName) || voiceName == null)
|
||||
{
|
||||
_logger.Warning($"Failed to find voice by id [id: {voiceId}]");
|
||||
return;
|
||||
}
|
||||
|
||||
if (state)
|
||||
_user.VoicesEnabled.Add(voiceId);
|
||||
else
|
||||
_user.VoicesEnabled.Remove(voiceId);
|
||||
_logger.Information($"Updated voice state [voice: {voiceName}][new state: {(state ? "enabled" : "disabled")}]");
|
||||
}
|
||||
else if (message.Request.Type == "get_redemptions")
|
||||
{
|
||||
IEnumerable<Redemption>? redemptions = JsonSerializer.Deserialize<IEnumerable<Redemption>>(message.Data!.ToString()!, _options);
|
||||
if (redemptions != null)
|
||||
{
|
||||
_logger.Information($"Redemptions [count: {redemptions.Count()}] loaded.");
|
||||
if (hermesRequestData != null)
|
||||
hermesRequestData.Data!.Add("redemptions", redemptions);
|
||||
}
|
||||
else
|
||||
_logger.Information(message.Data.GetType().ToString());
|
||||
}
|
||||
else if (message.Request.Type == "get_redeemable_actions")
|
||||
{
|
||||
IEnumerable<RedeemableAction>? actions = JsonSerializer.Deserialize<IEnumerable<RedeemableAction>>(message.Data!.ToString()!, _options);
|
||||
if (actions == null)
|
||||
{
|
||||
_logger.Warning("Failed to read the redeemable actions for redemptions.");
|
||||
return;
|
||||
}
|
||||
if (hermesRequestData?.Data == null || hermesRequestData.Data["redemptions"] is not IEnumerable<Redemption> redemptions)
|
||||
{
|
||||
_logger.Warning("Failed to read the redemptions while updating redemption actions.");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Information($"Redeemable actions [count: {actions.Count()}] loaded.");
|
||||
var redemptionManager = _serviceProvider.GetRequiredService<IRedemptionManager>();
|
||||
redemptionManager.Initialize(redemptions, actions.ToDictionary(a => a.Name, a => a));
|
||||
}
|
||||
else if (message.Request.Type == "get_default_tts_voice")
|
||||
{
|
||||
string? defaultVoice = message.Data?.ToString();
|
||||
if (defaultVoice != null)
|
||||
{
|
||||
_user.DefaultTTSVoice = defaultVoice;
|
||||
_logger.Information($"Default TTS voice was changed to '{defaultVoice}'.");
|
||||
}
|
||||
}
|
||||
else if (message.Request.Type == "update_default_tts_voice")
|
||||
{
|
||||
if (message.Request.Data?.TryGetValue("voice", out object? voice) == true && voice is string v)
|
||||
{
|
||||
_user.DefaultTTSVoice = v;
|
||||
_logger.Information($"Default TTS voice was changed to '{v}'.");
|
||||
}
|
||||
else
|
||||
_logger.Warning("Failed to update default TTS voice via request.");
|
||||
}
|
||||
else if (message.Request.Type == "get_policies")
|
||||
{
|
||||
var policies = JsonSerializer.Deserialize<IEnumerable<PolicyMessage>>(message.Data!.ToString()!, _options);
|
||||
if (policies == null || !policies.Any())
|
||||
{
|
||||
_logger.Information($"Policies have been set to default.");
|
||||
_policies.Set("everyone", "tts", 100, TimeSpan.FromSeconds(15));
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var policy in policies)
|
||||
{
|
||||
var group = _groups.Get(policy.GroupId.ToString());
|
||||
if (policy == null)
|
||||
{
|
||||
_logger.Debug($"Policy data failed");
|
||||
continue;
|
||||
}
|
||||
_logger.Debug($"Policy data [policy id: {policy.Id}][path: {policy.Path}][group id: {policy.GroupId}][group name: {group?.Name}]");
|
||||
_policies.Set(group?.Name ?? string.Empty, policy.Path, policy.Usage, TimeSpan.FromMilliseconds(policy.Span));
|
||||
}
|
||||
_logger.Information($"Policies have been loaded, a total of {policies.Count()} policies.");
|
||||
}
|
||||
else if (message.Request.Type == "update_policy")
|
||||
{
|
||||
var policy = JsonSerializer.Deserialize<PolicyMessage>(message.Data!.ToString()!, _options);
|
||||
var group = _groups.Get(policy.GroupId.ToString());
|
||||
if (policy == null || group == null)
|
||||
{
|
||||
_logger.Debug($"Policy data failed");
|
||||
return;
|
||||
}
|
||||
_logger.Debug($"Policy data [policy id: {policy.Id}][path: {policy.Path}][group id: {policy.GroupId}][group name: {group?.Name}]");
|
||||
_policies.Set(group?.Name ?? string.Empty, policy.Path, policy.Usage, TimeSpan.FromMilliseconds(policy.Span));
|
||||
_logger.Information($"Policy has been updated [policy id: {policy.Id}]");
|
||||
}
|
||||
else if (message.Request.Type == "create_policy")
|
||||
{
|
||||
var policy = JsonSerializer.Deserialize<PolicyMessage>(message.Data!.ToString()!, _options);
|
||||
|
||||
if (policy == null)
|
||||
{
|
||||
_logger.Debug($"Policy data failed");
|
||||
return;
|
||||
}
|
||||
var group = _groups.Get(policy.GroupId.ToString());
|
||||
if (group == null)
|
||||
{
|
||||
_logger.Debug($"Group data failed");
|
||||
return;
|
||||
}
|
||||
_logger.Debug($"Policy data [policy id: {policy.Id}][path: {policy.Path}][group id: {policy.GroupId}][group name: {group?.Name}]");
|
||||
_policies.Set(group?.Name, policy.Path, policy.Usage, TimeSpan.FromMilliseconds(policy.Span));
|
||||
_logger.Information($"Policy has been updated [policy id: {policy.Id}]");
|
||||
}
|
||||
else if (message.Request.Type == "update_policies")
|
||||
{
|
||||
var policy = JsonSerializer.Deserialize<PolicyMessage>(message.Data!.ToString()!, _options);
|
||||
var group = _groups.Get(policy.GroupId.ToString());
|
||||
if (policy == null)
|
||||
{
|
||||
_logger.Debug($"Policy data failed");
|
||||
return;
|
||||
}
|
||||
_logger.Debug($"Policy data [policy id: {policy.Id}][path: {policy.Path}][group id: {policy.GroupId}][group name: {group?.Name}]");
|
||||
_policies.Set(group?.Name ?? string.Empty, policy.Path, policy.Usage, TimeSpan.FromMilliseconds(policy.Span));
|
||||
_logger.Information($"Policy has been updated [policy id: {policy.Id}]");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warning($"Found unknown request type when acknowledging [type: {message.Request.Type}]");
|
||||
}
|
||||
|
||||
if (hermesRequestData != null)
|
||||
{
|
||||
_logger.Debug($"Callback was found for request [request id: {message.Request.RequestId}][type: {message.Request.Type}]");
|
||||
hermesRequestData.Callback?.Invoke(hermesRequestData.Data);
|
||||
_logger.Warning("An error occured while processing the request on the server: " + message.Error);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
_manager.Fulfill(message.Request.Type, message.Request.RequestId, json, message.Request.Data);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Net.WebSockets;
|
||||
using System.Text.Json;
|
||||
using System.Timers;
|
||||
using CommonSocketLibrary.Abstract;
|
||||
@@ -9,6 +8,7 @@ using HermesSocketLibrary.Requests.Messages;
|
||||
using HermesSocketLibrary.Socket.Data;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Serilog;
|
||||
using TwitchChatTTS.Bus;
|
||||
using TwitchChatTTS.Hermes.Socket.Handlers;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket
|
||||
@@ -19,6 +19,7 @@ namespace TwitchChatTTS.Hermes.Socket
|
||||
|
||||
private readonly User _user;
|
||||
private readonly Configuration _configuration;
|
||||
private readonly ServiceBusCentral _bus;
|
||||
private readonly ICallbackManager<HermesRequestData> _callbackManager;
|
||||
private string URL;
|
||||
|
||||
@@ -27,16 +28,18 @@ 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; }
|
||||
public bool Ready { get; set; }
|
||||
|
||||
|
||||
|
||||
public HermesSocketClient(
|
||||
User user,
|
||||
Configuration configuration,
|
||||
ServiceBusCentral bus,
|
||||
ICallbackManager<HermesRequestData> callbackManager,
|
||||
[FromKeyedServices("hermes")] IBackoff backoff,
|
||||
[FromKeyedServices("hermes")] IEnumerable<IWebSocketHandler> handlers,
|
||||
@@ -50,6 +53,7 @@ namespace TwitchChatTTS.Hermes.Socket
|
||||
{
|
||||
_user = user;
|
||||
_configuration = configuration;
|
||||
_bus = bus;
|
||||
_callbackManager = callbackManager;
|
||||
_backoff = backoff;
|
||||
_heartbeatTimer = new System.Timers.Timer(TimeSpan.FromSeconds(15));
|
||||
@@ -58,29 +62,54 @@ 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!
|
||||
}));
|
||||
|
||||
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!
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
public override async Task Connect()
|
||||
{
|
||||
lock (_lock)
|
||||
_rwls.EnterReadLock();
|
||||
try
|
||||
{
|
||||
if (Connected)
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitReadLock();
|
||||
}
|
||||
|
||||
_logger.Debug($"Attempting to connect to {URL}");
|
||||
await ConnectAsync(URL);
|
||||
|
||||
}
|
||||
|
||||
private async Task Disconnect()
|
||||
{
|
||||
lock (_lock)
|
||||
_rwls.EnterReadLock();
|
||||
try
|
||||
{
|
||||
if (!Connected)
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitReadLock();
|
||||
}
|
||||
|
||||
await DisconnectAsync(new SocketDisconnectionEventArgs("Normal disconnection", "Disconnection was executed"));
|
||||
}
|
||||
@@ -193,16 +222,10 @@ namespace TwitchChatTTS.Hermes.Socket
|
||||
|
||||
private async Task FetchRedeemableActions(IEnumerable<Redemption> redemptions)
|
||||
{
|
||||
var requestId = _callbackManager.GenerateKeyForCallback(new HermesRequestData()
|
||||
{
|
||||
Data = new Dictionary<string, object>() { { "redemptions", redemptions } }
|
||||
});
|
||||
|
||||
await Send(3, new RequestMessage()
|
||||
{
|
||||
RequestId = requestId,
|
||||
Type = "get_redeemable_actions",
|
||||
Data = null
|
||||
Data = new Dictionary<string, object>() { { "redemptions", redemptions } }
|
||||
});
|
||||
}
|
||||
|
||||
@@ -235,17 +258,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;
|
||||
@@ -255,21 +276,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);
|
||||
@@ -384,7 +405,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))
|
||||
@@ -395,7 +416,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;
|
||||
@@ -409,10 +430,18 @@ 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;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rwls.ExitReadLock();
|
||||
}
|
||||
|
||||
await base.Send(opcode, message);
|
||||
|
||||
49
Hermes/Socket/Requests/CreateGroupAck.cs
Normal file
49
Hermes/Socket/Requests/CreateGroupAck.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System.Text.Json;
|
||||
using HermesSocketLibrary.Requests.Messages;
|
||||
using Serilog;
|
||||
using TwitchChatTTS.Chat.Groups;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class CreateGroupAck : IRequestAck
|
||||
{
|
||||
public string Name => "create_group";
|
||||
private readonly IChatterGroupManager _groups;
|
||||
private readonly JsonSerializerOptions _options;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public CreateGroupAck(IChatterGroupManager groups, JsonSerializerOptions options, ILogger logger)
|
||||
{
|
||||
_groups = groups;
|
||||
_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 group = JsonSerializer.Deserialize<Group>(json, _options);
|
||||
if (group == null)
|
||||
{
|
||||
_logger.Warning($"Group data is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var exists = _groups.Get(group.Id);
|
||||
if (exists != null)
|
||||
{
|
||||
_logger.Warning($"Group id already exists [group id: {exists.Id}][group name: {exists.Name} / {group.Name}][group priority: {exists.Priority} / {group.Priority}]");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Debug($"Adding group [group id: {group.Id}][group name: {group.Name}][group priority: {group.Priority}]");
|
||||
_groups.Add(group);
|
||||
_logger.Information($"Group has been created [group id: {group.Id}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
49
Hermes/Socket/Requests/CreateGroupChatterAck.cs
Normal file
49
Hermes/Socket/Requests/CreateGroupChatterAck.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System.Text.Json;
|
||||
using HermesSocketLibrary.Requests.Messages;
|
||||
using Serilog;
|
||||
using TwitchChatTTS.Chat.Groups;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class CreateGroupChatterAck : IRequestAck
|
||||
{
|
||||
public string Name => "create_group_chatter";
|
||||
private readonly IChatterGroupManager _groups;
|
||||
private readonly JsonSerializerOptions _options;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public CreateGroupChatterAck(IChatterGroupManager groups, JsonSerializerOptions options, ILogger logger)
|
||||
{
|
||||
_groups = groups;
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
{
|
||||
_logger.Warning($"Group Chatter JSON data is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var groupChatter = JsonSerializer.Deserialize<GroupChatter>(json, _options);
|
||||
if (groupChatter == null)
|
||||
{
|
||||
_logger.Warning($"Group Chatter data is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var group = _groups.Get(groupChatter.GroupId);
|
||||
if (group == null)
|
||||
{
|
||||
_logger.Warning($"Group id for chatter does not exists [group id: {groupChatter.GroupId}]");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Debug($"Adding chatter to group [group id: {groupChatter.GroupId}][group name: {group.Name}][chatter id: {groupChatter.ChatterId}][chatter label: {groupChatter.ChatterLabel}]");
|
||||
_groups.Add(groupChatter.ChatterId, groupChatter.GroupId);
|
||||
_logger.Information($"Chatter has been added to group [group id: {groupChatter.GroupId}][group name: {group.Name}][chatter id: {groupChatter.ChatterId}][chatter label: {groupChatter.ChatterLabel}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
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}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
51
Hermes/Socket/Requests/CreatePolicyAck.cs
Normal file
51
Hermes/Socket/Requests/CreatePolicyAck.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System.Text.Json;
|
||||
using HermesSocketServer.Messages;
|
||||
using Serilog;
|
||||
using TwitchChatTTS.Chat.Commands.Limits;
|
||||
using TwitchChatTTS.Chat.Groups;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class CreatePolicyAck : IRequestAck
|
||||
{
|
||||
public string Name => "create_policy";
|
||||
private readonly IChatterGroupManager _groups;
|
||||
private readonly IUsagePolicy<long> _policies;
|
||||
private readonly JsonSerializerOptions _options;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public CreatePolicyAck(IChatterGroupManager groups, IUsagePolicy<long> policies, JsonSerializerOptions options, ILogger logger)
|
||||
{
|
||||
_groups = groups;
|
||||
_policies = policies;
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json)) {
|
||||
_logger.Warning($"Policy JSON data is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var policy = JsonSerializer.Deserialize<Policy>(json, _options);
|
||||
if (policy == null)
|
||||
{
|
||||
_logger.Warning($"Policy data is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var group = _groups.Get(policy.GroupId.ToString());
|
||||
if (group == null)
|
||||
{
|
||||
_logger.Warning($"Policy's group id not found [group id: {policy.GroupId}][policy id: {policy.Id}]");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Debug($"Policy data [policy id: {policy.Id}][path: {policy.Path}][group id: {policy.GroupId}][group name: {group.Name}]");
|
||||
_policies.Set(group.Name, policy.Path, policy.Usage, TimeSpan.FromMilliseconds(policy.Span));
|
||||
_logger.Information($"Policy has been created [policy id: {policy.Id}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
41
Hermes/Socket/Requests/CreateRedeemableActionAck.cs
Normal file
41
Hermes/Socket/Requests/CreateRedeemableActionAck.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System.Text.Json;
|
||||
using HermesSocketLibrary.Requests.Messages;
|
||||
using Serilog;
|
||||
using TwitchChatTTS.Twitch.Redemptions;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class CreateRedeemableActionAck : IRequestAck
|
||||
{
|
||||
public string Name => "create_redeemable_action";
|
||||
private readonly IRedemptionManager _redemptions;
|
||||
private readonly JsonSerializerOptions _options;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public CreateRedeemableActionAck(IRedemptionManager redemptions, JsonSerializerOptions options, ILogger logger)
|
||||
{
|
||||
_redemptions = redemptions;
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
if (json == null)
|
||||
{
|
||||
_logger.Warning($"Redeemable action JSON data received is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var action = JsonSerializer.Deserialize<RedeemableAction>(json, _options);
|
||||
if (action == null)
|
||||
{
|
||||
_logger.Warning($"Redeemable action data received is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
_redemptions.Add(action);
|
||||
_logger.Information($"A new redeemable action has been created [action name: {action.Name}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
41
Hermes/Socket/Requests/CreateRedemptionAck.cs
Normal file
41
Hermes/Socket/Requests/CreateRedemptionAck.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System.Text.Json;
|
||||
using HermesSocketLibrary.Requests.Messages;
|
||||
using Serilog;
|
||||
using TwitchChatTTS.Twitch.Redemptions;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class CreateRedemptionAck : IRequestAck
|
||||
{
|
||||
public string Name => "create_redemption";
|
||||
private readonly IRedemptionManager _redemptions;
|
||||
private readonly JsonSerializerOptions _options;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public CreateRedemptionAck(IRedemptionManager redemptions, JsonSerializerOptions options, ILogger logger)
|
||||
{
|
||||
_redemptions = redemptions;
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
if (json == null)
|
||||
{
|
||||
_logger.Warning($"Redemption JSON data received is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var redemption = JsonSerializer.Deserialize<Redemption>(json, _options);
|
||||
if (redemption == null)
|
||||
{
|
||||
_logger.Warning($"Redemption data received is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
_redemptions.Add(redemption);
|
||||
_logger.Information($"A new redemption has been created [redemption id: {redemption.Id}][twitch redemption id: {redemption.RedemptionId}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
59
Hermes/Socket/Requests/CreateTTSFilterAck.cs
Normal file
59
Hermes/Socket/Requests/CreateTTSFilterAck.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using HermesSocketLibrary.Requests.Messages;
|
||||
using Serilog;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class CreateTTSFilterAck : IRequestAck
|
||||
{
|
||||
public string Name => "create_tts_filter";
|
||||
private readonly User _user;
|
||||
private readonly JsonSerializerOptions _options;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public CreateTTSFilterAck(User user, JsonSerializerOptions options, ILogger logger)
|
||||
{
|
||||
_user = user;
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
if (json == null)
|
||||
{
|
||||
_logger.Warning($"TTS Filter JSON data received is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var filter = JsonSerializer.Deserialize<TTSWordFilter>(json, _options);
|
||||
if (filter == null)
|
||||
{
|
||||
_logger.Warning($"TTS Filter data received is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_user.RegexFilters.Any(f => f.Id == filter.Id))
|
||||
{
|
||||
_logger.Warning($"Filter already exists [filter id: {filter.Id}]");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var re = new Regex(filter.Search, ((RegexOptions)filter.Flag) | RegexOptions.Compiled);
|
||||
re.Match(string.Empty);
|
||||
filter.Regex = re;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_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}]");
|
||||
_user.RegexFilters.Add(filter);
|
||||
_logger.Information($"Filter has been created [filter id: {filter.Id}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
52
Hermes/Socket/Requests/CreateTTSUserAck.cs
Normal file
52
Hermes/Socket/Requests/CreateTTSUserAck.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using Serilog;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class CreateTTSUserAck : IRequestAck
|
||||
{
|
||||
public string Name => "create_tts_user";
|
||||
private readonly User _user;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public CreateTTSUserAck(User user, ILogger logger)
|
||||
{
|
||||
_user = user;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
if (requestData == null)
|
||||
{
|
||||
_logger.Warning("Request data is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!long.TryParse(requestData["chatter"].ToString(), out long chatterId))
|
||||
{
|
||||
_logger.Warning($"Failed to parse chatter id [chatter id: {requestData["chatter"]}]");
|
||||
return;
|
||||
}
|
||||
if (chatterId <= 0)
|
||||
{
|
||||
_logger.Warning($"Chatter Id is invalid [chatter id: {chatterId}]");
|
||||
return;
|
||||
}
|
||||
|
||||
var voiceId = requestData["voice"].ToString();
|
||||
if (string.IsNullOrEmpty(voiceId))
|
||||
{
|
||||
_logger.Warning("Voice Id is invalid.");
|
||||
return;
|
||||
}
|
||||
if (!_user.VoicesAvailable.TryGetValue(voiceId, out var voiceName))
|
||||
{
|
||||
_logger.Warning($"Voice Id does not exist [voice id: {voiceId}]");
|
||||
return;
|
||||
}
|
||||
|
||||
_user.VoicesSelected.Add(chatterId, voiceId);
|
||||
_logger.Information($"Created a new TTS user [chatter id: {_user.TwitchUserId}][chatter id: {chatterId}][voice id: {voiceId}][voice name: {voiceName}].");
|
||||
}
|
||||
}
|
||||
}
|
||||
47
Hermes/Socket/Requests/CreateTTSVoiceAck.cs
Normal file
47
Hermes/Socket/Requests/CreateTTSVoiceAck.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using Serilog;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class CreateTTSVoiceAck : IRequestAck
|
||||
{
|
||||
public string Name => "create_tts_voice";
|
||||
private readonly User _user;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public CreateTTSVoiceAck(User user, ILogger logger)
|
||||
{
|
||||
_user = user;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
if (requestData == null)
|
||||
{
|
||||
_logger.Warning("Request data is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var voice = requestData["voice"].ToString()!;
|
||||
var voiceId = json;
|
||||
if (string.IsNullOrEmpty(voice))
|
||||
{
|
||||
_logger.Warning("Voice name is invalid.");
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrEmpty(voiceId))
|
||||
{
|
||||
_logger.Warning("Voice Id is invalid.");
|
||||
return;
|
||||
}
|
||||
if (_user.VoicesAvailable.TryGetValue(voiceId, out var voiceName))
|
||||
{
|
||||
_logger.Warning($"Voice Id already exists [voice id: {voiceId}][voice name: {voiceName}]");
|
||||
return;
|
||||
}
|
||||
|
||||
_user.VoicesAvailable.Add(voiceId, voice);
|
||||
_logger.Information($"Created a new tts voice [voice: {voice}][id: {voiceId}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
45
Hermes/Socket/Requests/DeleteGroupAck.cs
Normal file
45
Hermes/Socket/Requests/DeleteGroupAck.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using Serilog;
|
||||
using TwitchChatTTS.Chat.Groups;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class DeleteGroupAck : IRequestAck
|
||||
{
|
||||
public string Name => "delete_group";
|
||||
private readonly IChatterGroupManager _groups;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public DeleteGroupAck(IChatterGroupManager groups, ILogger logger)
|
||||
{
|
||||
_groups = groups;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
if (requestData == null)
|
||||
{
|
||||
_logger.Warning("Request data is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var groupId = requestData["group"].ToString();
|
||||
if (string.IsNullOrEmpty(groupId))
|
||||
{
|
||||
_logger.Warning($"Action name is invalid [action name: {groupId}]");
|
||||
return;
|
||||
}
|
||||
|
||||
var group = _groups.Get(groupId);
|
||||
if (group == null)
|
||||
{
|
||||
_logger.Warning($"Group id does not exist [group id: {group}]");
|
||||
return;
|
||||
}
|
||||
|
||||
_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}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
51
Hermes/Socket/Requests/DeleteGroupChatterAck.cs
Normal file
51
Hermes/Socket/Requests/DeleteGroupChatterAck.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using Serilog;
|
||||
using TwitchChatTTS.Chat.Groups;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class DeleteGroupChatterAck : IRequestAck
|
||||
{
|
||||
public string Name => "delete_group_chatter";
|
||||
private readonly IChatterGroupManager _groups;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public DeleteGroupChatterAck(IChatterGroupManager groups, ILogger logger)
|
||||
{
|
||||
_groups = groups;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
if (requestData == null)
|
||||
{
|
||||
_logger.Warning("Request data is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!long.TryParse(requestData["chatter"].ToString(), out var chatterId))
|
||||
{
|
||||
_logger.Warning($"Chatter Id is invalid [chatter id: {chatterId}]");
|
||||
return;
|
||||
}
|
||||
|
||||
var groupId = requestData["group"].ToString();
|
||||
if (string.IsNullOrWhiteSpace(groupId))
|
||||
{
|
||||
_logger.Warning($"Group Id is invalid [group id: {groupId}]");
|
||||
return;
|
||||
}
|
||||
|
||||
var group = _groups.Get(groupId);
|
||||
if (group == null)
|
||||
{
|
||||
_logger.Warning($"Group id does not exist [group id: {groupId}]");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Debug($"Deleting chatter from group [group id: {group.Id}][chatter id: {chatterId}][group name: {group.Name}][group priority: {group.Priority}]");
|
||||
_groups.Remove(chatterId, groupId);
|
||||
_logger.Information($"Chatter has been deleted from group [group id: {group.Id}][chatter id: {chatterId}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
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}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
49
Hermes/Socket/Requests/DeletePolicyAck.cs
Normal file
49
Hermes/Socket/Requests/DeletePolicyAck.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using Serilog;
|
||||
using TwitchChatTTS.Chat.Commands.Limits;
|
||||
using TwitchChatTTS.Chat.Groups;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class DeletePolicyAck : IRequestAck
|
||||
{
|
||||
public string Name => "delete_policy";
|
||||
private readonly IChatterGroupManager _groups;
|
||||
private readonly IUsagePolicy<long> _policies;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public DeletePolicyAck(IChatterGroupManager groups, IUsagePolicy<long> policies, ILogger logger)
|
||||
{
|
||||
_groups = groups;
|
||||
_policies = policies;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
if (json == null)
|
||||
{
|
||||
_logger.Warning("Policy JSON data is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var data = json.Split('/');
|
||||
if (data.Length != 2)
|
||||
{
|
||||
_logger.Error("Deleting a policy failed: data received is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
var groupId = data[0];
|
||||
var path = data[1];
|
||||
var group = _groups.Get(groupId);
|
||||
if (group == null)
|
||||
{
|
||||
_logger.Warning($"Deleting a policy failed: group id does not exist [group id: {groupId}][path: {path}]");
|
||||
return;
|
||||
}
|
||||
|
||||
_policies.Remove(group.Name, path);
|
||||
_logger.Information($"Policy has been deleted [group id: {groupId}][path: {path}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
39
Hermes/Socket/Requests/DeleteRedeemableActionAck.cs
Normal file
39
Hermes/Socket/Requests/DeleteRedeemableActionAck.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using Serilog;
|
||||
using TwitchChatTTS.Twitch.Redemptions;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class DeleteRedeemableActionAck : IRequestAck
|
||||
{
|
||||
public string Name => "delete_redeemable_action";
|
||||
private readonly IRedemptionManager _redemptions;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public DeleteRedeemableActionAck(IRedemptionManager redemptions, ILogger logger)
|
||||
{
|
||||
_redemptions = redemptions;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
if (requestData == null)
|
||||
{
|
||||
_logger.Warning("Request data is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var name = requestData["name"].ToString();
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
_logger.Warning($"Action name is invalid [action name: {name}]");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_redemptions.RemoveAction(name))
|
||||
_logger.Information($"Deleted a redeemable action [action name: {name}]");
|
||||
else
|
||||
_logger.Warning($"Failed to delete a redeemable action [action name: {name}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
39
Hermes/Socket/Requests/DeleteRedemptionAck.cs
Normal file
39
Hermes/Socket/Requests/DeleteRedemptionAck.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using Serilog;
|
||||
using TwitchChatTTS.Twitch.Redemptions;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class DeleteRedemptionAck : IRequestAck
|
||||
{
|
||||
public string Name => "delete_redemption";
|
||||
private readonly IRedemptionManager _redemptions;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public DeleteRedemptionAck(IRedemptionManager redemptions, ILogger logger)
|
||||
{
|
||||
_redemptions = redemptions;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
if (requestData == null)
|
||||
{
|
||||
_logger.Warning("Request data is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var id = requestData["id"].ToString();
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
_logger.Warning($"Redemption Id is invalid [redemption id: {id}]");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_redemptions.RemoveRedemption(id))
|
||||
_logger.Information($"Deleted a redemption [redemption id: {id}]");
|
||||
else
|
||||
_logger.Warning($"Failed to delete a redemption [redemption id: {id}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
42
Hermes/Socket/Requests/DeleteTTSFilterAck.cs
Normal file
42
Hermes/Socket/Requests/DeleteTTSFilterAck.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using Serilog;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class DeleteTTSFilterAck : IRequestAck
|
||||
{
|
||||
public string Name => "delete_tts_filter";
|
||||
private readonly User _user;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public DeleteTTSFilterAck(User user, ILogger logger)
|
||||
{
|
||||
_user = user;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
if (requestData == null)
|
||||
{
|
||||
_logger.Warning("Request data is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var id = requestData["id"].ToString();
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
_logger.Warning($"Filter Id is invalid [filter id: {id}]");
|
||||
return;
|
||||
}
|
||||
var filter = _user.RegexFilters.FirstOrDefault(f => f.Id == id);
|
||||
if (filter == null)
|
||||
{
|
||||
_logger.Warning($"Filter Id does not exist [filter id: {id}]");
|
||||
return;
|
||||
}
|
||||
|
||||
_user.RegexFilters.Remove(filter);
|
||||
_logger.Information($"Deleted a filter [filter id: {id}][search: {filter.Search}][replace: {filter.Replace}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
41
Hermes/Socket/Requests/DeleteTTSVoiceAck.cs
Normal file
41
Hermes/Socket/Requests/DeleteTTSVoiceAck.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using Serilog;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class DeleteTTSVoiceAck : IRequestAck
|
||||
{
|
||||
public string Name => "delete_tts_voice";
|
||||
private readonly User _user;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public DeleteTTSVoiceAck(User user, ILogger logger)
|
||||
{
|
||||
_user = user;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
if (requestData == null)
|
||||
{
|
||||
_logger.Warning("Request data is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var voice = requestData["voice"].ToString();
|
||||
if (string.IsNullOrEmpty(voice))
|
||||
{
|
||||
_logger.Warning($"Voice Id is invalid [voice id: {voice}]");
|
||||
return;
|
||||
}
|
||||
if (!_user.VoicesAvailable.TryGetValue(voice, out string? voiceName))
|
||||
{
|
||||
_logger.Warning($"Voice Id does not exist [voice id: {voice}]");
|
||||
return;
|
||||
}
|
||||
|
||||
_user.VoicesAvailable.Remove(voice);
|
||||
_logger.Information($"Deleted a voice [voice id: {voice}][voice name: {voiceName}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
44
Hermes/Socket/Requests/GetChatterIdsAck.cs
Normal file
44
Hermes/Socket/Requests/GetChatterIdsAck.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System.Text.Json;
|
||||
using Serilog;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class GetChatterIdsAck : IRequestAck
|
||||
{
|
||||
public string Name => "get_chatter_ids";
|
||||
private readonly User _user;
|
||||
private readonly JsonSerializerOptions _options;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public GetChatterIdsAck(User user, JsonSerializerOptions options, ILogger logger)
|
||||
{
|
||||
_user = user;
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
if (json == null)
|
||||
{
|
||||
_logger.Warning("Chatters JSON is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var chatters = JsonSerializer.Deserialize<IEnumerable<long>>(json, _options);
|
||||
if (chatters == null)
|
||||
{
|
||||
_logger.Warning("Chatters is null.");
|
||||
return;
|
||||
}
|
||||
if (!chatters.Any())
|
||||
{
|
||||
_logger.Warning("Chatters is empty.");
|
||||
return;
|
||||
}
|
||||
|
||||
_user.Chatters = [.. chatters];
|
||||
_logger.Information($"Fetched chatters [count: {chatters.Count()}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
58
Hermes/Socket/Requests/GetConnectionsAck.cs
Normal file
58
Hermes/Socket/Requests/GetConnectionsAck.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System.Text.Json;
|
||||
using HermesSocketLibrary.Socket.Data;
|
||||
using Serilog;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class GetConnectionsAck : IRequestAck
|
||||
{
|
||||
public string Name => "get_connections";
|
||||
private readonly TwitchApiClient _twitch;
|
||||
private readonly NightbotApiClient _nightbot;
|
||||
private readonly User _user;
|
||||
private readonly JsonSerializerOptions _options;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public GetConnectionsAck
|
||||
(
|
||||
TwitchApiClient twitch,
|
||||
NightbotApiClient nightbot,
|
||||
User user,
|
||||
JsonSerializerOptions options,
|
||||
ILogger logger
|
||||
)
|
||||
{
|
||||
_twitch = twitch;
|
||||
_nightbot = nightbot;
|
||||
_user = user;
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
if (json == null)
|
||||
{
|
||||
_logger.Error("Connections JSON is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var connections = JsonSerializer.Deserialize<IEnumerable<Connection>>(json, _options);
|
||||
if (connections == null)
|
||||
{
|
||||
_logger.Error("Null value was given when attempting to fetch connections.");
|
||||
return;
|
||||
}
|
||||
|
||||
_user.TwitchConnection = connections.FirstOrDefault(c => c.Type == "twitch" && c.Default);
|
||||
_user.NightbotConnection = connections.FirstOrDefault(c => c.Type == "nightbot" && c.Default);
|
||||
|
||||
_logger.Information($"Fetched connections from TTS account [count: {connections.Count()}][twitch: {_user.TwitchConnection != null}][nightbot: {_user.NightbotConnection != null}]");
|
||||
|
||||
if (_user.TwitchConnection != null)
|
||||
_twitch.Initialize(_user.TwitchConnection.ClientId, _user.TwitchConnection.AccessToken);
|
||||
if (_user.NightbotConnection != null)
|
||||
_nightbot.Initialize(_user.NightbotConnection.ClientId, _user.NightbotConnection.AccessToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
31
Hermes/Socket/Requests/GetDefaultTTSVoiceAck.cs
Normal file
31
Hermes/Socket/Requests/GetDefaultTTSVoiceAck.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using Serilog;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class GetDefaultTTSVoiceAck : IRequestAck
|
||||
{
|
||||
public string Name => "get_default_tts_voice";
|
||||
private readonly User _user;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public GetDefaultTTSVoiceAck(User user, ILogger logger)
|
||||
{
|
||||
_user = user;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
string? defaultVoice = json;
|
||||
if (defaultVoice != null)
|
||||
{
|
||||
_user.DefaultTTSVoice = defaultVoice;
|
||||
_logger.Information($"Default TTS voice was changed [voice: {defaultVoice}]");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error($"Failed to load default TTS voice.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
54
Hermes/Socket/Requests/GetEmotesAck.cs
Normal file
54
Hermes/Socket/Requests/GetEmotesAck.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System.Text.Json;
|
||||
using HermesSocketLibrary.Requests.Messages;
|
||||
using Serilog;
|
||||
using TwitchChatTTS.Chat.Emotes;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class GetEmotesAck : IRequestAck
|
||||
{
|
||||
public string Name => "get_emotes";
|
||||
private readonly IEmoteDatabase _emotes;
|
||||
private readonly JsonSerializerOptions _options;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public GetEmotesAck(IEmoteDatabase emotes, JsonSerializerOptions options, ILogger logger)
|
||||
{
|
||||
_emotes = emotes;
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
if (json == null)
|
||||
{
|
||||
_logger.Warning("Emotes JSON is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var data = JsonSerializer.Deserialize<IEnumerable<EmoteInfo>>(json, _options);
|
||||
if (data == null)
|
||||
{
|
||||
_logger.Warning("Emotes is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var count = 0;
|
||||
var duplicateNames = 0;
|
||||
foreach (var emote in data)
|
||||
{
|
||||
if (_emotes.Get(emote.Name) == null)
|
||||
{
|
||||
_emotes.Add(emote.Name, emote.Id);
|
||||
count++;
|
||||
}
|
||||
else
|
||||
duplicateNames++;
|
||||
}
|
||||
_logger.Information($"Fetched emotes of various sources [count: {count}]");
|
||||
if (duplicateNames > 0)
|
||||
_logger.Warning($"Found {duplicateNames} emotes with duplicate names.");
|
||||
}
|
||||
}
|
||||
}
|
||||
47
Hermes/Socket/Requests/GetEnabledTTSVoices.cs
Normal file
47
Hermes/Socket/Requests/GetEnabledTTSVoices.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System.Text.Json;
|
||||
using Serilog;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class GetEnabledTTSVoicesAck : IRequestAck
|
||||
{
|
||||
public string Name => "get_enabled_tts_voices";
|
||||
private readonly User _user;
|
||||
private readonly JsonSerializerOptions _options;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public GetEnabledTTSVoicesAck(User user, JsonSerializerOptions options, ILogger logger)
|
||||
{
|
||||
_user = user;
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
if (json == null)
|
||||
{
|
||||
_logger.Warning("TTS Voices JSON is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var enabledTTSVoices = JsonSerializer.Deserialize<IEnumerable<string>>(json, _options);
|
||||
if (enabledTTSVoices == null)
|
||||
{
|
||||
_logger.Warning("Failed to load enabled tts voices.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_user.VoicesEnabled == null)
|
||||
_user.VoicesEnabled = enabledTTSVoices.ToHashSet();
|
||||
else
|
||||
{
|
||||
_user.VoicesEnabled.Clear();
|
||||
foreach (var voice in enabledTTSVoices)
|
||||
_user.VoicesEnabled.Add(voice);
|
||||
}
|
||||
|
||||
_logger.Information($"TTS voices [count: {_user.VoicesEnabled.Count}] have been enabled.");
|
||||
}
|
||||
}
|
||||
}
|
||||
76
Hermes/Socket/Requests/GetPermissionsAck.cs
Normal file
76
Hermes/Socket/Requests/GetPermissionsAck.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
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 GetPermissionsAck : IRequestAck
|
||||
{
|
||||
public string Name => "get_permissions";
|
||||
private readonly IGroupPermissionManager _permissions;
|
||||
private readonly IChatterGroupManager _groups;
|
||||
private readonly JsonSerializerOptions _options;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public GetPermissionsAck(
|
||||
IGroupPermissionManager permissions,
|
||||
IChatterGroupManager groups,
|
||||
JsonSerializerOptions options,
|
||||
ILogger logger)
|
||||
{
|
||||
_permissions = permissions;
|
||||
_groups = groups;
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
if (json == null)
|
||||
{
|
||||
_logger.Error("Failed to load groups & permissions: JSON is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var groupInfo = JsonSerializer.Deserialize<GroupInfo>(json, _options);
|
||||
if (groupInfo == null)
|
||||
{
|
||||
_logger.Error("Failed to load groups & permissions: object is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
_permissions.Clear();
|
||||
_groups.Clear();
|
||||
|
||||
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.ToString(), 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}";
|
||||
_permissions.Set(path, permission.Allow);
|
||||
_logger.Debug($"Added group permission [id: {permission.Id}][group id: {permission.GroupId}][path: {permission.Path}]");
|
||||
}
|
||||
|
||||
_logger.Information($"Groups [count: {groupInfo.Groups.Count()}] & Permissions [count: {groupInfo.GroupPermissions.Count()}] have been loaded.");
|
||||
|
||||
foreach (var chatter in groupInfo.GroupChatters)
|
||||
if (groupsById.TryGetValue(chatter.GroupId, out var group))
|
||||
_groups.Add(chatter.ChatterId, group.Id);
|
||||
_logger.Information($"Users in each group [count: {groupInfo.GroupChatters.Count()}] have been loaded.");
|
||||
}
|
||||
}
|
||||
}
|
||||
58
Hermes/Socket/Requests/GetPoliciesAck.cs
Normal file
58
Hermes/Socket/Requests/GetPoliciesAck.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System.Text.Json;
|
||||
using HermesSocketServer.Messages;
|
||||
using Serilog;
|
||||
using TwitchChatTTS.Chat.Commands.Limits;
|
||||
using TwitchChatTTS.Chat.Groups;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class GetPoliciesAck : IRequestAck
|
||||
{
|
||||
public string Name => "get_policies";
|
||||
private readonly IChatterGroupManager _groups;
|
||||
private readonly IUsagePolicy<long> _policies;
|
||||
private readonly JsonSerializerOptions _options;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public GetPoliciesAck(
|
||||
IChatterGroupManager groups,
|
||||
IUsagePolicy<long> policies,
|
||||
JsonSerializerOptions options,
|
||||
ILogger logger)
|
||||
{
|
||||
_groups = groups;
|
||||
_policies = policies;
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
if (json == null)
|
||||
{
|
||||
_logger.Error($"No policies have been found: JSON is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var policies = JsonSerializer.Deserialize<IEnumerable<Policy>>(json, _options);
|
||||
if (policies == null || !policies.Any())
|
||||
{
|
||||
_logger.Error($"No policies have been found: object is null or empty.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var policy in policies)
|
||||
{
|
||||
var group = _groups.Get(policy.GroupId.ToString());
|
||||
if (group == null)
|
||||
{
|
||||
_logger.Debug($"Policy's group id not found [group id: {policy.GroupId}][policy id: {policy.Id}]");
|
||||
continue;
|
||||
}
|
||||
_logger.Debug($"Policy data loaded [policy id: {policy.Id}][path: {policy.Path}][group id: {policy.GroupId}][group name: {group.Name}]");
|
||||
_policies.Set(group.Name, policy.Path, policy.Usage, TimeSpan.FromMilliseconds(policy.Span));
|
||||
}
|
||||
_logger.Information($"Policies have been loaded [count: {policies.Count()}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
56
Hermes/Socket/Requests/GetRedeemableActionsAck.cs
Normal file
56
Hermes/Socket/Requests/GetRedeemableActionsAck.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System.Text.Json;
|
||||
using HermesSocketLibrary.Requests.Messages;
|
||||
using Serilog;
|
||||
using TwitchChatTTS.Bus;
|
||||
using TwitchChatTTS.Bus.Data;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class GetRedeemableActionsAck : IRequestAck
|
||||
{
|
||||
public string Name => "get_redeemable_actions";
|
||||
private readonly ServiceBusCentral _bus;
|
||||
private readonly JsonSerializerOptions _options;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public GetRedeemableActionsAck(
|
||||
ServiceBusCentral bus,
|
||||
JsonSerializerOptions options,
|
||||
ILogger logger)
|
||||
{
|
||||
_bus = bus;
|
||||
_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;
|
||||
}
|
||||
|
||||
IEnumerable<Redemption>? redemptions = JsonSerializer.Deserialize<IEnumerable<Redemption>>(requestData["redemptions"].ToString() ?? string.Empty, _options);
|
||||
if (redemptions == null)
|
||||
{
|
||||
_logger.Warning($"Failed to read the redemptions while updating redemption actions [class type: {requestData["redemptions"].GetType().Name}]");
|
||||
return;
|
||||
}
|
||||
|
||||
IEnumerable<RedeemableAction>? actions = JsonSerializer.Deserialize<IEnumerable<RedeemableAction>>(json, _options);
|
||||
if (actions == null)
|
||||
{
|
||||
_logger.Warning("Failed to read the redeemable actions for redemptions.");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Information($"Redeemable actions loaded [count: {actions.Count()}]");
|
||||
_bus.Send(this, "redemptions_initiation", new RedemptionInitiation()
|
||||
{
|
||||
Redemptions = redemptions,
|
||||
Actions = actions.ToDictionary(a => a.Name, a => a)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
52
Hermes/Socket/Requests/GetRedemptionsAck.cs
Normal file
52
Hermes/Socket/Requests/GetRedemptionsAck.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using System.Text.Json;
|
||||
using HermesSocketLibrary.Requests.Callbacks;
|
||||
using HermesSocketLibrary.Requests.Messages;
|
||||
using Serilog;
|
||||
using TwitchChatTTS.Hermes.Socket.Handlers;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class GetRedemptionsAck : IRequestAck
|
||||
{
|
||||
public string Name => "get_redemptions";
|
||||
private readonly ICallbackManager<HermesRequestData> _callbacks;
|
||||
private readonly JsonSerializerOptions _options;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public GetRedemptionsAck(
|
||||
ICallbackManager<HermesRequestData> callbacks,
|
||||
JsonSerializerOptions options,
|
||||
ILogger logger)
|
||||
{
|
||||
_callbacks = callbacks;
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
HermesRequestData? hermesRequestData = null;
|
||||
if (!string.IsNullOrEmpty(requestId))
|
||||
{
|
||||
hermesRequestData = _callbacks.Take(requestId);
|
||||
if (hermesRequestData == null)
|
||||
_logger.Warning($"Could not find callback for request [request id: {requestId}][type: {Name}]");
|
||||
else if (hermesRequestData.Data == null)
|
||||
hermesRequestData.Data = new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
IEnumerable<Redemption>? redemptions = JsonSerializer.Deserialize<IEnumerable<Redemption>>(json, _options);
|
||||
if (redemptions != null)
|
||||
{
|
||||
_logger.Information($"Redemptions loaded [count: {redemptions.Count()}]");
|
||||
if (hermesRequestData?.Data != null)
|
||||
{
|
||||
hermesRequestData.Data.Add("redemptions", redemptions);
|
||||
|
||||
_logger.Debug($"Callback was found for request [request id: {requestId}][type: {Name}]");
|
||||
hermesRequestData.Callback?.Invoke(hermesRequestData.Data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
35
Hermes/Socket/Requests/GetTTSUsersAck.cs
Normal file
35
Hermes/Socket/Requests/GetTTSUsersAck.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text.Json;
|
||||
using Serilog;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class GetTTSUsersAck : IRequestAck
|
||||
{
|
||||
public string Name => "get_tts_users";
|
||||
private readonly User _user;
|
||||
private readonly JsonSerializerOptions _options;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public GetTTSUsersAck(User user, JsonSerializerOptions options, ILogger logger)
|
||||
{
|
||||
_user = user;
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
var users = JsonSerializer.Deserialize<IDictionary<long, string>>(json, _options);
|
||||
if (users == null)
|
||||
return;
|
||||
|
||||
var temp = new ConcurrentDictionary<long, string>();
|
||||
foreach (var entry in users)
|
||||
temp.TryAdd(entry.Key, entry.Value);
|
||||
|
||||
_user.VoicesSelected = temp;
|
||||
_logger.Information($"Updated chatters' selected voice [count: {temp.Count()}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
40
Hermes/Socket/Requests/GetTTSVoicesAck.cs
Normal file
40
Hermes/Socket/Requests/GetTTSVoicesAck.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text.Json;
|
||||
using HermesSocketLibrary.Requests.Messages;
|
||||
using Serilog;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class GetTTSVoicesAck : IRequestAck
|
||||
{
|
||||
public string Name => "get_tts_voices";
|
||||
private readonly User _user;
|
||||
private readonly JsonSerializerOptions _options;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public GetTTSVoicesAck(User user, JsonSerializerOptions options, ILogger logger)
|
||||
{
|
||||
_user = user;
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
var voices = JsonSerializer.Deserialize<IEnumerable<TTSVoice>>(json, _options);
|
||||
if (voices == null)
|
||||
{
|
||||
_logger.Warning("Voices received is null.");
|
||||
return;
|
||||
}
|
||||
if (!voices.Any())
|
||||
{
|
||||
_logger.Warning("Voices received is empty.");
|
||||
return;
|
||||
}
|
||||
|
||||
_user.VoicesAvailable = new ConcurrentDictionary<string, string>(voices.ToDictionary(e => e.Id, e => e.Name));
|
||||
_logger.Information($"Fetched all available voices for TTS [count: {_user.VoicesAvailable.Count}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
49
Hermes/Socket/Requests/GetTTSWordFiltersAck.cs
Normal file
49
Hermes/Socket/Requests/GetTTSWordFiltersAck.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using HermesSocketLibrary.Requests.Messages;
|
||||
using Serilog;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class GetTTSWordFiltersAck : IRequestAck
|
||||
{
|
||||
public string Name => "get_tts_word_filters";
|
||||
private readonly User _user;
|
||||
private readonly JsonSerializerOptions _options;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public GetTTSWordFiltersAck(User user, JsonSerializerOptions options, ILogger logger)
|
||||
{
|
||||
_user = user;
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
var wordFilters = JsonSerializer.Deserialize<IEnumerable<TTSWordFilter>>(json, _options);
|
||||
if (wordFilters == null)
|
||||
{
|
||||
_logger.Error("Failed to load word filters.");
|
||||
return;
|
||||
}
|
||||
|
||||
var filters = 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);
|
||||
re.Match(string.Empty);
|
||||
filter.Regex = re;
|
||||
}
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
8
Hermes/Socket/Requests/IRequestAck.cs
Normal file
8
Hermes/Socket/Requests/IRequestAck.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public interface IRequestAck
|
||||
{
|
||||
string Name { get; }
|
||||
void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData);
|
||||
}
|
||||
}
|
||||
36
Hermes/Socket/Requests/RequestAckManager.cs
Normal file
36
Hermes/Socket/Requests/RequestAckManager.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using Serilog;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class RequestAckManager
|
||||
{
|
||||
private readonly IDictionary<string, IRequestAck> _acknowledgements;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public RequestAckManager(IEnumerable<IRequestAck> acks, ILogger logger)
|
||||
{
|
||||
_acknowledgements = acks.ToDictionary(a => a.Name, a => a);
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Fulfill(string type, string requestId, string? data, IDictionary<string, object>? requestData)
|
||||
{
|
||||
if (!_acknowledgements.TryGetValue(type, out var ack))
|
||||
{
|
||||
_logger.Warning($"Found unknown request type when acknowledging [type: {type}]");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Debug($"Request acknowledgement found [type: {type}][data: {data}]");
|
||||
try
|
||||
{
|
||||
ack.Acknowledge(requestId, data, requestData);
|
||||
_logger.Debug($"Request acknowledged without error [type: {type}][data: {data}]");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Failed to fulfill a request ackowledgement.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
Hermes/Socket/Requests/UpdateDefaultTTSVoiceAck.cs
Normal file
37
Hermes/Socket/Requests/UpdateDefaultTTSVoiceAck.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Serilog;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class UpdateDefaultTTSVoiceAck : IRequestAck
|
||||
{
|
||||
public string Name => "update_default_tts_voice";
|
||||
private readonly User _user;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public UpdateDefaultTTSVoiceAck(User user, ILogger logger)
|
||||
{
|
||||
_user = user;
|
||||
_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("voice", out object? voice) == true && voice is string v)
|
||||
{
|
||||
if (_user.VoicesEnabled.Contains(v))
|
||||
{
|
||||
_user.DefaultTTSVoice = v;
|
||||
_logger.Information($"Default TTS voice was changed to '{v}'.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
_logger.Warning("Failed to update default TTS voice via request.");
|
||||
}
|
||||
}
|
||||
}
|
||||
49
Hermes/Socket/Requests/UpdateGroupAck.cs
Normal file
49
Hermes/Socket/Requests/UpdateGroupAck.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System.Text.Json;
|
||||
using HermesSocketLibrary.Requests.Messages;
|
||||
using Serilog;
|
||||
using TwitchChatTTS.Chat.Groups;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class UpdateGroupAck : IRequestAck
|
||||
{
|
||||
public string Name => "update_group";
|
||||
private readonly IChatterGroupManager _groups;
|
||||
private readonly JsonSerializerOptions _options;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public UpdateGroupAck(IChatterGroupManager groups, JsonSerializerOptions options, ILogger logger)
|
||||
{
|
||||
_groups = groups;
|
||||
_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 group = JsonSerializer.Deserialize<Group>(json, _options);
|
||||
if (group == null)
|
||||
{
|
||||
_logger.Warning($"Group data is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var exists = _groups.Get(group.Id);
|
||||
if (exists == null)
|
||||
{
|
||||
_logger.Warning($"Group id does not exist [group id: {group.Id}][group name: {group.Name}][group priority: {group.Priority}]");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Debug($"Updating group [group id: {group.Id}][group name: {group.Name}][group priority: {group.Priority}]");
|
||||
_groups.Modify(group);
|
||||
_logger.Information($"Group has been updated [group id: {group.Id}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
40
Hermes/Socket/Requests/UpdateGroupChatterAck.cs
Normal file
40
Hermes/Socket/Requests/UpdateGroupChatterAck.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System.Text.Json;
|
||||
using HermesSocketLibrary.Requests.Messages;
|
||||
using Serilog;
|
||||
using TwitchChatTTS.Chat.Groups;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class UpdateGroupChatterAck : IRequestAck
|
||||
{
|
||||
public string Name => "update_group_chatter";
|
||||
private readonly IChatterGroupManager _groups;
|
||||
private readonly JsonSerializerOptions _options;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public UpdateGroupChatterAck(IChatterGroupManager groups, JsonSerializerOptions options, ILogger logger)
|
||||
{
|
||||
_groups = groups;
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json)) {
|
||||
_logger.Warning($"Group Chatter JSON data is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var groupChatter = JsonSerializer.Deserialize<GroupChatter>(json, _options);
|
||||
if (groupChatter == null)
|
||||
{
|
||||
_logger.Warning($"Group Chatter data is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
_groups.Add(groupChatter.ChatterId, groupChatter.GroupId);
|
||||
_logger.Information($"Chatter has been updated [chatter label: {groupChatter.ChatterLabel}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
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}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
51
Hermes/Socket/Requests/UpdatePolicyAck.cs
Normal file
51
Hermes/Socket/Requests/UpdatePolicyAck.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System.Text.Json;
|
||||
using HermesSocketServer.Messages;
|
||||
using Serilog;
|
||||
using TwitchChatTTS.Chat.Commands.Limits;
|
||||
using TwitchChatTTS.Chat.Groups;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class UpdatePolicyAck : IRequestAck
|
||||
{
|
||||
public string Name => "update_policy";
|
||||
private readonly IChatterGroupManager _groups;
|
||||
private readonly IUsagePolicy<long> _policies;
|
||||
private readonly JsonSerializerOptions _options;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public UpdatePolicyAck(IChatterGroupManager groups, IUsagePolicy<long> policies, JsonSerializerOptions options, ILogger logger)
|
||||
{
|
||||
_groups = groups;
|
||||
_policies = policies;
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
if (json == null)
|
||||
{
|
||||
_logger.Warning($"Policy JSON data is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var policy = JsonSerializer.Deserialize<Policy>(json, _options);
|
||||
if (policy == null)
|
||||
{
|
||||
_logger.Warning($"Policy data is null.");
|
||||
return;
|
||||
}
|
||||
var group = _groups.Get(policy.GroupId.ToString());
|
||||
if (group == null)
|
||||
{
|
||||
_logger.Warning($"Policy's group id not found [group id: {policy.GroupId}][policy id: {policy.Id}]");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Debug($"Policy data loaded [policy id: {policy.Id}][path: {policy.Path}][group id: {policy.GroupId}][group name: {group.Name}]");
|
||||
_policies.Set(group.Name, policy.Path, policy.Usage, TimeSpan.FromMilliseconds(policy.Span));
|
||||
_logger.Information($"Policy has been updated [policy id: {policy.Id}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
37
Hermes/Socket/Requests/UpdateRedeemableActionAck.cs
Normal file
37
Hermes/Socket/Requests/UpdateRedeemableActionAck.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System.Text.Json;
|
||||
using HermesSocketLibrary.Requests.Messages;
|
||||
using Serilog;
|
||||
using TwitchChatTTS.Twitch.Redemptions;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class UpdateRedeemableActionAck : IRequestAck
|
||||
{
|
||||
public string Name => "update_redeemable_action";
|
||||
private readonly IRedemptionManager _redemptions;
|
||||
private readonly JsonSerializerOptions _options;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public UpdateRedeemableActionAck(IRedemptionManager redemptions, JsonSerializerOptions options, ILogger logger)
|
||||
{
|
||||
_redemptions = redemptions;
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
var action = JsonSerializer.Deserialize<RedeemableAction>(json, _options);
|
||||
if (action == null)
|
||||
{
|
||||
_logger.Warning($"Redeemable action data received is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_redemptions.Update(action))
|
||||
_logger.Information($"A redeemable action has been updated [action name: {action.Name}]");
|
||||
else
|
||||
_logger.Warning($"Failed to update an existing redeemable action [action name: {action.Name}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
42
Hermes/Socket/Requests/UpdateRedeemptionAck.cs
Normal file
42
Hermes/Socket/Requests/UpdateRedeemptionAck.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System.Text.Json;
|
||||
using HermesSocketLibrary.Requests.Messages;
|
||||
using Serilog;
|
||||
using TwitchChatTTS.Twitch.Redemptions;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class UpdateRedemptionAck : IRequestAck
|
||||
{
|
||||
public string Name => "update_redemption";
|
||||
private readonly IRedemptionManager _redemptions;
|
||||
private readonly JsonSerializerOptions _options;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public UpdateRedemptionAck(IRedemptionManager redemptions, JsonSerializerOptions options, ILogger logger)
|
||||
{
|
||||
_redemptions = redemptions;
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
if (json == null) {
|
||||
_logger.Warning($"Redemption JSON data received is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var redemption = JsonSerializer.Deserialize<Redemption>(json, _options);
|
||||
if (redemption == null)
|
||||
{
|
||||
_logger.Warning($"Redemption data received is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_redemptions.Update(redemption))
|
||||
_logger.Information($"A redemption has been updated [redemption id: {redemption.Id}][twitch redemption id: {redemption.RedemptionId}]");
|
||||
else
|
||||
_logger.Warning($"Failed to update an existing redemption [redemption id: {redemption.Id}][twitch redemption id: {redemption.RedemptionId}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
63
Hermes/Socket/Requests/UpdateTTSFilterAck.cs
Normal file
63
Hermes/Socket/Requests/UpdateTTSFilterAck.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using HermesSocketLibrary.Requests.Messages;
|
||||
using Serilog;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class UpdateTTSFilterAck : IRequestAck
|
||||
{
|
||||
public string Name => "update_tts_filter";
|
||||
private readonly User _user;
|
||||
private readonly JsonSerializerOptions _options;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public UpdateTTSFilterAck(User user, JsonSerializerOptions options, ILogger logger)
|
||||
{
|
||||
_user = user;
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
if (json == null)
|
||||
{
|
||||
_logger.Warning($"TTS Filter JSON data is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var filter = JsonSerializer.Deserialize<TTSWordFilter>(json, _options);
|
||||
if (filter == null)
|
||||
{
|
||||
_logger.Warning($"TTS Filter data is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Debug($"Filter data [filter id: {filter.Id}][search: {filter.Search}][group id: {filter.Replace}]");
|
||||
var current = _user.RegexFilters.FirstOrDefault(f => f.Id == filter.Id);
|
||||
if (current == null)
|
||||
{
|
||||
_logger.Warning($"TTS Filter doest exist by id [filter id: {filter.Id}]");
|
||||
return;
|
||||
}
|
||||
|
||||
current.Search = filter.Search;
|
||||
current.Replace = filter.Replace;
|
||||
current.Flag = filter.Flag;
|
||||
|
||||
try
|
||||
{
|
||||
var re = new Regex(current.Search, ((RegexOptions)current.Flag) | RegexOptions.Compiled);
|
||||
re.Match(string.Empty);
|
||||
current.Regex = re;
|
||||
}
|
||||
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}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
52
Hermes/Socket/Requests/UpdateTTSUserAck.cs
Normal file
52
Hermes/Socket/Requests/UpdateTTSUserAck.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using Serilog;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class UpdateTTSUserAck : IRequestAck
|
||||
{
|
||||
public string Name => "update_tts_user";
|
||||
private readonly User _user;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public UpdateTTSUserAck(User user, ILogger logger)
|
||||
{
|
||||
_user = user;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
if (requestData == null)
|
||||
{
|
||||
_logger.Warning("Request data is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!long.TryParse(requestData["chatter"].ToString(), out long chatterId))
|
||||
{
|
||||
_logger.Warning($"Failed to parse chatter id [chatter id: {requestData["chatter"]}]");
|
||||
return;
|
||||
}
|
||||
if (chatterId <= 0)
|
||||
{
|
||||
_logger.Warning("Chatter Id is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
var voiceId = requestData["voice"].ToString();
|
||||
if (string.IsNullOrEmpty(voiceId))
|
||||
{
|
||||
_logger.Warning("Voice Id is invalid.");
|
||||
return;
|
||||
}
|
||||
if (!_user.VoicesAvailable.TryGetValue(voiceId, out var voiceName))
|
||||
{
|
||||
_logger.Warning("Voice Id does not exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
_user.VoicesSelected[chatterId] = voiceId;
|
||||
_logger.Information($"Updated a TTS user's voice [user id: {_user.TwitchUserId}][voice: {voiceId}][voice name: {voiceName}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
47
Hermes/Socket/Requests/UpdateTTSVoiceAck.cs
Normal file
47
Hermes/Socket/Requests/UpdateTTSVoiceAck.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using Serilog;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class UpdateTTSVoiceAck : IRequestAck
|
||||
{
|
||||
public string Name => "update_tts_voice";
|
||||
private readonly User _user;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public UpdateTTSVoiceAck(User user, ILogger logger)
|
||||
{
|
||||
_user = user;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
if (requestData == null)
|
||||
{
|
||||
_logger.Warning("Request data is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
var voice = requestData["voice"].ToString();
|
||||
var voiceId = requestData["idd"].ToString();
|
||||
if (string.IsNullOrEmpty(voice))
|
||||
{
|
||||
_logger.Warning("Voice name is invalid.");
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrEmpty(voiceId))
|
||||
{
|
||||
_logger.Warning("Voice Id is invalid.");
|
||||
return;
|
||||
}
|
||||
if (_user.VoicesAvailable.ContainsKey(voiceId))
|
||||
{
|
||||
_logger.Warning($"Voice Id already exists [voice id: {voiceId}]");
|
||||
return;
|
||||
}
|
||||
|
||||
_user.VoicesAvailable[voiceId] = voice;
|
||||
_logger.Information($"Created a new tts voice [voice id: {voiceId}][voice name: {voice}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
58
Hermes/Socket/Requests/UpdateTTSVoiceStateAck.cs
Normal file
58
Hermes/Socket/Requests/UpdateTTSVoiceStateAck.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using Serilog;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Requests
|
||||
{
|
||||
public class UpdateTTSVoiceStateAck : IRequestAck
|
||||
{
|
||||
public string Name => "update_tts_voice_state";
|
||||
private readonly User _user;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public UpdateTTSVoiceStateAck(User user, ILogger logger)
|
||||
{
|
||||
_user = user;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Acknowledge(string requestId, string? json, IDictionary<string, object>? requestData)
|
||||
{
|
||||
if (requestData == null)
|
||||
{
|
||||
_logger.Warning("Request data is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!long.TryParse(requestData["chatter"].ToString(), out long chatterId))
|
||||
{
|
||||
_logger.Warning($"Failed to parse chatter id [chatter id: {requestData["chatter"]}]");
|
||||
return;
|
||||
}
|
||||
if (chatterId <= 0)
|
||||
{
|
||||
_logger.Warning("Chatter Id is invalid.");
|
||||
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.");
|
||||
return;
|
||||
}
|
||||
if (!_user.VoicesAvailable.TryGetValue(voiceId, out var voiceName))
|
||||
{
|
||||
_logger.Warning("Voice Id does not exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
_user.VoicesSelected[chatterId] = voiceId;
|
||||
_logger.Information($"Updated a TTS user's voice [user id: {userId}][voice: {voiceId}][voice name: {voiceName}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,8 @@ namespace TwitchChatTTS.Hermes
|
||||
{
|
||||
public int MajorVersion { get; set; }
|
||||
public int MinorVersion { get; set; }
|
||||
public string Download { get; set; }
|
||||
public string Changelog { get; set; }
|
||||
public int? PatchVersion { get; set; }
|
||||
public required string Download { get; set; }
|
||||
public required string Changelog { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
public class TTSVoice
|
||||
{
|
||||
public string Label { get; set; }
|
||||
public int Value { get; set; }
|
||||
public string? Gender { get; set; }
|
||||
public string? Language { get; set; }
|
||||
}
|
||||
|
||||
public class TTSChatterSelectedVoice
|
||||
{
|
||||
public long ChatterId { get; set; }
|
||||
public string Voice { get; set; }
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
public class TwitchBotToken {
|
||||
public string? ClientId { get; set; }
|
||||
public string? ClientSecret { get; set; }
|
||||
public string? AccessToken { get; set; }
|
||||
public string? RefreshToken { get; set; }
|
||||
public string? BroadcasterId { get; set; }
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
public class TwitchConnection {
|
||||
public string? Id { get; set; }
|
||||
public string? Secret { get; set; }
|
||||
public string? BroadcasterId { get; set; }
|
||||
public string? Username { get; set; }
|
||||
public string? UserId { get; set; }
|
||||
}
|
||||
@@ -2,8 +2,8 @@ namespace TwitchChatTTS.OBS.Socket.Data
|
||||
{
|
||||
public class EventMessage
|
||||
{
|
||||
public string EventType { get; set; }
|
||||
public required string EventType { get; set; }
|
||||
public int EventIntent { get; set; }
|
||||
public Dictionary<string, object> EventData { get; set; }
|
||||
public Dictionary<string, object>? EventData { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,13 @@ namespace TwitchChatTTS.OBS.Socket.Data
|
||||
{
|
||||
public class HelloMessage
|
||||
{
|
||||
public string ObsWebSocketVersion { get; set; }
|
||||
public required string ObsWebSocketVersion { get; set; }
|
||||
public int RpcVersion { get; set; }
|
||||
public AuthenticationMessage Authentication { get; set; }
|
||||
public AuthenticationMessage? Authentication { get; set; }
|
||||
}
|
||||
|
||||
public class AuthenticationMessage {
|
||||
public string Challenge { get; set; }
|
||||
public string Salt { get; set; }
|
||||
public required string Challenge { get; set; }
|
||||
public required string Salt { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,9 @@ namespace TwitchChatTTS.OBS.Socket.Data
|
||||
{
|
||||
public class OBSSceneItem
|
||||
{
|
||||
public string SourceUuid { get; set; }
|
||||
public string SourceName { get; set; }
|
||||
public string SourceType { get; set; }
|
||||
public required string SourceUuid { get; set; }
|
||||
public required string SourceName { get; set; }
|
||||
public required string SourceType { get; set; }
|
||||
public int SceneItemId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace TwitchChatTTS.OBS.Socket.Data
|
||||
{
|
||||
public class RequestBatchMessage
|
||||
|
||||
@@ -2,7 +2,7 @@ namespace TwitchChatTTS.OBS.Socket.Data
|
||||
{
|
||||
public class RequestBatchResponseMessage
|
||||
{
|
||||
public string RequestId { get; set; }
|
||||
public IEnumerable<object> Results { get; set; }
|
||||
public required string RequestId { get; set; }
|
||||
public required IEnumerable<object> Results { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ namespace TwitchChatTTS.OBS.Socket.Data
|
||||
public class RequestMessage
|
||||
{
|
||||
public string RequestType { get; set; }
|
||||
public string RequestId { get; set; }
|
||||
public string? RequestId { get; set; }
|
||||
public Dictionary<string, object> RequestData { get; set; }
|
||||
|
||||
public RequestMessage(string type, string id, Dictionary<string, object> data)
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace TwitchChatTTS.OBS.Socket.Data
|
||||
{
|
||||
public class RequestResponseMessage
|
||||
{
|
||||
public string RequestType { get; set; }
|
||||
public string RequestId { get; set; }
|
||||
public object RequestStatus { get; set; }
|
||||
public Dictionary<string, object> ResponseData { get; set; }
|
||||
public required string RequestType { get; set; }
|
||||
public required string RequestId { get; set; }
|
||||
public required object RequestStatus { get; set; }
|
||||
public Dictionary<string, object>? ResponseData { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ namespace TwitchChatTTS.OBS.Socket.Data
|
||||
public int Alignment { get; set; }
|
||||
public int BoundsAlignment { get; set; }
|
||||
public double BoundsHeight { get; set; }
|
||||
public string BoundsType { get; set; }
|
||||
public required string BoundsType { get; set; }
|
||||
public double BoundsWidth { get; set; }
|
||||
public int CropBottom { get; set; }
|
||||
public int CropLeft { get; set; }
|
||||
|
||||
@@ -17,19 +17,17 @@ namespace TwitchChatTTS.OBS.Socket.Handlers
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task Execute<Data>(SocketClient<WebSocketMessage> sender, Data data)
|
||||
public Task Execute<Data>(SocketClient<WebSocketMessage> sender, Data data)
|
||||
{
|
||||
if (data is not EventMessage message || message == null)
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
if (sender is not OBSSocketClient obs)
|
||||
return;
|
||||
|
||||
return Task.CompletedTask;
|
||||
switch (message.EventType)
|
||||
{
|
||||
case "StreamStateChanged":
|
||||
if (sender is not OBSSocketClient client)
|
||||
return;
|
||||
|
||||
return Task.CompletedTask;
|
||||
string? raw_state = message.EventData["outputState"].ToString();
|
||||
string? state = raw_state?.Substring(21).ToLower();
|
||||
obs.Streaming = message.EventData["outputActive"].ToString()!.ToLower() == "true";
|
||||
@@ -44,6 +42,8 @@ namespace TwitchChatTTS.OBS.Socket.Handlers
|
||||
_logger.Debug(message.EventType + " EVENT: " + string.Join(" | ", message.EventData?.Select(x => x.Key + "=" + x.Value?.ToString()) ?? Array.Empty<string>()));
|
||||
break;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,12 +18,12 @@ namespace TwitchChatTTS.OBS.Socket.Handlers
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task Execute<Data>(SocketClient<WebSocketMessage> sender, Data data)
|
||||
public Task Execute<Data>(SocketClient<WebSocketMessage> sender, Data data)
|
||||
{
|
||||
if (data is not RequestResponseMessage message || message == null)
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
if (sender is not OBSSocketClient obs)
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
|
||||
_logger.Debug($"Received an OBS request response [obs request id: {message.RequestId}]");
|
||||
|
||||
@@ -31,13 +31,12 @@ namespace TwitchChatTTS.OBS.Socket.Handlers
|
||||
if (requestData == null)
|
||||
{
|
||||
_logger.Warning($"OBS Request Response not being processed: request not stored [obs request id: {message.RequestId}]");
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var request = requestData.Message;
|
||||
if (request == null)
|
||||
return;
|
||||
|
||||
return Task.CompletedTask;
|
||||
try
|
||||
{
|
||||
switch (request.RequestType)
|
||||
@@ -50,26 +49,26 @@ namespace TwitchChatTTS.OBS.Socket.Handlers
|
||||
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;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
if (!request.RequestData.TryGetValue("sourceName", out object? sourceName) || sourceName == null)
|
||||
{
|
||||
_logger.Warning($"Failed to find the scene item name that was requested [scene: {sceneName}][obs request id: {message.RequestId}]");
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
if (message.ResponseData == null)
|
||||
{
|
||||
_logger.Warning($"OBS Response is null [scene: {sceneName}][scene item: {sourceName}][obs request id: {message.RequestId}]");
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
if (!message.ResponseData.TryGetValue("sceneItemId", out object? sceneItemId) || sceneItemId == null)
|
||||
{
|
||||
_logger.Warning($"Failed to fetch the scene item id [scene: {sceneName}][scene item: {sourceName}][obs request id: {message.RequestId}]");
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
_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);
|
||||
obs.AddSourceId(sourceName.ToString()!, long.Parse(sceneItemId.ToString()!));
|
||||
|
||||
requestData.ResponseValues = message.ResponseData;
|
||||
break;
|
||||
@@ -79,22 +78,22 @@ namespace TwitchChatTTS.OBS.Socket.Handlers
|
||||
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;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
if (!request.RequestData.TryGetValue("sceneItemId", out object? sceneItemId) || sceneItemId == null)
|
||||
{
|
||||
_logger.Warning($"Failed to find the scene item name that was requested [scene: {sceneName}][obs request id: {message.RequestId}]");
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
if (message.ResponseData == null)
|
||||
{
|
||||
_logger.Warning($"OBS Response is null [scene: {sceneName}][scene item id: {sceneItemId}][obs request id: {message.RequestId}]");
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
if (!message.ResponseData.TryGetValue("sceneItemTransform", out object? transformData) || transformData == null)
|
||||
{
|
||||
_logger.Warning($"Failed to fetch the OBS transformation data [obs request id: {message.RequestId}]");
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
_logger.Debug($"Fetched OBS transformation data [scene: {sceneName}][scene item id: {sceneItemId}][transformation: {transformData}][obs request id: {message.RequestId}]");
|
||||
@@ -106,22 +105,22 @@ namespace TwitchChatTTS.OBS.Socket.Handlers
|
||||
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;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
if (!request.RequestData.TryGetValue("sceneItemId", out object? sceneItemId) || sceneItemId == null)
|
||||
{
|
||||
_logger.Warning($"Failed to find the scene item name that was requested [scene: {sceneName}][obs request id: {message.RequestId}]");
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
if (message.ResponseData == null)
|
||||
{
|
||||
_logger.Warning($"OBS Response is null [scene: {sceneName}][scene item id: {sceneItemId}][obs request id: {message.RequestId}]");
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
if (!message.ResponseData.TryGetValue("sceneItemEnabled", out object? sceneItemVisibility) || sceneItemVisibility == null)
|
||||
{
|
||||
_logger.Warning($"Failed to fetch the scene item visibility [scene: {sceneName}][scene item id: {sceneItemId}][obs request id: {message.RequestId}]");
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
_logger.Debug($"Fetched OBS scene item visibility [scene: {sceneName}][scene item id: {sceneItemId}][visibility: {sceneItemVisibility}][obs request id: {message.RequestId}]");
|
||||
@@ -133,12 +132,12 @@ namespace TwitchChatTTS.OBS.Socket.Handlers
|
||||
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;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
if (!request.RequestData.TryGetValue("sceneItemId", out object? sceneItemId) || sceneItemId == null)
|
||||
{
|
||||
_logger.Warning($"Failed to find the scene item name that was requested [scene: {sceneName}][obs request id: {message.RequestId}]");
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
_logger.Debug($"Received response from OBS for updating scene item transformation [scene: {sceneName}][scene item id: {sceneItemId}][obs request id: {message.RequestId}]");
|
||||
break;
|
||||
@@ -148,12 +147,12 @@ namespace TwitchChatTTS.OBS.Socket.Handlers
|
||||
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;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
if (!request.RequestData.TryGetValue("sceneItemId", out object? sceneItemId) || sceneItemId == null)
|
||||
{
|
||||
_logger.Warning($"Failed to find the scene item name that was requested [scene: {sceneName}][obs request id: {message.RequestId}]");
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
_logger.Debug($"Received response from OBS for updating scene item visibility [scene: {sceneName}][scene item id: {sceneItemId}][obs request id: {message.RequestId}]");
|
||||
break;
|
||||
@@ -163,12 +162,12 @@ namespace TwitchChatTTS.OBS.Socket.Handlers
|
||||
if (message.ResponseData == null)
|
||||
{
|
||||
_logger.Warning($"OBS Response is null [obs request id: {message.RequestId}]");
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
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;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
var groups = JsonSerializer.Deserialize<IEnumerable<string>>(value.ToString());
|
||||
_logger.Debug($"Fetched OBS groups [obs request id: {message.RequestId}]");
|
||||
@@ -183,17 +182,17 @@ namespace TwitchChatTTS.OBS.Socket.Handlers
|
||||
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;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
if (message.ResponseData == null)
|
||||
{
|
||||
_logger.Warning($"OBS Response is null [scene: {sceneName}][obs request id: {message.RequestId}]");
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
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;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
_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()
|
||||
@@ -203,7 +202,7 @@ namespace TwitchChatTTS.OBS.Socket.Handlers
|
||||
if (sceneItems == null)
|
||||
{
|
||||
_logger.Warning($"Failed to deserialize the data received [scene: {sceneName}][obs request id: {message.RequestId}]");
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
foreach (var sceneItem in sceneItems)
|
||||
@@ -220,7 +219,7 @@ namespace TwitchChatTTS.OBS.Socket.Handlers
|
||||
if (!request.RequestData.TryGetValue("sleepMillis", out object? sleepMillis) || sleepMillis == null)
|
||||
{
|
||||
_logger.Warning($"Failed to find the amount of time to sleep for [obs request id: {message.RequestId}]");
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
_logger.Debug($"Received response from OBS for sleeping [sleep: {sleepMillis}][obs request id: {message.RequestId}]");
|
||||
break;
|
||||
@@ -230,12 +229,12 @@ namespace TwitchChatTTS.OBS.Socket.Handlers
|
||||
if (message.ResponseData == null)
|
||||
{
|
||||
_logger.Warning($"OBS Response is null [obs request id: {message.RequestId}]");
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
if (!message.ResponseData.TryGetValue("outputActive", out object? outputActive) || outputActive == null)
|
||||
{
|
||||
_logger.Warning($"Failed to fetch the scene item visibility [obs request id: {message.RequestId}]");
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
obs.Streaming = outputActive?.ToString()!.ToLower() == "true";
|
||||
@@ -257,6 +256,8 @@ namespace TwitchChatTTS.OBS.Socket.Handlers
|
||||
if (requestData.Callback != null)
|
||||
requestData.Callback(requestData.ResponseValues);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,7 @@ using Serilog;
|
||||
using System.Text.Json;
|
||||
using System.Collections.Concurrent;
|
||||
using TwitchChatTTS.OBS.Socket.Data;
|
||||
using System.Timers;
|
||||
using System.Net.WebSockets;
|
||||
using CommonSocketLibrary.Backoff;
|
||||
|
||||
namespace TwitchChatTTS.OBS.Socket
|
||||
{
|
||||
@@ -16,8 +15,8 @@ namespace TwitchChatTTS.OBS.Socket
|
||||
private readonly IDictionary<string, long> _sourceIds;
|
||||
private string? URL;
|
||||
|
||||
private readonly IBackoff _backoff;
|
||||
private readonly Configuration _configuration;
|
||||
private System.Timers.Timer _reconnectTimer;
|
||||
|
||||
public bool Connected { get; set; }
|
||||
public bool Identified { get; set; }
|
||||
@@ -26,6 +25,7 @@ namespace TwitchChatTTS.OBS.Socket
|
||||
|
||||
public OBSSocketClient(
|
||||
Configuration configuration,
|
||||
[FromKeyedServices("hermes")] IBackoff backoff,
|
||||
[FromKeyedServices("obs")] IEnumerable<IWebSocketHandler> handlers,
|
||||
[FromKeyedServices("obs")] MessageTypeManager<IWebSocketHandler> typeManager,
|
||||
ILogger logger
|
||||
@@ -35,12 +35,9 @@ namespace TwitchChatTTS.OBS.Socket
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
}, logger)
|
||||
{
|
||||
_backoff = backoff;
|
||||
_configuration = configuration;
|
||||
|
||||
_reconnectTimer = new System.Timers.Timer(TimeSpan.FromSeconds(30));
|
||||
_reconnectTimer.Elapsed += async (sender, e) => await Reconnect(e);
|
||||
_reconnectTimer.Enabled = false;
|
||||
|
||||
_requests = new ConcurrentDictionary<string, RequestData>();
|
||||
_sourceIds = new Dictionary<string, long>();
|
||||
}
|
||||
@@ -51,18 +48,19 @@ namespace TwitchChatTTS.OBS.Socket
|
||||
OnConnected += (sender, e) =>
|
||||
{
|
||||
Connected = true;
|
||||
_reconnectTimer.Enabled = false;
|
||||
_logger.Information("OBS websocket client connected.");
|
||||
};
|
||||
|
||||
OnDisconnected += (sender, e) =>
|
||||
OnDisconnected += async (sender, e) =>
|
||||
{
|
||||
_reconnectTimer.Enabled = Identified;
|
||||
_logger.Information($"OBS websocket client disconnected [status: {e.Status}][reason: {e.Reason}] " + (Identified ? "Will be attempting to reconnect every 30 seconds." : "Will not be attempting to reconnect."));
|
||||
|
||||
Connected = false;
|
||||
Identified = false;
|
||||
Streaming = false;
|
||||
|
||||
if (Identified)
|
||||
await Reconnect(_backoff);
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_configuration.Obs?.Host) && _configuration.Obs?.Port != null)
|
||||
@@ -115,34 +113,6 @@ namespace TwitchChatTTS.OBS.Socket
|
||||
await handler.Execute(this, message);
|
||||
}
|
||||
|
||||
private async Task Reconnect(ElapsedEventArgs e)
|
||||
{
|
||||
if (Connected)
|
||||
{
|
||||
try
|
||||
{
|
||||
await DisconnectAsync(new SocketDisconnectionEventArgs(WebSocketCloseStatus.Empty.ToString(), ""));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.Error("Failed to disconnect from OBS websocket server.");
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await Connect();
|
||||
}
|
||||
catch (WebSocketException wse) when (wse.Message.Contains("502"))
|
||||
{
|
||||
_logger.Error($"OBS websocket server cannot be found. Be sure the server is on by looking at OBS > Tools > Websocket Server Settings [code: {wse.ErrorCode}]");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Failed to reconnect to OBS websocket server.");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Send(IEnumerable<RequestMessage> messages)
|
||||
{
|
||||
if (!Connected)
|
||||
@@ -167,7 +137,7 @@ namespace TwitchChatTTS.OBS.Socket
|
||||
await Send(8, new RequestBatchMessage(uid, list));
|
||||
}
|
||||
|
||||
public async Task Send(RequestMessage message, Action<Dictionary<string, object>>? callback = null)
|
||||
public async Task Send(RequestMessage message, Action<Dictionary<string, object>?>? callback = null)
|
||||
{
|
||||
if (!Connected)
|
||||
{
|
||||
@@ -335,7 +305,7 @@ namespace TwitchChatTTS.OBS.Socket
|
||||
}
|
||||
|
||||
var m = new RequestMessage("GetSceneItemId", string.Empty, new Dictionary<string, object>() { { "sceneName", sceneName }, { "sourceName", sceneItemName } });
|
||||
await Send(m, async (d) =>
|
||||
await Send(m, (d) =>
|
||||
{
|
||||
if (d == null || !d.TryGetValue("sceneItemId", out object? value) || value == null || !long.TryParse(value.ToString(), out long sceneItemId))
|
||||
return;
|
||||
@@ -357,7 +327,7 @@ namespace TwitchChatTTS.OBS.Socket
|
||||
public RequestMessage Message { get; }
|
||||
public string ParentId { get; }
|
||||
public Dictionary<string, object>? ResponseValues { get; set; }
|
||||
public Action<Dictionary<string, object>>? Callback { get; set; }
|
||||
public Action<Dictionary<string, object>?>? Callback { get; set; }
|
||||
|
||||
public RequestData(RequestMessage message, string parentId)
|
||||
{
|
||||
|
||||
@@ -23,12 +23,25 @@ public class SevenApiClient
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<EmoteSet?> FetchChannelEmoteSet(string twitchId)
|
||||
public async Task<EmoteSet?> FetchChannelEmoteSet(long twitchId)
|
||||
{
|
||||
if (twitchId <= 0)
|
||||
{
|
||||
_logger.Warning("No valid Twitch Id was given for 7tv emotes.");
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Debug($"Fetching 7tv information using Twitch Id [twitch id: {twitchId}]");
|
||||
var details = await _web.GetJson<UserDetails>($"{API_URL}/users/twitch/" + twitchId);
|
||||
return details?.EmoteSet;
|
||||
_logger.Information($"Fetched 7tv emotes [count: {details?.EmoteSet.EmoteCount ?? -1}]");
|
||||
if (details?.EmoteSet == null)
|
||||
{
|
||||
_logger.Warning("Could not find 7tv emotes linked to your Twitch account.");
|
||||
return null;
|
||||
}
|
||||
return details.EmoteSet;
|
||||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
|
||||
@@ -2,10 +2,10 @@ namespace TwitchChatTTS.Seven.Socket.Data
|
||||
{
|
||||
public class ChangeMapMessage
|
||||
{
|
||||
public object Id { get; set; }
|
||||
public required object Id { get; set; }
|
||||
public byte Kind { get; set; }
|
||||
public bool? Contextual { get; set; }
|
||||
public object Actor { get; set; }
|
||||
public required object Actor { get; set; }
|
||||
public IEnumerable<ChangeField>? Added { get; set; }
|
||||
public IEnumerable<ChangeField>? Updated { get; set; }
|
||||
public IEnumerable<ChangeField>? Removed { get; set; }
|
||||
@@ -14,17 +14,17 @@ namespace TwitchChatTTS.Seven.Socket.Data
|
||||
}
|
||||
|
||||
public class ChangeField {
|
||||
public string Key { get; set; }
|
||||
public required string Key { get; set; }
|
||||
public int? Index { get; set; }
|
||||
public bool Nested { get; set; }
|
||||
public object OldValue { get; set; }
|
||||
public object Value { get; set; }
|
||||
public required object OldValue { get; set; }
|
||||
public required object Value { get; set; }
|
||||
}
|
||||
|
||||
public class EmoteField {
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string ActorId { get; set; }
|
||||
public required string Id { get; set; }
|
||||
public required string Name { get; set; }
|
||||
public required string ActorId { get; set; }
|
||||
public int Flags { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ namespace TwitchChatTTS.Seven.Socket.Data
|
||||
{
|
||||
public class DispatchMessage
|
||||
{
|
||||
public object EventType { get; set; }
|
||||
public ChangeMapMessage Body { get; set; }
|
||||
public required object EventType { get; set; }
|
||||
public required ChangeMapMessage Body { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,6 @@ namespace TwitchChatTTS.Seven.Socket.Data
|
||||
public class EndOfStreamMessage
|
||||
{
|
||||
public int Code { get; set; }
|
||||
public string Message { get; set; }
|
||||
public required string Message { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ namespace TwitchChatTTS.Seven.Socket.Data
|
||||
{
|
||||
public class ErrorMessage
|
||||
{
|
||||
|
||||
public Exception? Exception { get; set; }
|
||||
public string? Message { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,6 @@ namespace TwitchChatTTS.Seven.Socket.Data
|
||||
{
|
||||
public class ReconnectMessage
|
||||
{
|
||||
public string Reason { get; set; }
|
||||
public required string Reason { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,6 @@ namespace TwitchChatTTS.Seven.Socket.Data
|
||||
{
|
||||
public class ResumeMessage
|
||||
{
|
||||
public string SessionId { get; set; }
|
||||
public required string SessionId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ namespace TwitchChatTTS.Seven.Socket.Data
|
||||
public class SevenHelloMessage
|
||||
{
|
||||
public uint HeartbeatInterval { get; set; }
|
||||
public string SessionId { get; set; }
|
||||
public required string SessionId { get; set; }
|
||||
public int SubscriptionLimit { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ namespace TwitchChatTTS.Seven.Socket.Data
|
||||
{
|
||||
public class SubscribeMessage
|
||||
{
|
||||
public string? Type { get; set; }
|
||||
public required string Type { get; set; }
|
||||
public IDictionary<string, string>? Condition { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ namespace TwitchChatTTS.Seven.Socket.Data
|
||||
{
|
||||
public class UnsubscribeMessage
|
||||
{
|
||||
public string Type { get; set; }
|
||||
public required string Type { get; set; }
|
||||
public IDictionary<string, string>? Condition { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -9,26 +9,29 @@ 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 async Task Execute<Data>(SocketClient<WebSocketMessage> sender, Data data)
|
||||
public Task Execute<Data>(SocketClient<WebSocketMessage> sender, Data data)
|
||||
{
|
||||
if (data is not DispatchMessage message || message == null)
|
||||
return;
|
||||
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;
|
||||
}
|
||||
|
||||
private void ApplyChanges(IEnumerable<ChangeField>? fields, Func<ChangeField, object> getter, bool removing, Func<ChangeField, object>? updater = null)
|
||||
@@ -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;
|
||||
@@ -15,7 +14,7 @@ namespace TwitchChatTTS.Seven.Socket.Handlers
|
||||
return;
|
||||
|
||||
var code = message.Code - 4000;
|
||||
await sender.DisconnectAsync(new SocketDisconnectionEventArgs(WebSocketCloseStatus.Empty.ToString(), code.ToString()));
|
||||
await sender.DisconnectAsync(new SocketDisconnectionEventArgs(message.Message, code.ToString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,10 +15,16 @@ namespace TwitchChatTTS.Seven.Socket.Handlers
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task Execute<Data>(SocketClient<WebSocketMessage> sender, Data data)
|
||||
public Task Execute<Data>(SocketClient<WebSocketMessage> sender, Data data)
|
||||
{
|
||||
if (data is not ErrorMessage message || message == null)
|
||||
return;
|
||||
if (data is not ErrorMessage message || message == null || message.Exception == null && message.Message == null)
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (message.Exception != null)
|
||||
_logger.Error(message.Exception, message.Message ?? string.Empty);
|
||||
else
|
||||
_logger.Error(message.Message!);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,15 +15,16 @@ namespace TwitchChatTTS.Seven.Socket.Handlers
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task Execute<Data>(SocketClient<WebSocketMessage> sender, Data data)
|
||||
public Task Execute<Data>(SocketClient<WebSocketMessage> sender, Data data)
|
||||
{
|
||||
if (data is not SevenHelloMessage message || message == null)
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
if (sender is not SevenSocketClient seven || seven == null)
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
|
||||
seven.ConnectionDetails = message;
|
||||
_logger.Debug("Received hello handshake ack.");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,18 +4,18 @@ namespace TwitchChatTTS.Seven
|
||||
{
|
||||
public class UserDetails
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Platform { get; set; }
|
||||
public string Username { get; set; }
|
||||
public required string Id { get; set; }
|
||||
public required string Platform { get; set; }
|
||||
public required string DisplayName { get; set; }
|
||||
public int EmoteCapacity { get; set; }
|
||||
public string EmoteSetId { get; set; }
|
||||
public EmoteSet EmoteSet { get; set; }
|
||||
public SevenUser User { get; set; }
|
||||
public required string EmoteSetId { get; set; }
|
||||
public required EmoteSet EmoteSet { get; set; }
|
||||
public required SevenUser User { get; set; }
|
||||
}
|
||||
|
||||
public class SevenUser
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Username { get; set; }
|
||||
public required string Id { get; set; }
|
||||
public required string Username { get; set; }
|
||||
}
|
||||
}
|
||||
64
Startup.cs
64
Startup.cs
@@ -33,6 +33,10 @@ using TwitchChatTTS.Chat.Speech;
|
||||
using TwitchChatTTS.Chat.Messaging;
|
||||
using TwitchChatTTS.Chat.Observers;
|
||||
using TwitchChatTTS.Chat.Commands.Limits;
|
||||
using TwitchChatTTS.Hermes.Socket.Requests;
|
||||
using TwitchChatTTS.Bus;
|
||||
using TwitchChatTTS.Veadotube;
|
||||
using TwitchChatTTS.Veadotube.Handlers;
|
||||
|
||||
// dotnet publish -r linux-x64 -p:PublishSingleFile=true --self-contained true
|
||||
// dotnet publish -r win-x64 -p:PublishSingleFile=true --self-contained true
|
||||
@@ -78,11 +82,52 @@ s.AddSingleton<IChatCommand, OBSCommand>();
|
||||
s.AddSingleton<IChatCommand, TTSCommand>();
|
||||
s.AddSingleton<IChatCommand, VersionCommand>();
|
||||
s.AddSingleton<ICommandBuilder, CommandBuilder>();
|
||||
s.AddSingleton<IChatterGroupManager, ChatterGroupManager>();
|
||||
s.AddSingleton<IGroupPermissionManager, GroupPermissionManager>();
|
||||
s.AddSingleton<ICommandManager, CommandManager>();
|
||||
s.AddTransient<ICommandFactory, CommandFactory>();
|
||||
|
||||
// Request acks
|
||||
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>();
|
||||
s.AddTransient<IRequestAck, CreateTTSFilterAck>();
|
||||
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>();
|
||||
s.AddTransient<IRequestAck, DeleteTTSFilterAck>();
|
||||
s.AddTransient<IRequestAck, DeleteTTSVoiceAck>();
|
||||
s.AddTransient<IRequestAck, GetChatterIdsAck>();
|
||||
s.AddTransient<IRequestAck, GetConnectionsAck>();
|
||||
s.AddTransient<IRequestAck, GetDefaultTTSVoiceAck>();
|
||||
s.AddTransient<IRequestAck, GetEmotesAck>();
|
||||
s.AddTransient<IRequestAck, GetEnabledTTSVoicesAck>();
|
||||
s.AddTransient<IRequestAck, GetPermissionsAck>();
|
||||
s.AddTransient<IRequestAck, GetPoliciesAck>();
|
||||
s.AddTransient<IRequestAck, GetRedeemableActionsAck>();
|
||||
s.AddTransient<IRequestAck, GetRedemptionsAck>();
|
||||
s.AddTransient<IRequestAck, GetTTSUsersAck>();
|
||||
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>();
|
||||
s.AddTransient<IRequestAck, UpdateRedemptionAck>();
|
||||
s.AddTransient<IRequestAck, UpdateTTSFilterAck>();
|
||||
s.AddTransient<IRequestAck, UpdateTTSUserAck>();
|
||||
s.AddTransient<IRequestAck, UpdateTTSVoiceAck>();
|
||||
s.AddTransient<IRequestAck, UpdateTTSVoiceStateAck>();
|
||||
|
||||
s.AddSingleton<TTSPlayer>();
|
||||
s.AddSingleton<IRedemptionManager, RedemptionManager>();
|
||||
s.AddSingleton<HermesApiClient>();
|
||||
@@ -91,13 +136,18 @@ s.AddSingleton<TwitchApiClient>();
|
||||
s.AddSingleton<SevenApiClient>();
|
||||
s.AddSingleton<IEmoteDatabase, EmoteDatabase>();
|
||||
|
||||
s.AddSingleton<ServiceBusCentral>();
|
||||
|
||||
s.AddSingleton<TTSConsumer>();
|
||||
s.AddSingleton<TTSPublisher>();
|
||||
|
||||
s.AddSingleton<IChatMessageReader, ChatMessageReader>();
|
||||
s.AddSingleton<IUsagePolicy<long>, UsagePolicy<long>>();
|
||||
s.AddSingleton<IChatterGroupManager, ChatterGroupManager>();
|
||||
s.AddSingleton<IGroupPermissionManager, GroupPermissionManager>();
|
||||
|
||||
// OBS websocket
|
||||
s.AddKeyedSingleton<IBackoff>("obs", new ExponentialBackoff(1000, 120 * 1000));
|
||||
s.AddKeyedSingleton<IWebSocketHandler, HelloHandler>("obs");
|
||||
s.AddKeyedSingleton<IWebSocketHandler, IdentifiedHandler>("obs");
|
||||
s.AddKeyedSingleton<IWebSocketHandler, RequestResponseHandler>("obs");
|
||||
@@ -118,6 +168,12 @@ s.AddKeyedSingleton<IWebSocketHandler, EndOfStreamHandler>("7tv");
|
||||
s.AddKeyedSingleton<MessageTypeManager<IWebSocketHandler>, SevenMessageTypeManager>("7tv");
|
||||
s.AddKeyedSingleton<SocketClient<WebSocketMessage>, SevenSocketClient>("7tv");
|
||||
|
||||
// Veadotube
|
||||
s.AddKeyedSingleton<IVeadotubeMessageHandler, FetchStatesHandler>("veadotube");
|
||||
|
||||
s.AddKeyedSingleton<MessageTypeManager<IWebSocketHandler>, VeadoMessageTypeManager>("veadotube");
|
||||
s.AddKeyedSingleton<SocketClient<object>, VeadoSocketClient>("veadotube");
|
||||
|
||||
// Nightbot
|
||||
s.AddSingleton<NightbotApiClient>();
|
||||
|
||||
@@ -128,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");
|
||||
@@ -149,6 +204,7 @@ s.AddKeyedSingleton<ITwitchSocketHandler, ChannelFollowHandler>("twitch-notifica
|
||||
s.AddKeyedSingleton<ITwitchSocketHandler, ChannelRaidHandler>("twitch-notifications");
|
||||
s.AddKeyedSingleton<ITwitchSocketHandler, ChannelResubscriptionHandler>("twitch-notifications");
|
||||
s.AddKeyedSingleton<ITwitchSocketHandler, ChannelSubscriptionHandler>("twitch-notifications");
|
||||
s.AddKeyedSingleton<ITwitchSocketHandler, ChannelSubscriptionEndHandler>("twitch-notifications");
|
||||
s.AddKeyedSingleton<ITwitchSocketHandler, ChannelSubscriptionGiftHandler>("twitch-notifications");
|
||||
|
||||
// hermes websocket
|
||||
@@ -156,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");
|
||||
|
||||
180
TTS.cs
180
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,14 +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 = 4;
|
||||
public const int MINOR_VERSION = 9;
|
||||
public const int PATCH_VERSION = 3;
|
||||
|
||||
private readonly User _user;
|
||||
private readonly HermesApiClient _hermesApiClient;
|
||||
@@ -29,11 +32,11 @@ namespace TwitchChatTTS
|
||||
private readonly OBSSocketClient _obs;
|
||||
private readonly SevenSocketClient _seven;
|
||||
private readonly TwitchWebsocketClient _twitch;
|
||||
private readonly VeadoSocketClient _veado;
|
||||
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;
|
||||
|
||||
@@ -45,11 +48,11 @@ namespace TwitchChatTTS
|
||||
[FromKeyedServices("obs")] SocketClient<WebSocketMessage> obs,
|
||||
[FromKeyedServices("7tv")] SocketClient<WebSocketMessage> seven,
|
||||
[FromKeyedServices("twitch")] SocketClient<TwitchWebsocketMessage> twitch,
|
||||
[FromKeyedServices("veadotube")] SocketClient<object> veado,
|
||||
ICommandFactory commandFactory,
|
||||
ICommandManager commandManager,
|
||||
IEmoteDatabase emotes,
|
||||
TTSPlayer player,
|
||||
AudioPlaybackEngine playback,
|
||||
ServiceBusCentral bus,
|
||||
Configuration configuration,
|
||||
ILogger logger
|
||||
)
|
||||
@@ -61,12 +64,12 @@ namespace TwitchChatTTS
|
||||
_obs = (obs as OBSSocketClient)!;
|
||||
_seven = (seven as SevenSocketClient)!;
|
||||
_twitch = (twitch as TwitchWebsocketClient)!;
|
||||
_veado = (veado as VeadoSocketClient)!;
|
||||
_commandFactory = commandFactory;
|
||||
_commandManager = commandManager;
|
||||
_emotes = emotes;
|
||||
_bus = bus;
|
||||
_configuration = configuration;
|
||||
_player = player;
|
||||
_playback = playback;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -75,83 +78,107 @@ 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 configuration file.");
|
||||
return;
|
||||
}
|
||||
|
||||
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");
|
||||
if (changes != null && changes.Any())
|
||||
_logger.Information("Changelog:\n - " + string.Join("\n - ", changes) + "\n\n");
|
||||
await Task.Delay(15 * 1000);
|
||||
}
|
||||
|
||||
await InitializeHermesWebsocket();
|
||||
try
|
||||
{
|
||||
var hermesAccount = await _hermesApiClient.FetchHermesAccountDetails();
|
||||
_user.HermesUserId = hermesAccount.Id;
|
||||
_user.HermesUsername = hermesAccount.Username;
|
||||
_user.TwitchUsername = hermesAccount.Username;
|
||||
_user.TwitchUserId = long.Parse(hermesAccount.BroadcasterId);
|
||||
}
|
||||
catch (ArgumentNullException)
|
||||
{
|
||||
_logger.Error("Ensure you have your Twitch account linked to TTS.");
|
||||
await Task.Delay(TimeSpan.FromSeconds(30));
|
||||
return;
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
_logger.Error("Ensure you have your Twitch account linked to TTS.");
|
||||
await Task.Delay(TimeSpan.FromSeconds(30));
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Failed to initialize properly. Restart app please.");
|
||||
await Task.Delay(TimeSpan.FromSeconds(30));
|
||||
return;
|
||||
}
|
||||
|
||||
_playback.AddOnMixerInputEnded((object? s, SampleProviderEventArgs e) =>
|
||||
{
|
||||
if (_player.Playing?.Audio == e.SampleProvider)
|
||||
var hermesVersion = await _hermesApiClient.GetLatestTTSVersion();
|
||||
if (hermesVersion == null)
|
||||
{
|
||||
_player.Playing = null;
|
||||
_logger.Error("Failed to fetch latest TTS version. Something went wrong.");
|
||||
return;
|
||||
}
|
||||
if (hermesVersion.MajorVersion > MAJOR_VERSION || hermesVersion.MajorVersion == MAJOR_VERSION && (hermesVersion.MinorVersion > MINOR_VERSION || hermesVersion.MinorVersion == MINOR_VERSION && (hermesVersion.PatchVersion == null || hermesVersion.PatchVersion > PATCH_VERSION)))
|
||||
{
|
||||
_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");
|
||||
await Task.Delay(15 * 1000);
|
||||
}
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
await _twitch.Connect();
|
||||
}
|
||||
catch (Exception e)
|
||||
catch
|
||||
{
|
||||
_logger.Error(e, "Failed to connect to Twitch websocket server.");
|
||||
await Task.Delay(TimeSpan.FromSeconds(30));
|
||||
return;
|
||||
_logger.Warning("Failed to check for version updates.");
|
||||
}
|
||||
|
||||
var emoteSet = await _sevenApiClient.FetchChannelEmoteSet(_user.TwitchUserId.ToString());
|
||||
if (emoteSet != null)
|
||||
_user.SevenEmoteSetId = emoteSet.Id;
|
||||
var disposables = new List<IDisposable>();
|
||||
var connected = _bus.GetTopic("tts_connected");
|
||||
|
||||
// 7tv
|
||||
disposables.Add(connected.FirstAsync().Subscribe(async (data) =>
|
||||
{
|
||||
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();
|
||||
}));
|
||||
|
||||
disposables.Add(connected.FirstAsync().Subscribe(async (data) =>
|
||||
{
|
||||
if (data.Value is not User user)
|
||||
{
|
||||
_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
|
||||
{
|
||||
await _twitch.Connect();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(e, "Failed to connect to Twitch websocket server.");
|
||||
}
|
||||
}));
|
||||
|
||||
_commandManager.Update(_commandFactory);
|
||||
|
||||
await InitializeEmotes(_sevenApiClient, emoteSet);
|
||||
await InitializeSevenTv();
|
||||
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)
|
||||
@@ -170,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.");
|
||||
@@ -202,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();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user