Fixed 7tv & Twitch reconnection. Added adbreak, follow, subscription handlers for Twitch. Added multi-chat support. Added support to unsubscribe from Twitch event subs.
This commit is contained in:
parent
75fcb8e0f8
commit
95d879f511
@ -1,310 +0,0 @@
|
|||||||
// using System.Text.RegularExpressions;
|
|
||||||
// using TwitchLib.Client.Events;
|
|
||||||
// using Serilog;
|
|
||||||
// using TwitchChatTTS;
|
|
||||||
// using TwitchChatTTS.Chat.Commands;
|
|
||||||
// using TwitchChatTTS.Hermes.Socket;
|
|
||||||
// using TwitchChatTTS.Chat.Groups.Permissions;
|
|
||||||
// using TwitchChatTTS.Chat.Groups;
|
|
||||||
// using TwitchChatTTS.Chat.Emotes;
|
|
||||||
// using Microsoft.Extensions.DependencyInjection;
|
|
||||||
// using CommonSocketLibrary.Common;
|
|
||||||
// using CommonSocketLibrary.Abstract;
|
|
||||||
// using TwitchChatTTS.OBS.Socket;
|
|
||||||
|
|
||||||
|
|
||||||
// public class ChatMessageHandler
|
|
||||||
// {
|
|
||||||
// private readonly User _user;
|
|
||||||
// private readonly TTSPlayer _player;
|
|
||||||
// private readonly CommandManager _commands;
|
|
||||||
// private readonly IGroupPermissionManager _permissionManager;
|
|
||||||
// private readonly IChatterGroupManager _chatterGroupManager;
|
|
||||||
// private readonly IEmoteDatabase _emotes;
|
|
||||||
// private readonly OBSSocketClient _obs;
|
|
||||||
// private readonly HermesSocketClient _hermes;
|
|
||||||
// private readonly Configuration _configuration;
|
|
||||||
|
|
||||||
// private readonly ILogger _logger;
|
|
||||||
|
|
||||||
// private Regex _sfxRegex;
|
|
||||||
// private HashSet<long> _chatters;
|
|
||||||
|
|
||||||
// public HashSet<long> Chatters { get => _chatters; set => _chatters = value; }
|
|
||||||
|
|
||||||
|
|
||||||
// public ChatMessageHandler(
|
|
||||||
// User user,
|
|
||||||
// TTSPlayer player,
|
|
||||||
// CommandManager commands,
|
|
||||||
// IGroupPermissionManager permissionManager,
|
|
||||||
// IChatterGroupManager chatterGroupManager,
|
|
||||||
// IEmoteDatabase emotes,
|
|
||||||
// [FromKeyedServices("hermes")] SocketClient<WebSocketMessage> hermes,
|
|
||||||
// [FromKeyedServices("obs")] SocketClient<WebSocketMessage> obs,
|
|
||||||
// Configuration configuration,
|
|
||||||
// ILogger logger
|
|
||||||
// )
|
|
||||||
// {
|
|
||||||
// _user = user;
|
|
||||||
// _player = player;
|
|
||||||
// _commands = commands;
|
|
||||||
// _permissionManager = permissionManager;
|
|
||||||
// _chatterGroupManager = chatterGroupManager;
|
|
||||||
// _emotes = emotes;
|
|
||||||
// _obs = (obs as OBSSocketClient)!;
|
|
||||||
// _hermes = (hermes as HermesSocketClient)!;
|
|
||||||
// _configuration = configuration;
|
|
||||||
// _logger = logger;
|
|
||||||
|
|
||||||
// _chatters = new HashSet<long>();
|
|
||||||
// _sfxRegex = new Regex(@"\(([A-Za-z0-9_-]+)\)");
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// public async Task<MessageResult> Handle(OnMessageReceivedArgs e)
|
|
||||||
// {
|
|
||||||
// var m = e.ChatMessage;
|
|
||||||
|
|
||||||
// if (_hermes.Connected && !_hermes.Ready)
|
|
||||||
// {
|
|
||||||
// _logger.Debug($"TTS is not yet ready. Ignoring chat messages [message id: {m.Id}]");
|
|
||||||
// return new MessageResult(MessageStatus.NotReady, -1, -1);
|
|
||||||
// }
|
|
||||||
// if (_configuration.Twitch?.TtsWhenOffline != true && !_obs.Streaming)
|
|
||||||
// {
|
|
||||||
// _logger.Debug($"OBS is not streaming. Ignoring chat messages [message id: {m.Id}]");
|
|
||||||
// return new MessageResult(MessageStatus.NotReady, -1, -1);
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// var msg = e.ChatMessage.Message;
|
|
||||||
// var chatterId = long.Parse(m.UserId);
|
|
||||||
// var tasks = new List<Task>();
|
|
||||||
|
|
||||||
// var checks = new bool[] { true, m.IsSubscriber, m.IsVip, m.IsModerator, m.IsBroadcaster };
|
|
||||||
// var defaultGroups = new string[] { "everyone", "subscribers", "vip", "moderators", "broadcaster" };
|
|
||||||
// var customGroups = _chatterGroupManager.GetGroupNamesFor(chatterId);
|
|
||||||
// var groups = defaultGroups.Where((e, i) => checks[i]).Union(customGroups);
|
|
||||||
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// var commandResult = await _commands.Execute(msg, m, groups);
|
|
||||||
// if (commandResult != ChatCommandResult.Unknown)
|
|
||||||
// return new MessageResult(MessageStatus.Command, -1, -1);
|
|
||||||
// }
|
|
||||||
// catch (Exception ex)
|
|
||||||
// {
|
|
||||||
// _logger.Error(ex, $"Failed executing a chat command [message: {msg}][chatter: {m.Username}][chatter id: {m.UserId}][message id: {m.Id}]");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var permissionPath = "tts.chat.messages.read";
|
|
||||||
// if (!string.IsNullOrWhiteSpace(m.CustomRewardId))
|
|
||||||
// permissionPath = "tts.chat.redemptions.read";
|
|
||||||
|
|
||||||
// var permission = chatterId == _user.OwnerId ? true : _permissionManager.CheckIfAllowed(groups, permissionPath);
|
|
||||||
// if (permission != true)
|
|
||||||
// {
|
|
||||||
// _logger.Debug($"Blocked message by {m.Username}: {msg}");
|
|
||||||
// return new MessageResult(MessageStatus.Blocked, -1, -1);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (_obs.Streaming && !_chatters.Contains(chatterId))
|
|
||||||
// {
|
|
||||||
// tasks.Add(_hermes.SendChatterDetails(chatterId, m.Username));
|
|
||||||
// _chatters.Add(chatterId);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Filter highly repetitive words (like emotes) from the message.
|
|
||||||
// int totalEmoteUsed = 0;
|
|
||||||
// var emotesUsed = new HashSet<string>();
|
|
||||||
// var words = msg.Split(' ');
|
|
||||||
// var wordCounter = new Dictionary<string, int>();
|
|
||||||
// string filteredMsg = string.Empty;
|
|
||||||
// var newEmotes = new Dictionary<string, string>();
|
|
||||||
// foreach (var w in words)
|
|
||||||
// {
|
|
||||||
// if (wordCounter.ContainsKey(w))
|
|
||||||
// wordCounter[w]++;
|
|
||||||
// else
|
|
||||||
// wordCounter.Add(w, 1);
|
|
||||||
|
|
||||||
// var emoteId = _emotes.Get(w);
|
|
||||||
// if (emoteId == null)
|
|
||||||
// {
|
|
||||||
// emoteId = m.EmoteSet.Emotes.FirstOrDefault(e => e.Name == w)?.Id;
|
|
||||||
// if (emoteId != null)
|
|
||||||
// {
|
|
||||||
// newEmotes.Add(emoteId, w);
|
|
||||||
// _emotes.Add(w, emoteId);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// if (emoteId != null)
|
|
||||||
// {
|
|
||||||
// emotesUsed.Add(emoteId);
|
|
||||||
// totalEmoteUsed++;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (wordCounter[w] <= 4 && (emoteId == null || totalEmoteUsed <= 5))
|
|
||||||
// filteredMsg += w + " ";
|
|
||||||
// }
|
|
||||||
// if (_obs.Streaming && newEmotes.Any())
|
|
||||||
// tasks.Add(_hermes.SendEmoteDetails(newEmotes));
|
|
||||||
// msg = filteredMsg;
|
|
||||||
|
|
||||||
// // Replace filtered words.
|
|
||||||
// if (_user.RegexFilters != null)
|
|
||||||
// {
|
|
||||||
// foreach (var wf in _user.RegexFilters)
|
|
||||||
// {
|
|
||||||
// if (wf.Search == null || wf.Replace == null)
|
|
||||||
// continue;
|
|
||||||
|
|
||||||
// if (wf.IsRegex)
|
|
||||||
// {
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// var regex = new Regex(wf.Search);
|
|
||||||
// msg = regex.Replace(msg, wf.Replace);
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
// catch (Exception)
|
|
||||||
// {
|
|
||||||
// wf.IsRegex = false;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// msg = msg.Replace(wf.Search, wf.Replace);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Determine the priority of this message
|
|
||||||
// int priority = _chatterGroupManager.GetPriorityFor(groups) + m.SubscribedMonthCount * (m.IsSubscriber ? 10 : 5);
|
|
||||||
|
|
||||||
// // Determine voice selected.
|
|
||||||
// string voiceSelected = _user.DefaultTTSVoice;
|
|
||||||
// if (_user.VoicesSelected?.ContainsKey(chatterId) == true)
|
|
||||||
// {
|
|
||||||
// var voiceId = _user.VoicesSelected[chatterId];
|
|
||||||
// if (_user.VoicesAvailable.TryGetValue(voiceId, out string? voiceName) && voiceName != null)
|
|
||||||
// {
|
|
||||||
// if (_user.VoicesEnabled.Contains(voiceName) || chatterId == _user.OwnerId || m.IsStaff)
|
|
||||||
// {
|
|
||||||
// voiceSelected = voiceName;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Determine additional voices used
|
|
||||||
// var matches = _user.WordFilterRegex?.Matches(msg).ToArray();
|
|
||||||
// if (matches == null || matches.FirstOrDefault() == null || matches.First().Index < 0)
|
|
||||||
// {
|
|
||||||
// HandlePartialMessage(priority, voiceSelected, msg.Trim(), e);
|
|
||||||
// return new MessageResult(MessageStatus.None, _user.TwitchUserId, chatterId, emotesUsed);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// HandlePartialMessage(priority, voiceSelected, msg.Substring(0, matches.First().Index).Trim(), e);
|
|
||||||
// foreach (Match match in matches)
|
|
||||||
// {
|
|
||||||
// var message = match.Groups[2].ToString();
|
|
||||||
// if (string.IsNullOrWhiteSpace(message))
|
|
||||||
// continue;
|
|
||||||
|
|
||||||
// var voice = match.Groups[1].ToString();
|
|
||||||
// voice = voice[0].ToString().ToUpper() + voice.Substring(1).ToLower();
|
|
||||||
// HandlePartialMessage(priority, voice, message.Trim(), e);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (tasks.Any())
|
|
||||||
// await Task.WhenAll(tasks);
|
|
||||||
|
|
||||||
// return new MessageResult(MessageStatus.None, _user.TwitchUserId, chatterId, emotesUsed);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// private void HandlePartialMessage(int priority, string voice, string message, OnMessageReceivedArgs e)
|
|
||||||
// {
|
|
||||||
// if (string.IsNullOrWhiteSpace(message))
|
|
||||||
// return;
|
|
||||||
|
|
||||||
// var m = e.ChatMessage;
|
|
||||||
// var parts = _sfxRegex.Split(message);
|
|
||||||
// var badgesString = string.Join(", ", e.ChatMessage.Badges.Select(b => b.Key + " = " + b.Value));
|
|
||||||
|
|
||||||
// if (parts.Length == 1)
|
|
||||||
// {
|
|
||||||
// _logger.Information($"Username: {m.Username}; User ID: {m.UserId}; Voice: {voice}; Priority: {priority}; Message: {message}; Month: {m.SubscribedMonthCount}; Reward Id: {m.CustomRewardId}; {badgesString}");
|
|
||||||
// _player.Add(new TTSMessage()
|
|
||||||
// {
|
|
||||||
// Voice = voice,
|
|
||||||
// Message = message,
|
|
||||||
// Timestamp = DateTime.UtcNow,
|
|
||||||
// Username = m.Username,
|
|
||||||
// //Bits = m.Bits,
|
|
||||||
// Badges = e.Badges,
|
|
||||||
// Priority = priority
|
|
||||||
// });
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var sfxMatches = _sfxRegex.Matches(message);
|
|
||||||
// var sfxStart = sfxMatches.FirstOrDefault()?.Index ?? message.Length;
|
|
||||||
|
|
||||||
// for (var i = 0; i < sfxMatches.Count; i++)
|
|
||||||
// {
|
|
||||||
// var sfxMatch = sfxMatches[i];
|
|
||||||
// var sfxName = sfxMatch.Groups[1]?.ToString()?.ToLower();
|
|
||||||
|
|
||||||
// if (!File.Exists("sfx/" + sfxName + ".mp3"))
|
|
||||||
// {
|
|
||||||
// parts[i * 2 + 2] = parts[i * 2] + " (" + parts[i * 2 + 1] + ")" + parts[i * 2 + 2];
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (!string.IsNullOrWhiteSpace(parts[i * 2]))
|
|
||||||
// {
|
|
||||||
// _logger.Information($"Username: {m.Username}; User ID: {m.UserId}; Voice: {voice}; Priority: {priority}; Message: {parts[i * 2]}; Month: {m.SubscribedMonthCount}; {badgesString}");
|
|
||||||
// _player.Add(new TTSMessage()
|
|
||||||
// {
|
|
||||||
// Voice = voice,
|
|
||||||
// Message = parts[i * 2],
|
|
||||||
// Moderator = m.IsModerator,
|
|
||||||
// Timestamp = DateTime.UtcNow,
|
|
||||||
// Username = m.Username,
|
|
||||||
// Bits = m.Bits,
|
|
||||||
// Badges = m.Badges,
|
|
||||||
// Priority = priority
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// _logger.Information($"Username: {m.Username}; User ID: {m.UserId}; Voice: {voice}; Priority: {priority}; SFX: {sfxName}; Month: {m.SubscribedMonthCount}; {badgesString}");
|
|
||||||
// _player.Add(new TTSMessage()
|
|
||||||
// {
|
|
||||||
// Voice = voice,
|
|
||||||
// Message = sfxName,
|
|
||||||
// File = $"sfx/{sfxName}.mp3",
|
|
||||||
// Moderator = m.IsModerator,
|
|
||||||
// Timestamp = DateTime.UtcNow,
|
|
||||||
// Username = m.Username,
|
|
||||||
// Bits = m.Bits,
|
|
||||||
// Badges = m.Badges,
|
|
||||||
// Priority = priority
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (!string.IsNullOrWhiteSpace(parts.Last()))
|
|
||||||
// {
|
|
||||||
// _logger.Information($"Username: {m.Username}; User ID: {m.UserId}; Voice: {voice}; Priority: {priority}; Message: {parts.Last()}; Month: {m.SubscribedMonthCount}; {badgesString}");
|
|
||||||
// _player.Add(new TTSMessage()
|
|
||||||
// {
|
|
||||||
// Voice = voice,
|
|
||||||
// Message = parts.Last(),
|
|
||||||
// Moderator = m.IsModerator,
|
|
||||||
// Timestamp = DateTime.UtcNow,
|
|
||||||
// Username = m.Username,
|
|
||||||
// Bits = m.Bits,
|
|
||||||
// Badges = m.Badges,
|
|
||||||
// Priority = priority
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
@ -13,6 +13,6 @@ namespace TwitchChatTTS.Chat.Commands
|
|||||||
public interface IChatPartialCommand
|
public interface IChatPartialCommand
|
||||||
{
|
{
|
||||||
bool AcceptCustomPermission { get; }
|
bool AcceptCustomPermission { get; }
|
||||||
Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient client);
|
Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient hermes);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -7,6 +7,7 @@ namespace TwitchChatTTS.Chat.Commands
|
|||||||
Success = 2,
|
Success = 2,
|
||||||
Permission = 3,
|
Permission = 3,
|
||||||
Syntax = 4,
|
Syntax = 4,
|
||||||
Fail = 5
|
Fail = 5,
|
||||||
|
OtherRoom = 6,
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -45,17 +45,18 @@ namespace TwitchChatTTS.Chat.Commands
|
|||||||
{
|
{
|
||||||
if (_current == _root)
|
if (_current == _root)
|
||||||
throw new Exception("Cannot add permissions without a command name.");
|
throw new Exception("Cannot add permissions without a command name.");
|
||||||
|
|
||||||
_current.AddPermission(path);
|
_current.AddPermission(path);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICommandBuilder AddAlias(string alias, string child) {
|
public ICommandBuilder AddAlias(string alias, string child)
|
||||||
|
{
|
||||||
if (_current == _root)
|
if (_current == _root)
|
||||||
throw new Exception("Cannot add aliases without a command name.");
|
throw new Exception("Cannot add aliases without a command name.");
|
||||||
if (_current.Children == null || !_current.Children.Any())
|
if (_current.Children == null || !_current.Children.Any())
|
||||||
throw new Exception("Cannot add alias if this has no parameter.");
|
throw new Exception("Cannot add alias if this has no parameter.");
|
||||||
|
|
||||||
_current.AddAlias(alias, child);
|
_current.AddAlias(alias, child);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -327,7 +328,8 @@ namespace TwitchChatTTS.Chat.Commands
|
|||||||
Permissions = Permissions.Union([path]).ToArray();
|
Permissions = Permissions.Union([path]).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public CommandNode AddAlias(string alias, string child) {
|
public CommandNode AddAlias(string alias, string child)
|
||||||
|
{
|
||||||
var target = _children.FirstOrDefault(c => c.Parameter.Name == child);
|
var target = _children.FirstOrDefault(c => c.Parameter.Name == child);
|
||||||
if (target == null)
|
if (target == null)
|
||||||
throw new Exception($"Cannot find child parameter [parameter: {child}][alias: {alias}]");
|
throw new Exception($"Cannot find child parameter [parameter: {child}][alias: {alias}]");
|
||||||
@ -339,6 +341,8 @@ namespace TwitchChatTTS.Chat.Commands
|
|||||||
var clone = target.MemberwiseClone() as CommandNode;
|
var clone = target.MemberwiseClone() as CommandNode;
|
||||||
var node = new CommandNode(new StaticParameter(alias, alias, target.Parameter.Optional));
|
var node = new CommandNode(new StaticParameter(alias, alias, target.Parameter.Optional));
|
||||||
node._children = target._children;
|
node._children = target._children;
|
||||||
|
node.Permissions = target.Permissions;
|
||||||
|
node.Command = target.Command;
|
||||||
_children.Add(node);
|
_children.Add(node);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
41
Chat/Commands/CommandFactory.cs
Normal file
41
Chat/Commands/CommandFactory.cs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
using Serilog;
|
||||||
|
using static TwitchChatTTS.Chat.Commands.TTSCommands;
|
||||||
|
|
||||||
|
namespace TwitchChatTTS.Chat.Commands
|
||||||
|
{
|
||||||
|
public class CommandFactory : ICommandFactory
|
||||||
|
{
|
||||||
|
private readonly IEnumerable<IChatCommand> _commands;
|
||||||
|
private readonly ICommandBuilder _builder;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
public CommandFactory(
|
||||||
|
IEnumerable<IChatCommand> commands,
|
||||||
|
ICommandBuilder builder,
|
||||||
|
ILogger logger
|
||||||
|
)
|
||||||
|
{
|
||||||
|
_commands = commands;
|
||||||
|
_builder = builder;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICommandSelector Build()
|
||||||
|
{
|
||||||
|
foreach (var command in _commands)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.Debug($"Creating command tree for '{command.Name}'.");
|
||||||
|
command.Build(_builder);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.Error(e, $"Failed to properly load a chat command [command name: {command.Name}]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _builder.Build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -10,37 +10,30 @@ using static TwitchChatTTS.Chat.Commands.TTSCommands;
|
|||||||
|
|
||||||
namespace TwitchChatTTS.Chat.Commands
|
namespace TwitchChatTTS.Chat.Commands
|
||||||
{
|
{
|
||||||
public class CommandManager
|
public class CommandManager : ICommandManager
|
||||||
{
|
{
|
||||||
private readonly User _user;
|
private readonly User _user;
|
||||||
private readonly ICommandSelector _commandSelector;
|
private ICommandSelector _commandSelector;
|
||||||
private readonly HermesSocketClient _hermes;
|
private readonly HermesSocketClient _hermes;
|
||||||
|
//private readonly TwitchWebsocketClient _twitch;
|
||||||
private readonly IGroupPermissionManager _permissionManager;
|
private readonly IGroupPermissionManager _permissionManager;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private string CommandStartSign { get; } = "!";
|
private string CommandStartSign { get; } = "!";
|
||||||
|
|
||||||
|
|
||||||
public CommandManager(
|
public CommandManager(
|
||||||
IEnumerable<IChatCommand> commands,
|
|
||||||
ICommandBuilder commandBuilder,
|
|
||||||
User user,
|
User user,
|
||||||
[FromKeyedServices("hermes")] SocketClient<WebSocketMessage> socketClient,
|
[FromKeyedServices("hermes")] SocketClient<WebSocketMessage> hermes,
|
||||||
|
//[FromKeyedServices("twitch")] SocketClient<TwitchWebsocketMessage> twitch,
|
||||||
IGroupPermissionManager permissionManager,
|
IGroupPermissionManager permissionManager,
|
||||||
ILogger logger
|
ILogger logger
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_user = user;
|
_user = user;
|
||||||
_hermes = (socketClient as HermesSocketClient)!;
|
_hermes = (hermes as HermesSocketClient)!;
|
||||||
|
//_twitch = (twitch as TwitchWebsocketClient)!;
|
||||||
_permissionManager = permissionManager;
|
_permissionManager = permissionManager;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
|
||||||
foreach (var command in commands)
|
|
||||||
{
|
|
||||||
_logger.Debug($"Creating command tree for '{command.Name}'.");
|
|
||||||
command.Build(commandBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
_commandSelector = commandBuilder.Build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -54,9 +47,13 @@ namespace TwitchChatTTS.Chat.Commands
|
|||||||
if (!arg.StartsWith(CommandStartSign))
|
if (!arg.StartsWith(CommandStartSign))
|
||||||
return ChatCommandResult.Unknown;
|
return ChatCommandResult.Unknown;
|
||||||
|
|
||||||
|
if (message.BroadcasterUserId != _user.TwitchUserId.ToString())
|
||||||
|
return ChatCommandResult.OtherRoom;
|
||||||
|
|
||||||
string[] parts = Regex.Matches(arg.Substring(CommandStartSign.Length), "(?<match>[^\"\\n\\s]+|\"[^\"\\n]*\")")
|
string[] parts = Regex.Matches(arg.Substring(CommandStartSign.Length), "(?<match>[^\"\\n\\s]+|\"[^\"\\n]*\")")
|
||||||
.Cast<Match>()
|
.Cast<Match>()
|
||||||
.Select(m => m.Groups["match"].Value)
|
.Select(m => m.Groups["match"].Value)
|
||||||
|
.Where(m => !string.IsNullOrEmpty(m))
|
||||||
.Select(m => m.StartsWith('"') && m.EndsWith('"') ? m.Substring(1, m.Length - 2) : m)
|
.Select(m => m.StartsWith('"') && m.EndsWith('"') ? m.Substring(1, m.Length - 2) : m)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
string[] args = parts.ToArray();
|
string[] args = parts.ToArray();
|
||||||
@ -65,7 +62,7 @@ namespace TwitchChatTTS.Chat.Commands
|
|||||||
CommandSelectorResult selectorResult = _commandSelector.GetBestMatch(args, message);
|
CommandSelectorResult selectorResult = _commandSelector.GetBestMatch(args, message);
|
||||||
if (selectorResult.Command == null)
|
if (selectorResult.Command == null)
|
||||||
{
|
{
|
||||||
_logger.Warning($"Could not match '{arg}' to any command.");
|
_logger.Warning($"Could not match '{arg}' to any command [chatter: {message.ChatterUserLogin}][chatter id: {message.ChatterUserId}]");
|
||||||
return ChatCommandResult.Missing;
|
return ChatCommandResult.Missing;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,10 +108,24 @@ namespace TwitchChatTTS.Chat.Commands
|
|||||||
return ChatCommandResult.Success;
|
return ChatCommandResult.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Update(ICommandFactory factory)
|
||||||
|
{
|
||||||
|
_commandSelector = factory.Build();
|
||||||
|
}
|
||||||
|
|
||||||
private bool CanExecute(long chatterId, IEnumerable<string> groups, string path, string[]? additionalPaths)
|
private bool CanExecute(long chatterId, IEnumerable<string> groups, string path, string[]? additionalPaths)
|
||||||
{
|
{
|
||||||
_logger.Debug($"Checking for permission [chatter id: {chatterId}][group: {string.Join(", ", groups)}][path: {path}]{(additionalPaths != null ? "[paths: " + string.Join('|', additionalPaths) + "]" : string.Empty)}");
|
_logger.Debug($"Checking for permission [chatter id: {chatterId}][group: {string.Join(", ", groups)}][path: {path}]{(additionalPaths != null ? "[paths: " + string.Join('|', additionalPaths) + "]" : string.Empty)}");
|
||||||
return _permissionManager.CheckIfAllowed(groups, path) != false && (additionalPaths == null || additionalPaths.All(p => _permissionManager.CheckIfAllowed(groups, p) != false));
|
if (_permissionManager.CheckIfAllowed(groups, path) != false)
|
||||||
|
{
|
||||||
|
if (additionalPaths == null)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// All direct allow must not be false and at least one of them must be true.
|
||||||
|
if (additionalPaths.All(p => _permissionManager.CheckIfDirectAllowed(groups, p) != false) && additionalPaths.Any(p => _permissionManager.CheckIfDirectAllowed(groups, p) == true))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
9
Chat/Commands/ICommandFactory.cs
Normal file
9
Chat/Commands/ICommandFactory.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
using static TwitchChatTTS.Chat.Commands.TTSCommands;
|
||||||
|
|
||||||
|
namespace TwitchChatTTS.Chat.Commands
|
||||||
|
{
|
||||||
|
public interface ICommandFactory
|
||||||
|
{
|
||||||
|
ICommandSelector Build();
|
||||||
|
}
|
||||||
|
}
|
9
Chat/Commands/ICommandManager.cs
Normal file
9
Chat/Commands/ICommandManager.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
using TwitchChatTTS.Twitch.Socket.Messages;
|
||||||
|
|
||||||
|
namespace TwitchChatTTS.Chat.Commands
|
||||||
|
{
|
||||||
|
public interface ICommandManager {
|
||||||
|
Task<ChatCommandResult> Execute(string arg, ChannelChatMessage message, IEnumerable<string> groups);
|
||||||
|
void Update(ICommandFactory factory);
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ using Serilog;
|
|||||||
using TwitchChatTTS.Hermes.Socket;
|
using TwitchChatTTS.Hermes.Socket;
|
||||||
using TwitchChatTTS.OBS.Socket;
|
using TwitchChatTTS.OBS.Socket;
|
||||||
using TwitchChatTTS.OBS.Socket.Data;
|
using TwitchChatTTS.OBS.Socket.Data;
|
||||||
|
using TwitchChatTTS.Twitch.Socket;
|
||||||
using TwitchChatTTS.Twitch.Socket.Messages;
|
using TwitchChatTTS.Twitch.Socket.Messages;
|
||||||
using static TwitchChatTTS.Chat.Commands.TTSCommands;
|
using static TwitchChatTTS.Chat.Commands.TTSCommands;
|
||||||
|
|
||||||
@ -71,7 +72,7 @@ namespace TwitchChatTTS.Chat.Commands
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient client)
|
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient hermes)
|
||||||
{
|
{
|
||||||
string sceneName = values["sceneName"];
|
string sceneName = values["sceneName"];
|
||||||
string sourceName = values["sourceName"];
|
string sourceName = values["sourceName"];
|
||||||
@ -97,7 +98,7 @@ namespace TwitchChatTTS.Chat.Commands
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient client)
|
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient hermes)
|
||||||
{
|
{
|
||||||
string sceneName = values["sceneName"];
|
string sceneName = values["sceneName"];
|
||||||
string sourceName = values["sourceName"];
|
string sourceName = values["sourceName"];
|
||||||
@ -133,7 +134,7 @@ namespace TwitchChatTTS.Chat.Commands
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient client)
|
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient hermes)
|
||||||
{
|
{
|
||||||
string sceneName = values["sceneName"];
|
string sceneName = values["sceneName"];
|
||||||
string sourceName = values["sourceName"];
|
string sourceName = values["sourceName"];
|
||||||
|
@ -4,6 +4,7 @@ using Microsoft.Extensions.DependencyInjection;
|
|||||||
using Serilog;
|
using Serilog;
|
||||||
using TwitchChatTTS.Hermes.Socket;
|
using TwitchChatTTS.Hermes.Socket;
|
||||||
using TwitchChatTTS.OBS.Socket;
|
using TwitchChatTTS.OBS.Socket;
|
||||||
|
using TwitchChatTTS.Twitch.Socket;
|
||||||
using TwitchChatTTS.Twitch.Socket.Messages;
|
using TwitchChatTTS.Twitch.Socket.Messages;
|
||||||
using static TwitchChatTTS.Chat.Commands.TTSCommands;
|
using static TwitchChatTTS.Chat.Commands.TTSCommands;
|
||||||
|
|
||||||
@ -44,9 +45,9 @@ namespace TwitchChatTTS.Chat.Commands
|
|||||||
{
|
{
|
||||||
public bool AcceptCustomPermission { get => true; }
|
public bool AcceptCustomPermission { get => true; }
|
||||||
|
|
||||||
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient client)
|
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient hermes)
|
||||||
{
|
{
|
||||||
await client.FetchEnabledTTSVoices();
|
await hermes.FetchEnabledTTSVoices();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,9 +55,9 @@ namespace TwitchChatTTS.Chat.Commands
|
|||||||
{
|
{
|
||||||
public bool AcceptCustomPermission { get => true; }
|
public bool AcceptCustomPermission { get => true; }
|
||||||
|
|
||||||
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient client)
|
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient hermes)
|
||||||
{
|
{
|
||||||
await client.FetchTTSWordFilters();
|
await hermes.FetchTTSWordFilters();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,9 +65,9 @@ namespace TwitchChatTTS.Chat.Commands
|
|||||||
{
|
{
|
||||||
public bool AcceptCustomPermission { get => true; }
|
public bool AcceptCustomPermission { get => true; }
|
||||||
|
|
||||||
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient client)
|
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient hermes)
|
||||||
{
|
{
|
||||||
await client.FetchTTSChatterVoices();
|
await hermes.FetchTTSChatterVoices();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,9 +75,9 @@ namespace TwitchChatTTS.Chat.Commands
|
|||||||
{
|
{
|
||||||
public bool AcceptCustomPermission { get => true; }
|
public bool AcceptCustomPermission { get => true; }
|
||||||
|
|
||||||
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient client)
|
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient hermes)
|
||||||
{
|
{
|
||||||
await client.FetchDefaultTTSVoice();
|
await hermes.FetchDefaultTTSVoice();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,9 +85,9 @@ namespace TwitchChatTTS.Chat.Commands
|
|||||||
{
|
{
|
||||||
public bool AcceptCustomPermission { get => true; }
|
public bool AcceptCustomPermission { get => true; }
|
||||||
|
|
||||||
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient client)
|
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient hermes)
|
||||||
{
|
{
|
||||||
await client.FetchRedemptions();
|
await hermes.FetchRedemptions();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,12 +98,13 @@ namespace TwitchChatTTS.Chat.Commands
|
|||||||
|
|
||||||
public bool AcceptCustomPermission { get => true; }
|
public bool AcceptCustomPermission { get => true; }
|
||||||
|
|
||||||
public RefreshObs(OBSSocketClient obsManager, ILogger logger) {
|
public RefreshObs(OBSSocketClient obsManager, ILogger logger)
|
||||||
|
{
|
||||||
_obsManager = obsManager;
|
_obsManager = obsManager;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient client)
|
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient hermes)
|
||||||
{
|
{
|
||||||
_obsManager.ClearCache();
|
_obsManager.ClearCache();
|
||||||
_logger.Information("Cleared the cache used for OBS.");
|
_logger.Information("Cleared the cache used for OBS.");
|
||||||
@ -114,9 +116,9 @@ namespace TwitchChatTTS.Chat.Commands
|
|||||||
|
|
||||||
public bool AcceptCustomPermission { get => true; }
|
public bool AcceptCustomPermission { get => true; }
|
||||||
|
|
||||||
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient client)
|
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient hermes)
|
||||||
{
|
{
|
||||||
await client.FetchPermissions();
|
await hermes.FetchPermissions();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using Serilog;
|
using Serilog;
|
||||||
using TwitchChatTTS.Hermes.Socket;
|
using TwitchChatTTS.Hermes.Socket;
|
||||||
|
using TwitchChatTTS.Twitch.Socket;
|
||||||
using TwitchChatTTS.Twitch.Socket.Messages;
|
using TwitchChatTTS.Twitch.Socket.Messages;
|
||||||
using static TwitchChatTTS.Chat.Commands.TTSCommands;
|
using static TwitchChatTTS.Chat.Commands.TTSCommands;
|
||||||
|
|
||||||
@ -51,7 +52,7 @@ namespace TwitchChatTTS.Chat.Commands
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient client)
|
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient hermes)
|
||||||
{
|
{
|
||||||
if (_player.Playing == null)
|
if (_player.Playing == null)
|
||||||
return;
|
return;
|
||||||
@ -78,7 +79,7 @@ namespace TwitchChatTTS.Chat.Commands
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient client)
|
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient hermes)
|
||||||
{
|
{
|
||||||
_player.RemoveAll();
|
_player.RemoveAll();
|
||||||
|
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
using CommonSocketLibrary.Abstract;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using TwitchChatTTS.Hermes.Socket;
|
using TwitchChatTTS.Hermes.Socket;
|
||||||
|
using TwitchChatTTS.Twitch.Socket;
|
||||||
using TwitchChatTTS.Twitch.Socket.Messages;
|
using TwitchChatTTS.Twitch.Socket.Messages;
|
||||||
using static TwitchChatTTS.Chat.Commands.TTSCommands;
|
using static TwitchChatTTS.Chat.Commands.TTSCommands;
|
||||||
|
|
||||||
@ -7,13 +10,21 @@ namespace TwitchChatTTS.Chat.Commands
|
|||||||
{
|
{
|
||||||
public class TTSCommand : IChatCommand
|
public class TTSCommand : IChatCommand
|
||||||
{
|
{
|
||||||
|
private readonly TwitchWebsocketClient _twitch;
|
||||||
private readonly User _user;
|
private readonly User _user;
|
||||||
|
private readonly TwitchApiClient _client;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
|
||||||
public TTSCommand(User user, ILogger logger)
|
public TTSCommand(
|
||||||
|
[FromKeyedServices("twitch")] SocketClient<TwitchWebsocketMessage> twitch,
|
||||||
|
User user,
|
||||||
|
TwitchApiClient client,
|
||||||
|
ILogger logger)
|
||||||
{
|
{
|
||||||
|
_twitch = (twitch as TwitchWebsocketClient)!;
|
||||||
_user = user;
|
_user = user;
|
||||||
|
_client = client;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,7 +62,19 @@ namespace TwitchChatTTS.Chat.Commands
|
|||||||
})
|
})
|
||||||
.AddAlias("off", "disable")
|
.AddAlias("off", "disable")
|
||||||
.AddAlias("disabled", "disable")
|
.AddAlias("disabled", "disable")
|
||||||
.AddAlias("false", "disable");
|
.AddAlias("false", "disable")
|
||||||
|
.CreateStaticInputParameter("join", b =>
|
||||||
|
{
|
||||||
|
b.CreateMentionParameter("mention", true)
|
||||||
|
.AddPermission("tts.commands.tts.join")
|
||||||
|
.CreateCommand(new JoinRoomCommand(_twitch, _client, _user, _logger));
|
||||||
|
})
|
||||||
|
.CreateStaticInputParameter("leave", b =>
|
||||||
|
{
|
||||||
|
b.CreateMentionParameter("mention", true)
|
||||||
|
.AddPermission("tts.commands.tts.leave")
|
||||||
|
.CreateCommand(new LeaveRoomCommand(_twitch, _client, _user, _logger));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,7 +142,8 @@ namespace TwitchChatTTS.Chat.Commands
|
|||||||
}
|
}
|
||||||
|
|
||||||
var voiceId = _user.VoicesAvailable.FirstOrDefault(v => v.Value.ToLower() == voiceNameLower).Key;
|
var voiceId = _user.VoicesAvailable.FirstOrDefault(v => v.Value.ToLower() == voiceNameLower).Key;
|
||||||
if (voiceId == null) {
|
if (voiceId == null)
|
||||||
|
{
|
||||||
_logger.Warning($"Could not find the identifier for the tts voice [voice name: {voiceName}]");
|
_logger.Warning($"Could not find the identifier for the tts voice [voice name: {voiceName}]");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -157,5 +181,94 @@ namespace TwitchChatTTS.Chat.Commands
|
|||||||
_logger.Information($"Changed state for TTS voice [voice: {voiceName}][state: {_state}][invoker: {message.ChatterUserLogin}][id: {message.ChatterUserId}]");
|
_logger.Information($"Changed state for TTS voice [voice: {voiceName}][state: {_state}][invoker: {message.ChatterUserLogin}][id: {message.ChatterUserId}]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class JoinRoomCommand : IChatPartialCommand
|
||||||
|
{
|
||||||
|
private readonly TwitchWebsocketClient _twitch;
|
||||||
|
private readonly TwitchApiClient _client;
|
||||||
|
private readonly User _user;
|
||||||
|
private ILogger _logger;
|
||||||
|
|
||||||
|
public bool AcceptCustomPermission { get => true; }
|
||||||
|
|
||||||
|
public JoinRoomCommand(
|
||||||
|
[FromKeyedServices("twitch")] SocketClient<TwitchWebsocketMessage> twitch,
|
||||||
|
TwitchApiClient client,
|
||||||
|
User user,
|
||||||
|
ILogger logger
|
||||||
|
)
|
||||||
|
{
|
||||||
|
_twitch = (twitch as TwitchWebsocketClient)!;
|
||||||
|
_client = client;
|
||||||
|
_user = user;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient client)
|
||||||
|
{
|
||||||
|
var mention = values["mention"].ToLower();
|
||||||
|
var fragment = message.Message.Fragments.FirstOrDefault(f => f.Mention != null && f.Text.ToLower() == mention);
|
||||||
|
if (fragment == null)
|
||||||
|
{
|
||||||
|
_logger.Warning("Cannot find the channel to join chat with.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _client.CreateEventSubscription("channel.chat.message", "1", _twitch.SessionId, _user.TwitchUserId.ToString(), fragment.Mention!.UserId);
|
||||||
|
_logger.Information($"Joined chat room [channel: {fragment.Mention.UserLogin}][channel id: {fragment.Mention.UserId}][invoker: {message.ChatterUserLogin}][id: {message.ChatterUserId}]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class LeaveRoomCommand : IChatPartialCommand
|
||||||
|
{
|
||||||
|
private readonly TwitchWebsocketClient _twitch;
|
||||||
|
private readonly TwitchApiClient _client;
|
||||||
|
private readonly User _user;
|
||||||
|
private ILogger _logger;
|
||||||
|
|
||||||
|
public bool AcceptCustomPermission { get => true; }
|
||||||
|
|
||||||
|
public LeaveRoomCommand(
|
||||||
|
[FromKeyedServices("twitch")] SocketClient<TwitchWebsocketMessage> twitch,
|
||||||
|
TwitchApiClient client,
|
||||||
|
User user,
|
||||||
|
ILogger logger
|
||||||
|
)
|
||||||
|
{
|
||||||
|
_twitch = (twitch as TwitchWebsocketClient)!;
|
||||||
|
_client = client;
|
||||||
|
_user = user;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient client)
|
||||||
|
{
|
||||||
|
var mention = values["mention"].ToLower();
|
||||||
|
var fragment = message.Message.Fragments.FirstOrDefault(f => f.Mention != null && f.Text.ToLower() == mention);
|
||||||
|
if (fragment?.Mention == null)
|
||||||
|
{
|
||||||
|
_logger.Warning("Cannot find the channel to leave chat from.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var subscriptionId = _twitch.GetSubscriptionId(_user.TwitchUserId.ToString(), "channel.chat.message");
|
||||||
|
if (subscriptionId == null)
|
||||||
|
{
|
||||||
|
_logger.Warning("Cannot find the subscription for that channel.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _client.DeleteEventSubscription(subscriptionId);
|
||||||
|
_twitch.RemoveSubscription(fragment.Mention.UserId, "channel.chat.message");
|
||||||
|
_logger.Information($"Joined chat room [channel: {fragment.Mention.UserLogin}][channel id: {fragment.Mention.UserId}][invoker: {message.ChatterUserLogin}][id: {message.ChatterUserId}]");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Failed to delete the subscription from Twitch.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,7 @@
|
|||||||
using HermesSocketLibrary.Socket.Data;
|
using HermesSocketLibrary.Socket.Data;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using TwitchChatTTS.Hermes.Socket;
|
using TwitchChatTTS.Hermes.Socket;
|
||||||
|
using TwitchChatTTS.Twitch.Socket;
|
||||||
using TwitchChatTTS.Twitch.Socket.Messages;
|
using TwitchChatTTS.Twitch.Socket.Messages;
|
||||||
using static TwitchChatTTS.Chat.Commands.TTSCommands;
|
using static TwitchChatTTS.Chat.Commands.TTSCommands;
|
||||||
|
|
||||||
@ -37,11 +38,11 @@ namespace TwitchChatTTS.Chat.Commands
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient client)
|
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}");
|
||||||
|
|
||||||
await client.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}.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,6 @@ namespace TwitchChatTTS.Chat.Commands
|
|||||||
public class VoiceCommand : IChatCommand
|
public class VoiceCommand : IChatCommand
|
||||||
{
|
{
|
||||||
private readonly User _user;
|
private readonly User _user;
|
||||||
// TODO: get permissions
|
|
||||||
// TODO: validated parameter for username by including '@' and regex for username
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public VoiceCommand(User user, ILogger logger)
|
public VoiceCommand(User user, ILogger logger)
|
||||||
@ -26,7 +24,7 @@ namespace TwitchChatTTS.Chat.Commands
|
|||||||
{
|
{
|
||||||
b.CreateVoiceNameParameter("voiceName", true)
|
b.CreateVoiceNameParameter("voiceName", true)
|
||||||
.CreateCommand(new TTSVoiceSelector(_user, _logger))
|
.CreateCommand(new TTSVoiceSelector(_user, _logger))
|
||||||
.CreateUnvalidatedParameter("chatter", optional: true)
|
.CreateMentionParameter("chatter", enabled: true, optional: true)
|
||||||
.AddPermission("tts.command.voice.admin")
|
.AddPermission("tts.command.voice.admin")
|
||||||
.CreateCommand(new TTSVoiceSelectorAdmin(_user, _logger));
|
.CreateCommand(new TTSVoiceSelectorAdmin(_user, _logger));
|
||||||
});
|
});
|
||||||
@ -45,7 +43,7 @@ namespace TwitchChatTTS.Chat.Commands
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient client)
|
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient hermes)
|
||||||
{
|
{
|
||||||
if (_user == null || _user.VoicesSelected == null)
|
if (_user == null || _user.VoicesSelected == null)
|
||||||
return;
|
return;
|
||||||
@ -57,12 +55,12 @@ namespace TwitchChatTTS.Chat.Commands
|
|||||||
|
|
||||||
if (_user.VoicesSelected.ContainsKey(chatterId))
|
if (_user.VoicesSelected.ContainsKey(chatterId))
|
||||||
{
|
{
|
||||||
await client.UpdateTTSUser(chatterId, voice.Key);
|
await hermes.UpdateTTSUser(chatterId, voice.Key);
|
||||||
_logger.Debug($"Sent request to create chat TTS voice [voice: {voice.Value}][username: {message.ChatterUserLogin}][reason: command]");
|
_logger.Debug($"Sent request to create chat TTS voice [voice: {voice.Value}][username: {message.ChatterUserLogin}][reason: command]");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await client.CreateTTSUser(chatterId, voice.Key);
|
await hermes.CreateTTSUser(chatterId, voice.Key);
|
||||||
_logger.Debug($"Sent request to update chat TTS voice [voice: {voice.Value}][username: {message.ChatterUserLogin}][reason: command]");
|
_logger.Debug($"Sent request to update chat TTS voice [voice: {voice.Value}][username: {message.ChatterUserLogin}][reason: command]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -81,13 +79,12 @@ namespace TwitchChatTTS.Chat.Commands
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient client)
|
public async Task Execute(IDictionary<string, string> values, ChannelChatMessage message, HermesSocketClient hermes)
|
||||||
{
|
{
|
||||||
if (_user == null || _user.VoicesSelected == null)
|
if (_user == null || _user.VoicesSelected == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var chatterLogin = values["chatter"].Substring(1);
|
var mention = message.Message.Fragments.FirstOrDefault(f => f.Mention != null && f.Text == values["chatter"])?.Mention;
|
||||||
var mention = message.Message.Fragments.FirstOrDefault(f => f.Mention != null && f.Mention.UserLogin == chatterLogin)?.Mention;
|
|
||||||
if (mention == null)
|
if (mention == null)
|
||||||
{
|
{
|
||||||
_logger.Warning("Failed to find the chatter to apply voice command to.");
|
_logger.Warning("Failed to find the chatter to apply voice command to.");
|
||||||
@ -101,12 +98,12 @@ namespace TwitchChatTTS.Chat.Commands
|
|||||||
|
|
||||||
if (_user.VoicesSelected.ContainsKey(chatterId))
|
if (_user.VoicesSelected.ContainsKey(chatterId))
|
||||||
{
|
{
|
||||||
await client.UpdateTTSUser(chatterId, voice.Key);
|
await hermes.UpdateTTSUser(chatterId, voice.Key);
|
||||||
_logger.Debug($"Sent request to create chat TTS voice [voice: {voice.Value}][username: {mention.UserLogin}][reason: command]");
|
_logger.Debug($"Sent request to create chat TTS voice [voice: {voice.Value}][username: {mention.UserLogin}][reason: command]");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await client.CreateTTSUser(chatterId, voice.Key);
|
await hermes.CreateTTSUser(chatterId, voice.Key);
|
||||||
_logger.Debug($"Sent request to update chat TTS voice [voice: {voice.Value}][username: {mention.UserLogin}][reason: command]");
|
_logger.Debug($"Sent request to update chat TTS voice [voice: {voice.Value}][username: {mention.UserLogin}][reason: command]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,62 +12,75 @@ namespace TwitchChatTTS.Chat.Groups
|
|||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
|
||||||
public ChatterGroupManager(ILogger logger) {
|
public ChatterGroupManager(ILogger logger)
|
||||||
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_groups = new ConcurrentDictionary<string, Group>();
|
_groups = new ConcurrentDictionary<string, Group>();
|
||||||
_chatters = new ConcurrentDictionary<long, ICollection<string>>();
|
_chatters = new ConcurrentDictionary<long, ICollection<string>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Add(Group group) {
|
public void Add(Group group)
|
||||||
|
{
|
||||||
_groups.Add(group.Name, group);
|
_groups.Add(group.Name, group);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Add(long chatter, string groupName) {
|
public void Add(long chatter, string groupName)
|
||||||
|
{
|
||||||
_chatters.Add(chatter, new List<string>() { groupName });
|
_chatters.Add(chatter, new List<string>() { groupName });
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Add(long chatter, ICollection<string> groupNames) {
|
public void Add(long chatter, ICollection<string> groupNames)
|
||||||
if (_chatters.TryGetValue(chatter, out var list)) {
|
{
|
||||||
|
if (_chatters.TryGetValue(chatter, out var list))
|
||||||
|
{
|
||||||
foreach (var group in groupNames)
|
foreach (var group in groupNames)
|
||||||
list.Add(group);
|
list.Add(group);
|
||||||
} else
|
}
|
||||||
|
else
|
||||||
_chatters.Add(chatter, groupNames);
|
_chatters.Add(chatter, groupNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Clear() {
|
public void Clear()
|
||||||
|
{
|
||||||
_groups.Clear();
|
_groups.Clear();
|
||||||
_chatters.Clear();
|
_chatters.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Group? Get(string groupName) {
|
public Group? Get(string groupName)
|
||||||
|
{
|
||||||
if (_groups.TryGetValue(groupName, out var group))
|
if (_groups.TryGetValue(groupName, out var group))
|
||||||
return group;
|
return group;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<string> GetGroupNamesFor(long chatter) {
|
public IEnumerable<string> GetGroupNamesFor(long chatter)
|
||||||
|
{
|
||||||
if (_chatters.TryGetValue(chatter, out var groups))
|
if (_chatters.TryGetValue(chatter, out var groups))
|
||||||
return groups.Select(g => _groups[g].Name);
|
return groups.Select(g => _groups[g].Name);
|
||||||
|
|
||||||
return Array.Empty<string>();
|
return Array.Empty<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int GetPriorityFor(long chatter) {
|
public int GetPriorityFor(long chatter)
|
||||||
|
{
|
||||||
if (!_chatters.TryGetValue(chatter, out var groups))
|
if (!_chatters.TryGetValue(chatter, out var groups))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
return GetPriorityFor(groups);
|
return GetPriorityFor(groups);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int GetPriorityFor(IEnumerable<string> groupNames) {
|
public int GetPriorityFor(IEnumerable<string> groupNames)
|
||||||
|
{
|
||||||
var values = groupNames.Select(g => _groups.TryGetValue(g, out var group) ? group : null).Where(g => g != null);
|
var values = groupNames.Select(g => _groups.TryGetValue(g, out var group) ? group : null).Where(g => g != null);
|
||||||
if (values.Any())
|
if (values.Any())
|
||||||
return values.Max(g => g.Priority);
|
return values.Max(g => g.Priority);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Remove(long chatterId, string groupId) {
|
public bool Remove(long chatterId, string groupId)
|
||||||
if (_chatters.TryGetValue(chatterId, out var groups)) {
|
{
|
||||||
|
if (_chatters.TryGetValue(chatterId, out var groups))
|
||||||
|
{
|
||||||
groups.Remove(groupId);
|
groups.Remove(groupId);
|
||||||
_logger.Debug($"Removed chatter from group [chatter id: {chatterId}][group name: {_groups[groupId]}][group id: {groupId}]");
|
_logger.Debug($"Removed chatter from group [chatter id: {chatterId}][group name: {_groups[groupId]}][group id: {groupId}]");
|
||||||
return true;
|
return true;
|
||||||
|
@ -23,6 +23,13 @@ namespace TwitchChatTTS.Chat.Groups.Permissions
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool? CheckIfDirectAllowed(string path)
|
||||||
|
{
|
||||||
|
var res = Get(path)?.DirectAllow;
|
||||||
|
_logger.Debug($"Permission Node GET {path} = {res?.ToString() ?? "null"} [direct]");
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
public bool? CheckIfAllowed(IEnumerable<string> groups, string path)
|
public bool? CheckIfAllowed(IEnumerable<string> groups, string path)
|
||||||
{
|
{
|
||||||
bool overall = false;
|
bool overall = false;
|
||||||
@ -37,6 +44,20 @@ namespace TwitchChatTTS.Chat.Groups.Permissions
|
|||||||
return overall ? true : null;
|
return overall ? true : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool? CheckIfDirectAllowed(IEnumerable<string> groups, string path)
|
||||||
|
{
|
||||||
|
bool overall = false;
|
||||||
|
foreach (var group in groups)
|
||||||
|
{
|
||||||
|
var result = CheckIfDirectAllowed($"{group}.{path}");
|
||||||
|
if (result == false)
|
||||||
|
return false;
|
||||||
|
if (result == true)
|
||||||
|
overall = true;
|
||||||
|
}
|
||||||
|
return overall ? true : null;
|
||||||
|
}
|
||||||
|
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
_root.Clear();
|
_root.Clear();
|
||||||
@ -104,6 +125,7 @@ namespace TwitchChatTTS.Chat.Groups.Permissions
|
|||||||
}
|
}
|
||||||
set => _allow = value;
|
set => _allow = value;
|
||||||
}
|
}
|
||||||
|
public bool? DirectAllow { get => _allow; }
|
||||||
|
|
||||||
internal PermissionNode? Parent { get => _parent; }
|
internal PermissionNode? Parent { get => _parent; }
|
||||||
public IList<PermissionNode>? Children { get => _children == null ? null : new ReadOnlyCollection<PermissionNode>(_children); }
|
public IList<PermissionNode>? Children { get => _children == null ? null : new ReadOnlyCollection<PermissionNode>(_children); }
|
||||||
|
@ -5,6 +5,8 @@ namespace TwitchChatTTS.Chat.Groups.Permissions
|
|||||||
void Set(string path, bool? allow);
|
void Set(string path, bool? allow);
|
||||||
bool? CheckIfAllowed(string path);
|
bool? CheckIfAllowed(string path);
|
||||||
bool? CheckIfAllowed(IEnumerable<string> groups, string path);
|
bool? CheckIfAllowed(IEnumerable<string> groups, string path);
|
||||||
|
bool? CheckIfDirectAllowed(string path);
|
||||||
|
bool? CheckIfDirectAllowed(IEnumerable<string> groups, string path);
|
||||||
void Clear();
|
void Clear();
|
||||||
bool Remove(string path);
|
bool Remove(string path);
|
||||||
}
|
}
|
||||||
|
@ -101,13 +101,14 @@ public class TTSPlayer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveAll(long chatterId)
|
public void RemoveAll(long broadcasterId, long chatterId)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_mutex2.WaitOne();
|
_mutex2.WaitOne();
|
||||||
if (_buffer.UnorderedItems.Any(i => i.Element.ChatterId == chatterId)) {
|
if (_buffer.UnorderedItems.Any(i => i.Element.RoomId == broadcasterId && i.Element.ChatterId == chatterId))
|
||||||
var list = _buffer.UnorderedItems.Where(i => i.Element.ChatterId != chatterId).ToArray();
|
{
|
||||||
|
var list = _buffer.UnorderedItems.Where(i => i.Element.RoomId == broadcasterId && i.Element.ChatterId != chatterId).ToArray();
|
||||||
_buffer.Clear();
|
_buffer.Clear();
|
||||||
foreach (var item in list)
|
foreach (var item in list)
|
||||||
_buffer.Enqueue(item.Element, item.Element.Priority);
|
_buffer.Enqueue(item.Element, item.Element.Priority);
|
||||||
@ -121,8 +122,9 @@ public class TTSPlayer
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
_mutex.WaitOne();
|
_mutex.WaitOne();
|
||||||
if (_messages.UnorderedItems.Any(i => i.Element.ChatterId == chatterId)) {
|
if (_messages.UnorderedItems.Any(i => i.Element.RoomId == broadcasterId && i.Element.ChatterId == chatterId))
|
||||||
var list = _messages.UnorderedItems.Where(i => i.Element.ChatterId != chatterId).ToArray();
|
{
|
||||||
|
var list = _messages.UnorderedItems.Where(i => i.Element.RoomId == broadcasterId && i.Element.ChatterId != chatterId).ToArray();
|
||||||
_messages.Clear();
|
_messages.Clear();
|
||||||
foreach (var item in list)
|
foreach (var item in list)
|
||||||
_messages.Enqueue(item.Element, item.Element.Priority);
|
_messages.Enqueue(item.Element, item.Element.Priority);
|
||||||
@ -139,7 +141,8 @@ public class TTSPlayer
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
_mutex2.WaitOne();
|
_mutex2.WaitOne();
|
||||||
if (_buffer.UnorderedItems.Any(i => i.Element.MessageId == messageId)) {
|
if (_buffer.UnorderedItems.Any(i => i.Element.MessageId == messageId))
|
||||||
|
{
|
||||||
var list = _buffer.UnorderedItems.Where(i => i.Element.MessageId != messageId).ToArray();
|
var list = _buffer.UnorderedItems.Where(i => i.Element.MessageId != messageId).ToArray();
|
||||||
_buffer.Clear();
|
_buffer.Clear();
|
||||||
foreach (var item in list)
|
foreach (var item in list)
|
||||||
@ -155,7 +158,8 @@ public class TTSPlayer
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
_mutex.WaitOne();
|
_mutex.WaitOne();
|
||||||
if (_messages.UnorderedItems.Any(i => i.Element.MessageId == messageId)) {
|
if (_messages.UnorderedItems.Any(i => i.Element.MessageId == messageId))
|
||||||
|
{
|
||||||
var list = _messages.UnorderedItems.Where(i => i.Element.MessageId != messageId).ToArray();
|
var list = _messages.UnorderedItems.Where(i => i.Element.MessageId != messageId).ToArray();
|
||||||
_messages.Clear();
|
_messages.Clear();
|
||||||
foreach (var item in list)
|
foreach (var item in list)
|
||||||
@ -182,6 +186,7 @@ public class TTSPlayer
|
|||||||
public class TTSMessage
|
public class TTSMessage
|
||||||
{
|
{
|
||||||
public string? Voice { get; set; }
|
public string? Voice { get; set; }
|
||||||
|
public long RoomId { get; set; }
|
||||||
public long ChatterId { get; set; }
|
public long ChatterId { get; set; }
|
||||||
public string MessageId { get; set; }
|
public string MessageId { get; set; }
|
||||||
public string? Message { get; set; }
|
public string? Message { get; set; }
|
||||||
|
@ -43,5 +43,15 @@ namespace TwitchChatTTS.Helpers
|
|||||||
{
|
{
|
||||||
return await _client.PostAsJsonAsync(uri, new object(), _options);
|
return await _client.PostAsJsonAsync(uri, new object(), _options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<T?> Delete<T>(string uri)
|
||||||
|
{
|
||||||
|
return await _client.DeleteFromJsonAsync<T>(uri, _options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<HttpResponseMessage> Delete(string uri)
|
||||||
|
{
|
||||||
|
return await _client.DeleteAsync(uri);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,6 @@
|
|||||||
public class Account {
|
public class Account {
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
public string Username { get; set; }
|
public string Username { get; set; }
|
||||||
|
public string Role { get; set; }
|
||||||
|
public string? BroadcasterId { get; set; }
|
||||||
}
|
}
|
@ -1,6 +1,7 @@
|
|||||||
namespace TwitchChatTTS.Hermes
|
namespace TwitchChatTTS.Hermes
|
||||||
{
|
{
|
||||||
public interface ICustomDataManager {
|
public interface ICustomDataManager
|
||||||
|
{
|
||||||
void Add(string key, object value, string type);
|
void Add(string key, object value, string type);
|
||||||
void Change(string key, object value);
|
void Change(string key, object value);
|
||||||
void Delete(string key);
|
void Delete(string key);
|
||||||
@ -11,7 +12,8 @@ namespace TwitchChatTTS.Hermes
|
|||||||
{
|
{
|
||||||
private IDictionary<string, DataInfo> _data;
|
private IDictionary<string, DataInfo> _data;
|
||||||
|
|
||||||
public CustomDataManager() {
|
public CustomDataManager()
|
||||||
|
{
|
||||||
_data = new Dictionary<string, DataInfo>();
|
_data = new Dictionary<string, DataInfo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,8 +39,9 @@ namespace TwitchChatTTS.Hermes
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// type: text (string), whole number (int), number (double), boolean, formula (string, data type of number)
|
// type: text (string), whole number (int), number (double), boolean, formula (string, data type of number)
|
||||||
public struct DataInfo {
|
public struct DataInfo
|
||||||
|
{
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
public string Type { get; set; }
|
public string Type { get; set; }
|
||||||
public object Value { get; set; }
|
public object Value { get; set; }
|
||||||
|
@ -10,7 +10,7 @@ public class HermesApiClient
|
|||||||
private readonly TwitchBotAuth _token;
|
private readonly TwitchBotAuth _token;
|
||||||
private readonly WebClientWrap _web;
|
private readonly WebClientWrap _web;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public const string BASE_URL = "tomtospeech.com";
|
public const string BASE_URL = "tomtospeech.com";
|
||||||
|
|
||||||
public HermesApiClient(TwitchBotAuth token, Configuration configuration, ILogger logger)
|
public HermesApiClient(TwitchBotAuth token, Configuration configuration, ILogger logger)
|
||||||
@ -31,7 +31,7 @@ public class HermesApiClient
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<bool> AuthorizeTwitch()
|
public async Task<TwitchBotAuth?> AuthorizeTwitch()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -51,10 +51,9 @@ public class HermesApiClient
|
|||||||
else if (authorize != null)
|
else if (authorize != null)
|
||||||
{
|
{
|
||||||
_logger.Error("Twitch API Authorization failed: " + authorize.AccessToken + " | " + authorize.RefreshToken + " | " + authorize.UserId + " | " + authorize.BroadcasterId);
|
_logger.Error("Twitch API Authorization failed: " + authorize.AccessToken + " | " + authorize.RefreshToken + " | " + authorize.UserId + " | " + authorize.BroadcasterId);
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
_logger.Debug($"Authorized Twitch API.");
|
return _token;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
catch (JsonException)
|
catch (JsonException)
|
||||||
{
|
{
|
||||||
@ -64,7 +63,7 @@ public class HermesApiClient
|
|||||||
{
|
{
|
||||||
_logger.Error(e, "Failed to authorize to Twitch API.");
|
_logger.Error(e, "Failed to authorize to Twitch API.");
|
||||||
}
|
}
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<TTSVersion?> GetLatestTTSVersion()
|
public async Task<TTSVersion?> GetLatestTTSVersion()
|
||||||
@ -74,7 +73,10 @@ public class HermesApiClient
|
|||||||
|
|
||||||
public async Task<Account> FetchHermesAccountDetails()
|
public async Task<Account> FetchHermesAccountDetails()
|
||||||
{
|
{
|
||||||
var account = await _web.GetJson<Account>($"https://{BASE_URL}/api/account");
|
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)
|
if (account == null || account.Id == null || account.Username == null)
|
||||||
throw new NullReferenceException("Invalid value found while fetching for hermes account data.");
|
throw new NullReferenceException("Invalid value found while fetching for hermes account data.");
|
||||||
return account;
|
return account;
|
||||||
|
@ -18,7 +18,6 @@ namespace TwitchChatTTS.Hermes.Socket.Handlers
|
|||||||
public class RequestAckHandler : IWebSocketHandler
|
public class RequestAckHandler : IWebSocketHandler
|
||||||
{
|
{
|
||||||
private User _user;
|
private User _user;
|
||||||
//private readonly RedemptionManager _redemptionManager;
|
|
||||||
private readonly ICallbackManager<HermesRequestData> _callbackManager;
|
private readonly ICallbackManager<HermesRequestData> _callbackManager;
|
||||||
private readonly IServiceProvider _serviceProvider;
|
private readonly IServiceProvider _serviceProvider;
|
||||||
private readonly JsonSerializerOptions _options;
|
private readonly JsonSerializerOptions _options;
|
||||||
@ -30,16 +29,16 @@ namespace TwitchChatTTS.Hermes.Socket.Handlers
|
|||||||
|
|
||||||
|
|
||||||
public RequestAckHandler(
|
public RequestAckHandler(
|
||||||
User user,
|
|
||||||
ICallbackManager<HermesRequestData> callbackManager,
|
ICallbackManager<HermesRequestData> callbackManager,
|
||||||
IServiceProvider serviceProvider,
|
IServiceProvider serviceProvider,
|
||||||
|
User user,
|
||||||
JsonSerializerOptions options,
|
JsonSerializerOptions options,
|
||||||
ILogger logger
|
ILogger logger
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_user = user;
|
|
||||||
_callbackManager = callbackManager;
|
_callbackManager = callbackManager;
|
||||||
_serviceProvider = serviceProvider;
|
_serviceProvider = serviceProvider;
|
||||||
|
_user = user;
|
||||||
_options = options;
|
_options = options;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
@ -263,15 +262,15 @@ namespace TwitchChatTTS.Hermes.Socket.Handlers
|
|||||||
re.Match(string.Empty);
|
re.Match(string.Empty);
|
||||||
filter.Regex = re;
|
filter.Regex = re;
|
||||||
}
|
}
|
||||||
catch (Exception e) { }
|
catch (Exception) { }
|
||||||
}
|
}
|
||||||
_user.RegexFilters = filters;
|
_user.RegexFilters = filters;
|
||||||
_logger.Information($"TTS word filters [count: {_user.RegexFilters.Count()}] have been refreshed.");
|
_logger.Information($"TTS word filters [count: {_user.RegexFilters.Count()}] have been refreshed.");
|
||||||
}
|
}
|
||||||
else if (message.Request.Type == "update_tts_voice_state")
|
else if (message.Request.Type == "update_tts_voice_state")
|
||||||
{
|
{
|
||||||
string voiceId = message.Request.Data["voice"].ToString();
|
string voiceId = message.Request.Data?["voice"].ToString()!;
|
||||||
bool state = message.Request.Data["state"].ToString().ToLower() == "true";
|
bool state = message.Request.Data?["state"].ToString()!.ToLower() == "true";
|
||||||
|
|
||||||
if (!_user.VoicesAvailable.TryGetValue(voiceId, out string? voiceName) || voiceName == null)
|
if (!_user.VoicesAvailable.TryGetValue(voiceId, out string? voiceName) || voiceName == null)
|
||||||
{
|
{
|
||||||
@ -305,14 +304,14 @@ namespace TwitchChatTTS.Hermes.Socket.Handlers
|
|||||||
_logger.Warning("Failed to read the redeemable actions for redemptions.");
|
_logger.Warning("Failed to read the redeemable actions for redemptions.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (hermesRequestData?.Data == null || !(hermesRequestData.Data["redemptions"] is IEnumerable<Redemption> redemptions))
|
if (hermesRequestData?.Data == null || hermesRequestData.Data["redemptions"] is not IEnumerable<Redemption> redemptions)
|
||||||
{
|
{
|
||||||
_logger.Warning("Failed to read the redemptions while updating redemption actions.");
|
_logger.Warning("Failed to read the redemptions while updating redemption actions.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.Information($"Redeemable actions [count: {actions.Count()}] loaded.");
|
_logger.Information($"Redeemable actions [count: {actions.Count()}] loaded.");
|
||||||
var redemptionManager = _serviceProvider.GetRequiredService<RedemptionManager>();
|
var redemptionManager = _serviceProvider.GetRequiredService<IRedemptionManager>();
|
||||||
redemptionManager.Initialize(redemptions, actions.ToDictionary(a => a.Name, a => a));
|
redemptionManager.Initialize(redemptions, actions.ToDictionary(a => a.Name, a => a));
|
||||||
}
|
}
|
||||||
else if (message.Request.Type == "get_default_tts_voice")
|
else if (message.Request.Type == "get_default_tts_voice")
|
||||||
|
@ -104,7 +104,8 @@ namespace TwitchChatTTS.Hermes.Socket
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task FetchChatterIdentifiers() {
|
public async Task FetchChatterIdentifiers()
|
||||||
|
{
|
||||||
await Send(3, new RequestMessage()
|
await Send(3, new RequestMessage()
|
||||||
{
|
{
|
||||||
Type = "get_chatter_ids",
|
Type = "get_chatter_ids",
|
||||||
@ -112,7 +113,8 @@ namespace TwitchChatTTS.Hermes.Socket
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task FetchDefaultTTSVoice() {
|
public async Task FetchDefaultTTSVoice()
|
||||||
|
{
|
||||||
await Send(3, new RequestMessage()
|
await Send(3, new RequestMessage()
|
||||||
{
|
{
|
||||||
Type = "get_default_tts_voice",
|
Type = "get_default_tts_voice",
|
||||||
@ -120,7 +122,8 @@ namespace TwitchChatTTS.Hermes.Socket
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task FetchEmotes() {
|
public async Task FetchEmotes()
|
||||||
|
{
|
||||||
await Send(3, new RequestMessage()
|
await Send(3, new RequestMessage()
|
||||||
{
|
{
|
||||||
Type = "get_emotes",
|
Type = "get_emotes",
|
||||||
@ -128,7 +131,8 @@ namespace TwitchChatTTS.Hermes.Socket
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task FetchEnabledTTSVoices() {
|
public async Task FetchEnabledTTSVoices()
|
||||||
|
{
|
||||||
await Send(3, new RequestMessage()
|
await Send(3, new RequestMessage()
|
||||||
{
|
{
|
||||||
Type = "get_enabled_tts_voices",
|
Type = "get_enabled_tts_voices",
|
||||||
@ -136,7 +140,8 @@ namespace TwitchChatTTS.Hermes.Socket
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task FetchTTSVoices() {
|
public async Task FetchTTSVoices()
|
||||||
|
{
|
||||||
await Send(3, new RequestMessage()
|
await Send(3, new RequestMessage()
|
||||||
{
|
{
|
||||||
Type = "get_tts_voices",
|
Type = "get_tts_voices",
|
||||||
@ -144,7 +149,8 @@ namespace TwitchChatTTS.Hermes.Socket
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task FetchTTSChatterVoices() {
|
public async Task FetchTTSChatterVoices()
|
||||||
|
{
|
||||||
await Send(3, new RequestMessage()
|
await Send(3, new RequestMessage()
|
||||||
{
|
{
|
||||||
Type = "get_tts_users",
|
Type = "get_tts_users",
|
||||||
@ -152,7 +158,8 @@ namespace TwitchChatTTS.Hermes.Socket
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task FetchTTSWordFilters() {
|
public async Task FetchTTSWordFilters()
|
||||||
|
{
|
||||||
await Send(3, new RequestMessage()
|
await Send(3, new RequestMessage()
|
||||||
{
|
{
|
||||||
Type = "get_tts_word_filters",
|
Type = "get_tts_word_filters",
|
||||||
|
@ -23,7 +23,7 @@ namespace TwitchChatTTS.OBS.Socket
|
|||||||
public bool Identified { get; set; }
|
public bool Identified { get; set; }
|
||||||
public bool Streaming { get; set; }
|
public bool Streaming { get; set; }
|
||||||
|
|
||||||
|
|
||||||
public OBSSocketClient(
|
public OBSSocketClient(
|
||||||
Configuration configuration,
|
Configuration configuration,
|
||||||
[FromKeyedServices("obs")] IEnumerable<IWebSocketHandler> handlers,
|
[FromKeyedServices("obs")] IEnumerable<IWebSocketHandler> handlers,
|
||||||
@ -104,7 +104,8 @@ namespace TwitchChatTTS.OBS.Socket
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ExecuteRequest(RequestResponseMessage message) {
|
public async Task ExecuteRequest(RequestResponseMessage message)
|
||||||
|
{
|
||||||
if (!_handlers.TryGetValue(7, out var handler) || handler == null)
|
if (!_handlers.TryGetValue(7, out var handler) || handler == null)
|
||||||
{
|
{
|
||||||
_logger.Error("Failed to find the request response handler for OBS.");
|
_logger.Error("Failed to find the request response handler for OBS.");
|
||||||
|
@ -54,7 +54,8 @@ namespace TwitchChatTTS.Seven.Socket.Handlers
|
|||||||
{
|
{
|
||||||
if (removing)
|
if (removing)
|
||||||
{
|
{
|
||||||
if (_emotes.Get(o.Name) != o.Id) {
|
if (_emotes.Get(o.Name) != o.Id)
|
||||||
|
{
|
||||||
_logger.Warning("Mismatched emote found while removing a 7tv emote.");
|
_logger.Warning("Mismatched emote found while removing a 7tv emote.");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -63,7 +64,8 @@ namespace TwitchChatTTS.Seven.Socket.Handlers
|
|||||||
}
|
}
|
||||||
else if (updater != null)
|
else if (updater != null)
|
||||||
{
|
{
|
||||||
if (_emotes.Get(o.Name) != o.Id) {
|
if (_emotes.Get(o.Name) != o.Id)
|
||||||
|
{
|
||||||
_logger.Warning("Mismatched emote found while updating a 7tv emote.");
|
_logger.Warning("Mismatched emote found while updating a 7tv emote.");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -120,7 +120,7 @@ namespace TwitchChatTTS.Seven.Socket
|
|||||||
_logger.Warning($"Received end of stream message for 7tv websocket [reason: {_errorCodes[code]}][code: {code}]");
|
_logger.Warning($"Received end of stream message for 7tv websocket [reason: {_errorCodes[code]}][code: {code}]");
|
||||||
else
|
else
|
||||||
_logger.Warning($"Received end of stream message for 7tv websocket [code: {code}]");
|
_logger.Warning($"Received end of stream message for 7tv websocket [code: {code}]");
|
||||||
|
|
||||||
if (code < 0 || code >= _reconnectDelay.Length)
|
if (code < 0 || code >= _reconnectDelay.Length)
|
||||||
await Task.Delay(TimeSpan.FromSeconds(30));
|
await Task.Delay(TimeSpan.FromSeconds(30));
|
||||||
else if (_reconnectDelay[code] < 0)
|
else if (_reconnectDelay[code] < 0)
|
||||||
@ -131,7 +131,8 @@ namespace TwitchChatTTS.Seven.Socket
|
|||||||
else if (_reconnectDelay[code] > 0)
|
else if (_reconnectDelay[code] > 0)
|
||||||
await Task.Delay(_reconnectDelay[code]);
|
await Task.Delay(_reconnectDelay[code]);
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
|
{
|
||||||
_logger.Warning("Unknown 7tv disconnection.");
|
_logger.Warning("Unknown 7tv disconnection.");
|
||||||
await Task.Delay(TimeSpan.FromSeconds(30));
|
await Task.Delay(TimeSpan.FromSeconds(30));
|
||||||
}
|
}
|
||||||
|
31
Startup.cs
31
Startup.cs
@ -28,6 +28,7 @@ using static TwitchChatTTS.Chat.Commands.TTSCommands;
|
|||||||
using TwitchChatTTS.Twitch.Socket;
|
using TwitchChatTTS.Twitch.Socket;
|
||||||
using TwitchChatTTS.Twitch.Socket.Messages;
|
using TwitchChatTTS.Twitch.Socket.Messages;
|
||||||
using TwitchChatTTS.Twitch.Socket.Handlers;
|
using TwitchChatTTS.Twitch.Socket.Handlers;
|
||||||
|
using CommonSocketLibrary.Backoff;
|
||||||
|
|
||||||
// dotnet publish -r linux-x64 -p:PublishSingleFile=true --self-contained true
|
// dotnet publish -r linux-x64 -p:PublishSingleFile=true --self-contained true
|
||||||
// dotnet publish -r win-x64 -p:PublishSingleFile=true --self-contained true
|
// dotnet publish -r win-x64 -p:PublishSingleFile=true --self-contained true
|
||||||
@ -43,13 +44,10 @@ var deserializer = new DeserializerBuilder()
|
|||||||
|
|
||||||
var configContent = File.ReadAllText("tts.config.yml");
|
var configContent = File.ReadAllText("tts.config.yml");
|
||||||
var configuration = deserializer.Deserialize<Configuration>(configContent);
|
var configuration = deserializer.Deserialize<Configuration>(configContent);
|
||||||
s.AddSingleton<Configuration>(configuration);
|
s.AddSingleton(configuration);
|
||||||
|
|
||||||
var logger = new LoggerConfiguration()
|
var logger = new LoggerConfiguration()
|
||||||
.MinimumLevel.Verbose()
|
.MinimumLevel.Verbose()
|
||||||
//.MinimumLevel.Override("TwitchLib.Communication.Clients.WebSocketClient", LogEventLevel.Warning)
|
|
||||||
//.MinimumLevel.Override("TwitchLib.PubSub.TwitchPubSub", LogEventLevel.Warning)
|
|
||||||
.MinimumLevel.Override("TwitchLib", LogEventLevel.Warning)
|
|
||||||
.MinimumLevel.Override("mariuszgromada", LogEventLevel.Error)
|
.MinimumLevel.Override("mariuszgromada", LogEventLevel.Error)
|
||||||
.Enrich.FromLogContext()
|
.Enrich.FromLogContext()
|
||||||
.WriteTo.File("logs/log-.log", restrictedToMinimumLevel: LogEventLevel.Debug, rollingInterval: RollingInterval.Day, retainedFileCountLimit: 3)
|
.WriteTo.File("logs/log-.log", restrictedToMinimumLevel: LogEventLevel.Debug, rollingInterval: RollingInterval.Day, retainedFileCountLimit: 3)
|
||||||
@ -57,11 +55,11 @@ var logger = new LoggerConfiguration()
|
|||||||
.CreateLogger();
|
.CreateLogger();
|
||||||
|
|
||||||
s.AddSerilog(logger);
|
s.AddSerilog(logger);
|
||||||
s.AddSingleton<User>(new User());
|
s.AddSingleton<User>();
|
||||||
s.AddSingleton<AudioPlaybackEngine>();
|
s.AddSingleton<AudioPlaybackEngine>();
|
||||||
s.AddSingleton<ICallbackManager<HermesRequestData>, CallbackManager<HermesRequestData>>();
|
s.AddSingleton<ICallbackManager<HermesRequestData>, CallbackManager<HermesRequestData>>();
|
||||||
|
|
||||||
s.AddSingleton<JsonSerializerOptions>(new JsonSerializerOptions()
|
s.AddSingleton(new JsonSerializerOptions()
|
||||||
{
|
{
|
||||||
PropertyNameCaseInsensitive = false,
|
PropertyNameCaseInsensitive = false,
|
||||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
|
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
|
||||||
@ -77,10 +75,11 @@ s.AddSingleton<IChatCommand, VersionCommand>();
|
|||||||
s.AddSingleton<ICommandBuilder, CommandBuilder>();
|
s.AddSingleton<ICommandBuilder, CommandBuilder>();
|
||||||
s.AddSingleton<IChatterGroupManager, ChatterGroupManager>();
|
s.AddSingleton<IChatterGroupManager, ChatterGroupManager>();
|
||||||
s.AddSingleton<IGroupPermissionManager, GroupPermissionManager>();
|
s.AddSingleton<IGroupPermissionManager, GroupPermissionManager>();
|
||||||
s.AddSingleton<CommandManager>();
|
s.AddSingleton<ICommandFactory, CommandFactory>();
|
||||||
|
s.AddSingleton<ICommandManager, CommandManager>();
|
||||||
|
|
||||||
s.AddSingleton<TTSPlayer>();
|
s.AddSingleton<TTSPlayer>();
|
||||||
s.AddSingleton<RedemptionManager>();
|
s.AddSingleton<IRedemptionManager, RedemptionManager>();
|
||||||
s.AddSingleton<HermesApiClient>();
|
s.AddSingleton<HermesApiClient>();
|
||||||
s.AddSingleton<TwitchBotAuth>();
|
s.AddSingleton<TwitchBotAuth>();
|
||||||
s.AddSingleton<TwitchApiClient>();
|
s.AddSingleton<TwitchApiClient>();
|
||||||
@ -109,19 +108,33 @@ s.AddKeyedSingleton<MessageTypeManager<IWebSocketHandler>, SevenMessageTypeManag
|
|||||||
s.AddKeyedSingleton<SocketClient<WebSocketMessage>, SevenSocketClient>("7tv");
|
s.AddKeyedSingleton<SocketClient<WebSocketMessage>, SevenSocketClient>("7tv");
|
||||||
|
|
||||||
// twitch websocket
|
// twitch websocket
|
||||||
s.AddKeyedSingleton<SocketClient<TwitchWebsocketMessage>, TwitchWebsocketClient>("twitch");
|
s.AddKeyedSingleton<IBackoff>("twitch", new ExponentialBackoff(1000, 120 * 1000));
|
||||||
|
s.AddSingleton<ITwitchConnectionManager, TwitchConnectionManager>();
|
||||||
|
s.AddKeyedTransient<SocketClient<TwitchWebsocketMessage>, TwitchWebsocketClient>("twitch", (sp, _) =>
|
||||||
|
{
|
||||||
|
var factory = sp.GetRequiredService<ITwitchConnectionManager>();
|
||||||
|
var client = factory.GetWorkingClient();
|
||||||
|
client.Connect().Wait();
|
||||||
|
return client;
|
||||||
|
});
|
||||||
|
s.AddKeyedTransient<SocketClient<TwitchWebsocketMessage>, TwitchWebsocketClient>("twitch-create");
|
||||||
|
|
||||||
|
s.AddKeyedSingleton<ITwitchSocketHandler, SessionKeepAliveHandler>("twitch");
|
||||||
s.AddKeyedSingleton<ITwitchSocketHandler, SessionWelcomeHandler>("twitch");
|
s.AddKeyedSingleton<ITwitchSocketHandler, SessionWelcomeHandler>("twitch");
|
||||||
s.AddKeyedSingleton<ITwitchSocketHandler, SessionReconnectHandler>("twitch");
|
s.AddKeyedSingleton<ITwitchSocketHandler, SessionReconnectHandler>("twitch");
|
||||||
s.AddKeyedSingleton<ITwitchSocketHandler, NotificationHandler>("twitch");
|
s.AddKeyedSingleton<ITwitchSocketHandler, NotificationHandler>("twitch");
|
||||||
|
|
||||||
|
s.AddKeyedSingleton<ITwitchSocketHandler, ChannelAdBreakHandler>("twitch-notifications");
|
||||||
s.AddKeyedSingleton<ITwitchSocketHandler, ChannelBanHandler>("twitch-notifications");
|
s.AddKeyedSingleton<ITwitchSocketHandler, ChannelBanHandler>("twitch-notifications");
|
||||||
s.AddKeyedSingleton<ITwitchSocketHandler, ChannelChatMessageHandler>("twitch-notifications");
|
s.AddKeyedSingleton<ITwitchSocketHandler, ChannelChatMessageHandler>("twitch-notifications");
|
||||||
s.AddKeyedSingleton<ITwitchSocketHandler, ChannelChatClearUserHandler>("twitch-notifications");
|
s.AddKeyedSingleton<ITwitchSocketHandler, ChannelChatClearUserHandler>("twitch-notifications");
|
||||||
s.AddKeyedSingleton<ITwitchSocketHandler, ChannelChatClearHandler>("twitch-notifications");
|
s.AddKeyedSingleton<ITwitchSocketHandler, ChannelChatClearHandler>("twitch-notifications");
|
||||||
s.AddKeyedSingleton<ITwitchSocketHandler, ChannelChatDeleteMessageHandler>("twitch-notifications");
|
s.AddKeyedSingleton<ITwitchSocketHandler, ChannelChatDeleteMessageHandler>("twitch-notifications");
|
||||||
s.AddKeyedSingleton<ITwitchSocketHandler, ChannelCustomRedemptionHandler>("twitch-notifications");
|
s.AddKeyedSingleton<ITwitchSocketHandler, ChannelCustomRedemptionHandler>("twitch-notifications");
|
||||||
|
s.AddKeyedSingleton<ITwitchSocketHandler, ChannelFollowHandler>("twitch-notifications");
|
||||||
|
s.AddKeyedSingleton<ITwitchSocketHandler, ChannelResubscriptionHandler>("twitch-notifications");
|
||||||
s.AddKeyedSingleton<ITwitchSocketHandler, ChannelSubscriptionHandler>("twitch-notifications");
|
s.AddKeyedSingleton<ITwitchSocketHandler, ChannelSubscriptionHandler>("twitch-notifications");
|
||||||
|
s.AddKeyedSingleton<ITwitchSocketHandler, ChannelSubscriptionGiftHandler>("twitch-notifications");
|
||||||
|
|
||||||
// hermes websocket
|
// hermes websocket
|
||||||
s.AddKeyedSingleton<IWebSocketHandler, HeartbeatHandler>("hermes");
|
s.AddKeyedSingleton<IWebSocketHandler, HeartbeatHandler>("hermes");
|
||||||
|
83
TTS.cs
83
TTS.cs
@ -13,6 +13,7 @@ using CommonSocketLibrary.Common;
|
|||||||
using TwitchChatTTS.OBS.Socket;
|
using TwitchChatTTS.OBS.Socket;
|
||||||
using TwitchChatTTS.Twitch.Socket.Messages;
|
using TwitchChatTTS.Twitch.Socket.Messages;
|
||||||
using TwitchChatTTS.Twitch.Socket;
|
using TwitchChatTTS.Twitch.Socket;
|
||||||
|
using TwitchChatTTS.Chat.Commands;
|
||||||
|
|
||||||
namespace TwitchChatTTS
|
namespace TwitchChatTTS
|
||||||
{
|
{
|
||||||
@ -24,45 +25,51 @@ namespace TwitchChatTTS
|
|||||||
private readonly User _user;
|
private readonly User _user;
|
||||||
private readonly HermesApiClient _hermesApiClient;
|
private readonly HermesApiClient _hermesApiClient;
|
||||||
private readonly SevenApiClient _sevenApiClient;
|
private readonly SevenApiClient _sevenApiClient;
|
||||||
|
private readonly TwitchApiClient _twitchApiClient;
|
||||||
private readonly HermesSocketClient _hermes;
|
private readonly HermesSocketClient _hermes;
|
||||||
private readonly OBSSocketClient _obs;
|
private readonly OBSSocketClient _obs;
|
||||||
private readonly SevenSocketClient _seven;
|
private readonly SevenSocketClient _seven;
|
||||||
private readonly TwitchWebsocketClient _twitch;
|
private readonly TwitchWebsocketClient _twitch;
|
||||||
|
private readonly ICommandFactory _commandFactory;
|
||||||
|
private readonly ICommandManager _commandManager;
|
||||||
private readonly IEmoteDatabase _emotes;
|
private readonly IEmoteDatabase _emotes;
|
||||||
private readonly Configuration _configuration;
|
|
||||||
private readonly TTSPlayer _player;
|
private readonly TTSPlayer _player;
|
||||||
private readonly AudioPlaybackEngine _playback;
|
private readonly AudioPlaybackEngine _playback;
|
||||||
private readonly IServiceProvider _serviceProvider;
|
private readonly Configuration _configuration;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public TTS(
|
public TTS(
|
||||||
User user,
|
User user,
|
||||||
HermesApiClient hermesApiClient,
|
HermesApiClient hermesApiClient,
|
||||||
SevenApiClient sevenApiClient,
|
SevenApiClient sevenApiClient,
|
||||||
|
TwitchApiClient twitchApiClient,
|
||||||
[FromKeyedServices("hermes")] SocketClient<WebSocketMessage> hermes,
|
[FromKeyedServices("hermes")] SocketClient<WebSocketMessage> hermes,
|
||||||
[FromKeyedServices("obs")] SocketClient<WebSocketMessage> obs,
|
[FromKeyedServices("obs")] SocketClient<WebSocketMessage> obs,
|
||||||
[FromKeyedServices("7tv")] SocketClient<WebSocketMessage> seven,
|
[FromKeyedServices("7tv")] SocketClient<WebSocketMessage> seven,
|
||||||
[FromKeyedServices("twitch")] SocketClient<TwitchWebsocketMessage> twitch,
|
[FromKeyedServices("twitch")] SocketClient<TwitchWebsocketMessage> twitch,
|
||||||
|
ICommandFactory commandFactory,
|
||||||
|
ICommandManager commandManager,
|
||||||
IEmoteDatabase emotes,
|
IEmoteDatabase emotes,
|
||||||
Configuration configuration,
|
|
||||||
TTSPlayer player,
|
TTSPlayer player,
|
||||||
AudioPlaybackEngine playback,
|
AudioPlaybackEngine playback,
|
||||||
IServiceProvider serviceProvider,
|
Configuration configuration,
|
||||||
ILogger logger
|
ILogger logger
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_user = user;
|
_user = user;
|
||||||
_hermesApiClient = hermesApiClient;
|
_hermesApiClient = hermesApiClient;
|
||||||
_sevenApiClient = sevenApiClient;
|
_sevenApiClient = sevenApiClient;
|
||||||
|
_twitchApiClient = twitchApiClient;
|
||||||
_hermes = (hermes as HermesSocketClient)!;
|
_hermes = (hermes as HermesSocketClient)!;
|
||||||
_obs = (obs as OBSSocketClient)!;
|
_obs = (obs as OBSSocketClient)!;
|
||||||
_seven = (seven as SevenSocketClient)!;
|
_seven = (seven as SevenSocketClient)!;
|
||||||
_twitch = (twitch as TwitchWebsocketClient)!;
|
_twitch = (twitch as TwitchWebsocketClient)!;
|
||||||
|
_commandFactory = commandFactory;
|
||||||
|
_commandManager = commandManager;
|
||||||
_emotes = emotes;
|
_emotes = emotes;
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
_player = player;
|
_player = player;
|
||||||
_playback = playback;
|
_playback = playback;
|
||||||
_serviceProvider = serviceProvider;
|
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,6 +98,7 @@ namespace TwitchChatTTS
|
|||||||
await Task.Delay(15 * 1000);
|
await Task.Delay(15 * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await _twitch.Connect();
|
||||||
await InitializeHermesWebsocket();
|
await InitializeHermesWebsocket();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -98,29 +106,33 @@ namespace TwitchChatTTS
|
|||||||
_user.HermesUserId = hermesAccount.Id;
|
_user.HermesUserId = hermesAccount.Id;
|
||||||
_user.HermesUsername = hermesAccount.Username;
|
_user.HermesUsername = hermesAccount.Username;
|
||||||
_user.TwitchUsername = 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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.Error(ex, "Failed to initialize properly. Restart app please.");
|
_logger.Error(ex, "Failed to initialize properly. Restart app please.");
|
||||||
await Task.Delay(30 * 1000);
|
await Task.Delay(TimeSpan.FromSeconds(30));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _hermesApiClient.AuthorizeTwitch();
|
|
||||||
var twitchBotToken = await _hermesApiClient.FetchTwitchBotToken();
|
|
||||||
_user.TwitchUserId = long.Parse(twitchBotToken.BroadcasterId!);
|
|
||||||
_logger.Information($"Username: {_user.TwitchUsername} [id: {_user.TwitchUserId}]");
|
|
||||||
|
|
||||||
var twitchapiclient2 = _serviceProvider.GetRequiredService<TwitchApiClient>();
|
|
||||||
twitchapiclient2.Initialize(twitchBotToken);
|
|
||||||
|
|
||||||
_twitch.Initialize();
|
|
||||||
await _twitch.Connect();
|
|
||||||
|
|
||||||
var emoteSet = await _sevenApiClient.FetchChannelEmoteSet(_user.TwitchUserId.ToString());
|
var emoteSet = await _sevenApiClient.FetchChannelEmoteSet(_user.TwitchUserId.ToString());
|
||||||
if (emoteSet != null)
|
if (emoteSet != null)
|
||||||
_user.SevenEmoteSetId = emoteSet.Id;
|
_user.SevenEmoteSetId = emoteSet.Id;
|
||||||
|
|
||||||
|
_commandManager.Update(_commandFactory);
|
||||||
|
|
||||||
await InitializeEmotes(_sevenApiClient, emoteSet);
|
await InitializeEmotes(_sevenApiClient, emoteSet);
|
||||||
await InitializeSevenTv();
|
await InitializeSevenTv();
|
||||||
await InitializeObs();
|
await InitializeObs();
|
||||||
@ -265,43 +277,6 @@ namespace TwitchChatTTS
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// private async Task<TwitchApiClient?> InitializeTwitchApiClient(string username)
|
|
||||||
// {
|
|
||||||
// _logger.Debug("Initializing twitch client.");
|
|
||||||
|
|
||||||
// var hermesapiclient = _serviceProvider.GetRequiredService<HermesApiClient>();
|
|
||||||
// if (!await hermesapiclient.AuthorizeTwitch())
|
|
||||||
// {
|
|
||||||
// _logger.Error("Cannot connect to Twitch API.");
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var twitchapiclient = _serviceProvider.GetRequiredService<TwitchApiClient>();
|
|
||||||
// var channels = _configuration.Twitch?.Channels ?? [username];
|
|
||||||
// _logger.Information("Twitch channels: " + string.Join(", ", channels));
|
|
||||||
// twitchapiclient.InitializeClient(username, channels);
|
|
||||||
// twitchapiclient.InitializePublisher();
|
|
||||||
|
|
||||||
// var handler = _serviceProvider.GetRequiredService<ChatMessageHandler>();
|
|
||||||
// twitchapiclient.AddOnNewMessageReceived(async (object? s, OnMessageReceivedArgs e) =>
|
|
||||||
// {
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// var result = await handler.Handle(e);
|
|
||||||
// if (result.Status != MessageStatus.None || result.Emotes == null || !result.Emotes.Any())
|
|
||||||
// return;
|
|
||||||
|
|
||||||
// await _hermes.SendEmoteUsage(e.ChatMessage.Id, result.ChatterId, result.Emotes);
|
|
||||||
// }
|
|
||||||
// catch (Exception ex)
|
|
||||||
// {
|
|
||||||
// _logger.Error(ex, "Unable to either execute a command or to send emote usage message.");
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
// return twitchapiclient;
|
|
||||||
// }
|
|
||||||
|
|
||||||
private async Task InitializeEmotes(SevenApiClient sevenapi, EmoteSet? channelEmotes)
|
private async Task InitializeEmotes(SevenApiClient sevenapi, EmoteSet? channelEmotes)
|
||||||
{
|
{
|
||||||
var globalEmotes = await sevenapi.FetchGlobalSevenEmotes();
|
var globalEmotes = await sevenapi.FetchGlobalSevenEmotes();
|
||||||
|
11
Twitch/Redemptions/IRedemptionManager.cs
Normal file
11
Twitch/Redemptions/IRedemptionManager.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
using HermesSocketLibrary.Requests.Messages;
|
||||||
|
|
||||||
|
namespace TwitchChatTTS.Twitch.Redemptions
|
||||||
|
{
|
||||||
|
public interface IRedemptionManager
|
||||||
|
{
|
||||||
|
Task Execute(RedeemableAction action, string senderDisplayName, long senderId);
|
||||||
|
IList<RedeemableAction> Get(string twitchRedemptionId);
|
||||||
|
void Initialize(IEnumerable<Redemption> redemptions, IDictionary<string, RedeemableAction> actions);
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,7 @@ using TwitchChatTTS.OBS.Socket.Data;
|
|||||||
|
|
||||||
namespace TwitchChatTTS.Twitch.Redemptions
|
namespace TwitchChatTTS.Twitch.Redemptions
|
||||||
{
|
{
|
||||||
public class RedemptionManager
|
public class RedemptionManager : IRedemptionManager
|
||||||
{
|
{
|
||||||
private readonly IDictionary<string, IList<RedeemableAction>> _store;
|
private readonly IDictionary<string, IList<RedeemableAction>> _store;
|
||||||
private readonly User _user;
|
private readonly User _user;
|
||||||
|
57
Twitch/Socket/Handlers/ChannelAdBreakHandler.cs
Normal file
57
Twitch/Socket/Handlers/ChannelAdBreakHandler.cs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
using Serilog;
|
||||||
|
using TwitchChatTTS.Twitch.Redemptions;
|
||||||
|
using TwitchChatTTS.Twitch.Socket.Messages;
|
||||||
|
|
||||||
|
namespace TwitchChatTTS.Twitch.Socket.Handlers
|
||||||
|
{
|
||||||
|
public class ChannelAdBreakHandler : ITwitchSocketHandler
|
||||||
|
{
|
||||||
|
public string Name => "channel.ad_break.begin";
|
||||||
|
|
||||||
|
private readonly IRedemptionManager _redemptionManager;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
public ChannelAdBreakHandler(IRedemptionManager redemptionManager, ILogger logger)
|
||||||
|
{
|
||||||
|
_redemptionManager = redemptionManager;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Execute(TwitchWebsocketClient sender, object data)
|
||||||
|
{
|
||||||
|
if (data is not ChannelAdBreakMessage message)
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool isAutomatic = message.IsAutomatic == "true";
|
||||||
|
if (isAutomatic)
|
||||||
|
_logger.Information($"Ad break has begun [duration: {message.DurationSeconds} seconds][automatic: {isAutomatic}]");
|
||||||
|
else
|
||||||
|
_logger.Information($"Ad break has begun [duration: {message.DurationSeconds} seconds][requester: {message.RequesterUserLogin}][requester id: {message.RequesterUserId}]");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var actions = _redemptionManager.Get("adbreak");
|
||||||
|
if (!actions.Any())
|
||||||
|
{
|
||||||
|
_logger.Debug($"No redemable actions for ad break was found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_logger.Debug($"Found {actions.Count} actions for this Twitch ad break");
|
||||||
|
|
||||||
|
foreach (var action in actions)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _redemptionManager.Execute(action, message.RequesterUserLogin, long.Parse(message.RequesterUserId));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, $"Failed to execute redeeemable action [action: {action.Name}][action type: {action.Type}][redeem: ad break]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, $"Failed to fetch the redeemable actions for ad break");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,7 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Execute(TwitchWebsocketClient sender, object? data)
|
public Task Execute(TwitchWebsocketClient sender, object data)
|
||||||
{
|
{
|
||||||
if (data is not ChannelBanMessage message)
|
if (data is not ChannelBanMessage message)
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
@ -18,7 +18,7 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Execute(TwitchWebsocketClient sender, object? data)
|
public Task Execute(TwitchWebsocketClient sender, object data)
|
||||||
{
|
{
|
||||||
if (data is not ChannelChatClearMessage message)
|
if (data is not ChannelChatClearMessage message)
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
@ -18,14 +18,16 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Execute(TwitchWebsocketClient sender, object? data)
|
public Task Execute(TwitchWebsocketClient sender, object data)
|
||||||
{
|
{
|
||||||
if (data is not ChannelChatClearUserMessage message)
|
if (data is not ChannelChatClearUserMessage message)
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
|
||||||
|
long broadcasterId = long.Parse(message.BroadcasterUserId);
|
||||||
long chatterId = long.Parse(message.TargetUserId);
|
long chatterId = long.Parse(message.TargetUserId);
|
||||||
_player.RemoveAll(chatterId);
|
_player.RemoveAll(broadcasterId, chatterId);
|
||||||
if (_player.Playing?.ChatterId == chatterId) {
|
if (_player.Playing != null && _player.Playing.RoomId == broadcasterId && _player.Playing.ChatterId == chatterId)
|
||||||
|
{
|
||||||
_playback.RemoveMixerInput(_player.Playing.Audio!);
|
_playback.RemoveMixerInput(_player.Playing.Audio!);
|
||||||
_player.Playing = null;
|
_player.Playing = null;
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Execute(TwitchWebsocketClient sender, object? data)
|
public Task Execute(TwitchWebsocketClient sender, object data)
|
||||||
{
|
{
|
||||||
if (data is not ChannelChatDeleteMessage message)
|
if (data is not ChannelChatDeleteMessage message)
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
@ -19,7 +19,7 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
|
|||||||
|
|
||||||
private readonly User _user;
|
private readonly User _user;
|
||||||
private readonly TTSPlayer _player;
|
private readonly TTSPlayer _player;
|
||||||
private readonly CommandManager _commands;
|
private readonly ICommandManager _commands;
|
||||||
private readonly IGroupPermissionManager _permissionManager;
|
private readonly IGroupPermissionManager _permissionManager;
|
||||||
private readonly IChatterGroupManager _chatterGroupManager;
|
private readonly IChatterGroupManager _chatterGroupManager;
|
||||||
private readonly IEmoteDatabase _emotes;
|
private readonly IEmoteDatabase _emotes;
|
||||||
@ -34,7 +34,7 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
|
|||||||
public ChannelChatMessageHandler(
|
public ChannelChatMessageHandler(
|
||||||
User user,
|
User user,
|
||||||
TTSPlayer player,
|
TTSPlayer player,
|
||||||
CommandManager commands,
|
ICommandManager commands,
|
||||||
IGroupPermissionManager permissionManager,
|
IGroupPermissionManager permissionManager,
|
||||||
IChatterGroupManager chatterGroupManager,
|
IChatterGroupManager chatterGroupManager,
|
||||||
IEmoteDatabase emotes,
|
IEmoteDatabase emotes,
|
||||||
@ -59,15 +59,10 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Execute(TwitchWebsocketClient sender, object? data)
|
public async Task Execute(TwitchWebsocketClient sender, object data)
|
||||||
{
|
{
|
||||||
if (sender == null)
|
if (sender == null)
|
||||||
return;
|
return;
|
||||||
if (data == null)
|
|
||||||
{
|
|
||||||
_logger.Warning("Twitch websocket message data is null.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (data is not ChannelChatMessage message)
|
if (data is not ChannelChatMessage message)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -231,6 +226,7 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
|
|||||||
|
|
||||||
var parts = _sfxRegex.Split(message);
|
var parts = _sfxRegex.Split(message);
|
||||||
var chatterId = long.Parse(e.ChatterUserId);
|
var chatterId = long.Parse(e.ChatterUserId);
|
||||||
|
var broadcasterId = long.Parse(e.BroadcasterUserId);
|
||||||
var badgesString = string.Join(", ", e.Badges.Select(b => b.SetId + '|' + b.Id + '=' + b.Info));
|
var badgesString = string.Join(", ", e.Badges.Select(b => b.SetId + '|' + b.Id + '=' + b.Info));
|
||||||
|
|
||||||
if (parts.Length == 1)
|
if (parts.Length == 1)
|
||||||
@ -241,6 +237,7 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
|
|||||||
Voice = voice,
|
Voice = voice,
|
||||||
Message = message,
|
Message = message,
|
||||||
Timestamp = DateTime.UtcNow,
|
Timestamp = DateTime.UtcNow,
|
||||||
|
RoomId = broadcasterId,
|
||||||
ChatterId = chatterId,
|
ChatterId = chatterId,
|
||||||
MessageId = e.MessageId,
|
MessageId = e.MessageId,
|
||||||
Badges = e.Badges,
|
Badges = e.Badges,
|
||||||
@ -271,6 +268,7 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
|
|||||||
Voice = voice,
|
Voice = voice,
|
||||||
Message = parts[i * 2],
|
Message = parts[i * 2],
|
||||||
Timestamp = DateTime.UtcNow,
|
Timestamp = DateTime.UtcNow,
|
||||||
|
RoomId = broadcasterId,
|
||||||
ChatterId = chatterId,
|
ChatterId = chatterId,
|
||||||
MessageId = e.MessageId,
|
MessageId = e.MessageId,
|
||||||
Badges = e.Badges,
|
Badges = e.Badges,
|
||||||
@ -284,6 +282,7 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
|
|||||||
Voice = voice,
|
Voice = voice,
|
||||||
File = $"sfx/{sfxName}.mp3",
|
File = $"sfx/{sfxName}.mp3",
|
||||||
Timestamp = DateTime.UtcNow,
|
Timestamp = DateTime.UtcNow,
|
||||||
|
RoomId = broadcasterId,
|
||||||
ChatterId = chatterId,
|
ChatterId = chatterId,
|
||||||
MessageId = e.MessageId,
|
MessageId = e.MessageId,
|
||||||
Badges = e.Badges,
|
Badges = e.Badges,
|
||||||
@ -299,6 +298,7 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
|
|||||||
Voice = voice,
|
Voice = voice,
|
||||||
Message = parts.Last(),
|
Message = parts.Last(),
|
||||||
Timestamp = DateTime.UtcNow,
|
Timestamp = DateTime.UtcNow,
|
||||||
|
RoomId = broadcasterId,
|
||||||
ChatterId = chatterId,
|
ChatterId = chatterId,
|
||||||
MessageId = e.MessageId,
|
MessageId = e.MessageId,
|
||||||
Badges = e.Badges,
|
Badges = e.Badges,
|
||||||
|
@ -8,11 +8,11 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
|
|||||||
{
|
{
|
||||||
public string Name => "channel.channel_points_custom_reward_redemption.add";
|
public string Name => "channel.channel_points_custom_reward_redemption.add";
|
||||||
|
|
||||||
private readonly RedemptionManager _redemptionManager;
|
private readonly IRedemptionManager _redemptionManager;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public ChannelCustomRedemptionHandler(
|
public ChannelCustomRedemptionHandler(
|
||||||
RedemptionManager redemptionManager,
|
IRedemptionManager redemptionManager,
|
||||||
ILogger logger
|
ILogger logger
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
@ -20,7 +20,7 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Execute(TwitchWebsocketClient sender, object? data)
|
public async Task Execute(TwitchWebsocketClient sender, object data)
|
||||||
{
|
{
|
||||||
if (data is not ChannelCustomRedemptionMessage message)
|
if (data is not ChannelCustomRedemptionMessage message)
|
||||||
return;
|
return;
|
||||||
|
52
Twitch/Socket/Handlers/ChannelFollowHandler.cs
Normal file
52
Twitch/Socket/Handlers/ChannelFollowHandler.cs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
using Serilog;
|
||||||
|
using TwitchChatTTS.Twitch.Redemptions;
|
||||||
|
using TwitchChatTTS.Twitch.Socket.Messages;
|
||||||
|
|
||||||
|
namespace TwitchChatTTS.Twitch.Socket.Handlers
|
||||||
|
{
|
||||||
|
public class ChannelFollowHandler : ITwitchSocketHandler
|
||||||
|
{
|
||||||
|
public string Name => "channel.follow";
|
||||||
|
|
||||||
|
private readonly IRedemptionManager _redemptionManager;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
public ChannelFollowHandler(IRedemptionManager redemptionManager, ILogger logger)
|
||||||
|
{
|
||||||
|
_redemptionManager = redemptionManager;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Execute(TwitchWebsocketClient sender, object data)
|
||||||
|
{
|
||||||
|
if (data is not ChannelFollowMessage message)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_logger.Information($"User followed [chatter: {message.UserLogin}][chatter id: {message.UserId}]");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var actions = _redemptionManager.Get("follow");
|
||||||
|
if (!actions.Any())
|
||||||
|
{
|
||||||
|
_logger.Debug($"No redemable actions for follow was found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_logger.Debug($"Found {actions.Count} actions for this Twitch follow");
|
||||||
|
|
||||||
|
foreach (var action in actions)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _redemptionManager.Execute(action, message.UserName, long.Parse(message.UserId));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, $"Failed to execute redeeemable action [action: {action.Name}][action type: {action.Type}][redeem: follow]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, $"Failed to fetch the redeemable actions for follow");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
52
Twitch/Socket/Handlers/ChannelResubscriptionHandler.cs
Normal file
52
Twitch/Socket/Handlers/ChannelResubscriptionHandler.cs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
using Serilog;
|
||||||
|
using TwitchChatTTS.Twitch.Redemptions;
|
||||||
|
using TwitchChatTTS.Twitch.Socket.Messages;
|
||||||
|
|
||||||
|
namespace TwitchChatTTS.Twitch.Socket.Handlers
|
||||||
|
{
|
||||||
|
public class ChannelResubscriptionHandler : ITwitchSocketHandler
|
||||||
|
{
|
||||||
|
public string Name => "channel.subscription.message";
|
||||||
|
|
||||||
|
private readonly IRedemptionManager _redemptionManager;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
public ChannelResubscriptionHandler(IRedemptionManager redemptionManager, ILogger logger)
|
||||||
|
{
|
||||||
|
_redemptionManager = redemptionManager;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Execute(TwitchWebsocketClient sender, object data)
|
||||||
|
{
|
||||||
|
if (data is not ChannelResubscriptionMessage message)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_logger.Debug("Resubscription occured.");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var actions = _redemptionManager.Get("subscription");
|
||||||
|
if (!actions.Any())
|
||||||
|
{
|
||||||
|
_logger.Debug($"No redemable actions for this subscription was found [message: {message.Message.Text}]");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_logger.Debug($"Found {actions.Count} actions for this Twitch subscription [message: {message.Message.Text}]");
|
||||||
|
|
||||||
|
foreach (var action in actions)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _redemptionManager.Execute(action, message.UserName, long.Parse(message.UserId));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, $"Failed to execute redeeemable action [action: {action.Name}][action type: {action.Type}][redeem: subscription][message: {message.Message.Text}]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, $"Failed to fetch the redeemable actions for subscription [message: {message.Message.Text}]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
52
Twitch/Socket/Handlers/ChannelSubscriptionGiftHandler.cs
Normal file
52
Twitch/Socket/Handlers/ChannelSubscriptionGiftHandler.cs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
using Serilog;
|
||||||
|
using TwitchChatTTS.Twitch.Redemptions;
|
||||||
|
using TwitchChatTTS.Twitch.Socket.Messages;
|
||||||
|
|
||||||
|
namespace TwitchChatTTS.Twitch.Socket.Handlers
|
||||||
|
{
|
||||||
|
public class ChannelSubscriptionGiftHandler : ITwitchSocketHandler
|
||||||
|
{
|
||||||
|
public string Name => "channel.subscription.gift";
|
||||||
|
|
||||||
|
private readonly IRedemptionManager _redemptionManager;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
public ChannelSubscriptionGiftHandler(IRedemptionManager redemptionManager, ILogger logger)
|
||||||
|
{
|
||||||
|
_redemptionManager = redemptionManager;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Execute(TwitchWebsocketClient sender, object data)
|
||||||
|
{
|
||||||
|
if (data is not ChannelSubscriptionGiftMessage message)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_logger.Debug("Gifted subscription occured.");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var actions = _redemptionManager.Get("subscription.gift");
|
||||||
|
if (!actions.Any())
|
||||||
|
{
|
||||||
|
_logger.Debug($"No redemable actions for this gifted subscription was found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_logger.Debug($"Found {actions.Count} actions for this Twitch gifted subscription [gifted: {message.UserLogin}][gifted id: {message.UserId}][Anonymous: {message.IsAnonymous}][cumulative: {message.CumulativeTotal ?? -1}]");
|
||||||
|
|
||||||
|
foreach (var action in actions)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _redemptionManager.Execute(action, message.UserName, long.Parse(message.UserId));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, $"Failed to execute redeeemable action [action: {action.Name}][action type: {action.Type}][redeem: gifted subscription]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, $"Failed to fetch the redeemable actions for gifted subscription");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,33 +1,54 @@
|
|||||||
using Serilog;
|
using Serilog;
|
||||||
|
using TwitchChatTTS.Twitch.Redemptions;
|
||||||
using TwitchChatTTS.Twitch.Socket.Messages;
|
using TwitchChatTTS.Twitch.Socket.Messages;
|
||||||
|
|
||||||
namespace TwitchChatTTS.Twitch.Socket.Handlers
|
namespace TwitchChatTTS.Twitch.Socket.Handlers
|
||||||
{
|
{
|
||||||
public class ChannelSubscriptionHandler : ITwitchSocketHandler
|
public class ChannelSubscriptionHandler : ITwitchSocketHandler
|
||||||
{
|
{
|
||||||
public string Name => "channel.subscription.message";
|
public string Name => "channel.subscription";
|
||||||
|
|
||||||
private readonly TTSPlayer _player;
|
private readonly IRedemptionManager _redemptionManager;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public ChannelSubscriptionHandler(TTSPlayer player, ILogger logger) {
|
public ChannelSubscriptionHandler(IRedemptionManager redemptionManager, ILogger logger)
|
||||||
_player = player;
|
{
|
||||||
|
_redemptionManager = redemptionManager;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Execute(TwitchWebsocketClient sender, object? data)
|
public async Task Execute(TwitchWebsocketClient sender, object data)
|
||||||
{
|
{
|
||||||
if (sender == null)
|
|
||||||
return;
|
|
||||||
if (data == null)
|
|
||||||
{
|
|
||||||
_logger.Warning("Twitch websocket message data is null.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (data is not ChannelSubscriptionMessage message)
|
if (data is not ChannelSubscriptionMessage message)
|
||||||
return;
|
return;
|
||||||
|
if (message.IsGifted)
|
||||||
|
return;
|
||||||
|
|
||||||
_logger.Debug("Subscription occured.");
|
_logger.Debug("Subscription occured.");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var actions = _redemptionManager.Get("subscription");
|
||||||
|
if (!actions.Any())
|
||||||
|
{
|
||||||
|
_logger.Debug($"No redemable actions for this subscription was found [subscriber: {message.UserLogin}][subscriber id: {message.UserId}]");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_logger.Debug($"Found {actions.Count} actions for this Twitch subscription [subscriber: {message.UserLogin}][subscriber id: {message.UserId}]");
|
||||||
|
|
||||||
|
foreach (var action in actions)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _redemptionManager.Execute(action, message.UserName, long.Parse(message.UserId));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, $"Failed to execute redeeemable action [action: {action.Name}][action type: {action.Type}][redeem: subscription][subscriber: {message.UserLogin}][subscriber id: {message.UserId}]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, $"Failed to fetch the redeemable actions for subscription [subscriber: {message.UserLogin}][subscriber id: {message.UserId}]");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,6 +3,6 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
|
|||||||
public interface ITwitchSocketHandler
|
public interface ITwitchSocketHandler
|
||||||
{
|
{
|
||||||
string Name { get; }
|
string Name { get; }
|
||||||
Task Execute(TwitchWebsocketClient sender, object? data);
|
Task Execute(TwitchWebsocketClient sender, object data);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -23,30 +23,30 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
|
|||||||
_handlers = handlers.ToDictionary(h => h.Name, h => h);
|
_handlers = handlers.ToDictionary(h => h.Name, h => h);
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
|
||||||
_options = new JsonSerializerOptions() {
|
_options = new JsonSerializerOptions()
|
||||||
|
{
|
||||||
PropertyNameCaseInsensitive = false,
|
PropertyNameCaseInsensitive = false,
|
||||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
|
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
|
||||||
};
|
};
|
||||||
|
|
||||||
_messageTypes = new Dictionary<string, Type>();
|
_messageTypes = new Dictionary<string, Type>();
|
||||||
|
_messageTypes.Add("channel.adbreak.begin", typeof(ChannelAdBreakMessage));
|
||||||
_messageTypes.Add("channel.ban", typeof(ChannelBanMessage));
|
_messageTypes.Add("channel.ban", typeof(ChannelBanMessage));
|
||||||
_messageTypes.Add("channel.chat.message", typeof(ChannelChatMessage));
|
_messageTypes.Add("channel.chat.message", typeof(ChannelChatMessage));
|
||||||
_messageTypes.Add("channel.chat.clear_user_messages", typeof(ChannelChatClearUserMessage));
|
_messageTypes.Add("channel.chat.clear_user_messages", typeof(ChannelChatClearUserMessage));
|
||||||
_messageTypes.Add("channel.chat.clear", typeof(ChannelChatClearMessage));
|
_messageTypes.Add("channel.chat.clear", typeof(ChannelChatClearMessage));
|
||||||
_messageTypes.Add("channel.chat.message_delete", typeof(ChannelChatDeleteMessage));
|
_messageTypes.Add("channel.chat.message_delete", typeof(ChannelChatDeleteMessage));
|
||||||
_messageTypes.Add("channel.channel_points_custom_reward_redemption.add", typeof(ChannelCustomRedemptionMessage));
|
_messageTypes.Add("channel.channel_points_custom_reward_redemption.add", typeof(ChannelCustomRedemptionMessage));
|
||||||
|
_messageTypes.Add("channel.follow", typeof(ChannelFollowMessage));
|
||||||
|
_messageTypes.Add("channel.resubscription", typeof(ChannelResubscriptionMessage));
|
||||||
_messageTypes.Add("channel.subscription.message", typeof(ChannelSubscriptionMessage));
|
_messageTypes.Add("channel.subscription.message", typeof(ChannelSubscriptionMessage));
|
||||||
|
_messageTypes.Add("channel.subscription.gift", typeof(ChannelSubscriptionGiftMessage));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Execute(TwitchWebsocketClient sender, object? data)
|
public async Task Execute(TwitchWebsocketClient sender, object data)
|
||||||
{
|
{
|
||||||
if (sender == null)
|
if (sender == null)
|
||||||
return;
|
return;
|
||||||
if (data == null)
|
|
||||||
{
|
|
||||||
_logger.Warning("Twitch websocket message data is null.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (data is not NotificationMessage message)
|
if (data is not NotificationMessage message)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
12
Twitch/Socket/Handlers/SessionKeepAliveHandler.cs
Normal file
12
Twitch/Socket/Handlers/SessionKeepAliveHandler.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
namespace TwitchChatTTS.Twitch.Socket.Handlers
|
||||||
|
{
|
||||||
|
public class SessionKeepAliveHandler : ITwitchSocketHandler
|
||||||
|
{
|
||||||
|
public string Name => "session_keepalive";
|
||||||
|
|
||||||
|
public Task Execute(TwitchWebsocketClient sender, object data)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,40 +8,45 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
|
|||||||
{
|
{
|
||||||
public string Name => "session_reconnect";
|
public string Name => "session_reconnect";
|
||||||
|
|
||||||
private readonly TwitchApiClient _api;
|
private readonly ITwitchConnectionManager _manager;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public SessionReconnectHandler(TwitchApiClient api, ILogger logger)
|
public SessionReconnectHandler(ITwitchConnectionManager manager, ILogger logger)
|
||||||
{
|
{
|
||||||
_api = api;
|
_manager = manager;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Execute(TwitchWebsocketClient sender, object? data)
|
public async Task Execute(TwitchWebsocketClient sender, object data)
|
||||||
{
|
{
|
||||||
if (sender == null)
|
if (sender == null)
|
||||||
return;
|
return;
|
||||||
if (data == null)
|
|
||||||
{
|
|
||||||
_logger.Warning("Twitch websocket message data is null.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (data is not SessionWelcomeMessage message)
|
if (data is not SessionWelcomeMessage message)
|
||||||
return;
|
return;
|
||||||
if (_api == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(message.Session.Id))
|
if (string.IsNullOrEmpty(message.Session.Id))
|
||||||
{
|
{
|
||||||
_logger.Warning($"No session info provided by Twitch [status: {message.Session.Status}]");
|
_logger.Warning($"No session id provided by Twitch [status: {message.Session.Status}]");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Be able to handle multiple websocket connections.
|
if (message.Session.ReconnectUrl == null)
|
||||||
sender.URL = message.Session.ReconnectUrl;
|
{
|
||||||
await Task.Delay(TimeSpan.FromSeconds(29));
|
_logger.Warning($"No reconnection info provided by Twitch [status: {message.Session.Status}]");
|
||||||
await sender.DisconnectAsync(new SocketDisconnectionEventArgs("Close", "Twitch asking to reconnect."));
|
return;
|
||||||
await sender.Connect();
|
}
|
||||||
|
|
||||||
|
sender.ReceivedReconnecting = true;
|
||||||
|
|
||||||
|
var backup = _manager.GetBackupClient();
|
||||||
|
var identified = _manager.GetWorkingClient();
|
||||||
|
if (identified != null && backup != identified)
|
||||||
|
{
|
||||||
|
await identified.DisconnectAsync(new SocketDisconnectionEventArgs("Closed", "Reconnection from another client."));
|
||||||
|
}
|
||||||
|
|
||||||
|
backup.URL = message.Session.ReconnectUrl;
|
||||||
|
await backup.Connect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,3 @@
|
|||||||
using CommonSocketLibrary.Abstract;
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using TwitchChatTTS.Twitch.Socket.Messages;
|
using TwitchChatTTS.Twitch.Socket.Messages;
|
||||||
|
|
||||||
@ -8,26 +7,23 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
|
|||||||
{
|
{
|
||||||
public string Name => "session_welcome";
|
public string Name => "session_welcome";
|
||||||
|
|
||||||
|
private readonly HermesApiClient _hermes;
|
||||||
private readonly TwitchApiClient _api;
|
private readonly TwitchApiClient _api;
|
||||||
private readonly User _user;
|
private readonly User _user;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public SessionWelcomeHandler(TwitchApiClient api, User user, ILogger logger)
|
public SessionWelcomeHandler(HermesApiClient hermes, TwitchApiClient api, User user, ILogger logger)
|
||||||
{
|
{
|
||||||
|
_hermes = hermes;
|
||||||
_api = api;
|
_api = api;
|
||||||
_user = user;
|
_user = user;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Execute(TwitchWebsocketClient sender, object? data)
|
public async Task Execute(TwitchWebsocketClient sender, object data)
|
||||||
{
|
{
|
||||||
if (sender == null)
|
if (sender == null)
|
||||||
return;
|
return;
|
||||||
if (data == null)
|
|
||||||
{
|
|
||||||
_logger.Warning("Twitch websocket message data is null.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (data is not SessionWelcomeMessage message)
|
if (data is not SessionWelcomeMessage message)
|
||||||
return;
|
return;
|
||||||
if (_api == null)
|
if (_api == null)
|
||||||
@ -39,6 +35,11 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await _hermes.AuthorizeTwitch();
|
||||||
|
var token = await _hermes.FetchTwitchBotToken();
|
||||||
|
_api.Initialize(token);
|
||||||
|
|
||||||
|
string broadcasterId = _user.TwitchUserId.ToString();
|
||||||
string[] subscriptionsv1 = [
|
string[] subscriptionsv1 = [
|
||||||
"channel.chat.message",
|
"channel.chat.message",
|
||||||
"channel.chat.message_delete",
|
"channel.chat.message_delete",
|
||||||
@ -53,17 +54,36 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
|
|||||||
string[] subscriptionsv2 = [
|
string[] subscriptionsv2 = [
|
||||||
"channel.follow",
|
"channel.follow",
|
||||||
];
|
];
|
||||||
string broadcasterId = _user.TwitchUserId.ToString();
|
|
||||||
|
string? pagination = null;
|
||||||
|
int size = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
var subscriptionsData = await _api.GetSubscriptions(status: "enabled", broadcasterId: broadcasterId, after: pagination);
|
||||||
|
var subscriptionNames = subscriptionsData?.Data == null ? [] : subscriptionsData.Data.Select(s => s.Type).ToArray();
|
||||||
|
|
||||||
|
if (subscriptionNames.Length == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
foreach (var d in subscriptionsData!.Data!)
|
||||||
|
sender.AddSubscription(broadcasterId, d.Type, d.Id);
|
||||||
|
|
||||||
|
subscriptionsv1 = subscriptionsv1.Except(subscriptionNames).ToArray();
|
||||||
|
subscriptionsv2 = subscriptionsv2.Except(subscriptionNames).ToArray();
|
||||||
|
|
||||||
|
pagination = subscriptionsData?.Pagination?.Cursor;
|
||||||
|
size = subscriptionNames.Length;
|
||||||
|
} while (size >= 100 && pagination != null && subscriptionsv1.Length + subscriptionsv2.Length > 0);
|
||||||
|
|
||||||
foreach (var subscription in subscriptionsv1)
|
foreach (var subscription in subscriptionsv1)
|
||||||
await Subscribe(subscription, message.Session.Id, broadcasterId, "1");
|
await Subscribe(sender, subscription, message.Session.Id, broadcasterId, "1");
|
||||||
foreach (var subscription in subscriptionsv2)
|
foreach (var subscription in subscriptionsv2)
|
||||||
await Subscribe(subscription, message.Session.Id, broadcasterId, "2");
|
await Subscribe(sender, subscription, message.Session.Id, broadcasterId, "2");
|
||||||
|
|
||||||
sender.SessionId = message.Session.Id;
|
sender.Identify(message.Session.Id);
|
||||||
sender.Identified = sender.SessionId != null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Subscribe(string subscriptionName, string sessionId, string broadcasterId, string version)
|
private async Task Subscribe(TwitchWebsocketClient sender, string subscriptionName, string sessionId, string broadcasterId, string version)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -83,6 +103,10 @@ namespace TwitchChatTTS.Twitch.Socket.Handlers
|
|||||||
_logger.Error($"Failed to create an event subscription [subscription type: {subscriptionName}][reason: data is empty]");
|
_logger.Error($"Failed to create an event subscription [subscription type: {subscriptionName}][reason: data is empty]");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var d in response.Data)
|
||||||
|
sender.AddSubscription(broadcasterId, d.Type, d.Id);
|
||||||
|
|
||||||
_logger.Information($"Sucessfully added subscription to Twitch websockets [subscription type: {subscriptionName}]");
|
_logger.Information($"Sucessfully added subscription to Twitch websockets [subscription type: {subscriptionName}]");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
15
Twitch/Socket/Messages/ChannelAdBreakMessage.cs
Normal file
15
Twitch/Socket/Messages/ChannelAdBreakMessage.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
namespace TwitchChatTTS.Twitch.Socket.Messages
|
||||||
|
{
|
||||||
|
public class ChannelAdBreakMessage
|
||||||
|
{
|
||||||
|
public string DurationSeconds { get; set; }
|
||||||
|
public DateTime StartedAt { get; set; }
|
||||||
|
public string IsAutomatic { get; set; }
|
||||||
|
public string BroadcasterUserId { get; set; }
|
||||||
|
public string BroadcasterUserLogin { get; set; }
|
||||||
|
public string BroadcasterUserName { get; set; }
|
||||||
|
public string RequesterUserId { get; set; }
|
||||||
|
public string RequesterUserLogin { get; set; }
|
||||||
|
public string RequesterUserName { get; set; }
|
||||||
|
}
|
||||||
|
}
|
13
Twitch/Socket/Messages/ChannelFollowMessage.cs
Normal file
13
Twitch/Socket/Messages/ChannelFollowMessage.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
namespace TwitchChatTTS.Twitch.Socket.Messages
|
||||||
|
{
|
||||||
|
public class ChannelFollowMessage
|
||||||
|
{
|
||||||
|
public string BroadcasterUserId { get; set; }
|
||||||
|
public string BroadcasterUserLogin { get; set; }
|
||||||
|
public string BroadcasterUserName { get; set; }
|
||||||
|
public string UserId { get; set; }
|
||||||
|
public string UserLogin { get; set; }
|
||||||
|
public string UserName { get; set; }
|
||||||
|
public DateTime FollowedAt { get; set; }
|
||||||
|
}
|
||||||
|
}
|
10
Twitch/Socket/Messages/ChannelResubscriptionMessage.cs
Normal file
10
Twitch/Socket/Messages/ChannelResubscriptionMessage.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
namespace TwitchChatTTS.Twitch.Socket.Messages
|
||||||
|
{
|
||||||
|
public class ChannelResubscriptionMessage : ChannelSubscriptionData
|
||||||
|
{
|
||||||
|
public TwitchChatMessageInfo Message { get; set; }
|
||||||
|
public int CumulativeMonths { get; set; }
|
||||||
|
public int StreakMonths { get; set; }
|
||||||
|
public int DurationMonths { get; set; }
|
||||||
|
}
|
||||||
|
}
|
9
Twitch/Socket/Messages/ChannelSubscriptionGiftMessage.cs
Normal file
9
Twitch/Socket/Messages/ChannelSubscriptionGiftMessage.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace TwitchChatTTS.Twitch.Socket.Messages
|
||||||
|
{
|
||||||
|
public class ChannelSubscriptionGiftMessage : ChannelSubscriptionData
|
||||||
|
{
|
||||||
|
public int Total { get; set; }
|
||||||
|
public int? CumulativeTotal { get; set; }
|
||||||
|
public bool IsAnonymous { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -1,17 +1,18 @@
|
|||||||
namespace TwitchChatTTS.Twitch.Socket.Messages
|
namespace TwitchChatTTS.Twitch.Socket.Messages
|
||||||
{
|
{
|
||||||
public class ChannelSubscriptionMessage
|
public class ChannelSubscriptionData
|
||||||
{
|
{
|
||||||
|
public string UserId { get; set; }
|
||||||
|
public string UserLogin { get; set; }
|
||||||
|
public string UserName { get; set; }
|
||||||
public string BroadcasterUserId { get; set; }
|
public string BroadcasterUserId { get; set; }
|
||||||
public string BroadcasterUserLogin { get; set; }
|
public string BroadcasterUserLogin { get; set; }
|
||||||
public string BroadcasterUserName { get; set; }
|
public string BroadcasterUserName { get; set; }
|
||||||
public string ChatterUserId { get; set; }
|
|
||||||
public string ChatterUserLogin { get; set; }
|
|
||||||
public string ChatterUserName { get; set; }
|
|
||||||
public string Tier { get; set; }
|
public string Tier { get; set; }
|
||||||
public TwitchChatMessageInfo Message { get; set; }
|
}
|
||||||
public int CumulativeMonths { get; set; }
|
|
||||||
public int StreakMonths { get; set; }
|
public class ChannelSubscriptionMessage : ChannelSubscriptionData
|
||||||
public int DurationMonths { get; set; }
|
{
|
||||||
|
public bool IsGifted { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -6,5 +6,10 @@ namespace TwitchChatTTS.Twitch.Socket.Messages
|
|||||||
public int Total { get; set; }
|
public int Total { get; set; }
|
||||||
public int TotalCost { get; set; }
|
public int TotalCost { get; set; }
|
||||||
public int MaxTotalCost { get; set; }
|
public int MaxTotalCost { get; set; }
|
||||||
|
public EventResponsePagination? Pagination { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EventResponsePagination {
|
||||||
|
public string Cursor { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -11,7 +11,8 @@ namespace TwitchChatTTS.Twitch.Socket.Messages
|
|||||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
public int? Cost { get; set; }
|
public int? Cost { get; set; }
|
||||||
|
|
||||||
public EventSubscriptionMessage() {
|
public EventSubscriptionMessage()
|
||||||
|
{
|
||||||
Type = string.Empty;
|
Type = string.Empty;
|
||||||
Version = string.Empty;
|
Version = string.Empty;
|
||||||
Condition = new Dictionary<string, string>();
|
Condition = new Dictionary<string, string>();
|
||||||
@ -45,7 +46,8 @@ namespace TwitchChatTTS.Twitch.Socket.Messages
|
|||||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
public string? SessionId { get; }
|
public string? SessionId { get; }
|
||||||
|
|
||||||
public EventSubTransport() {
|
public EventSubTransport()
|
||||||
|
{
|
||||||
Method = string.Empty;
|
Method = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,6 @@ namespace TwitchChatTTS.Twitch.Socket.Messages
|
|||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
public string Status { get; set; }
|
public string Status { get; set; }
|
||||||
public DateTime CreatedAt { get; set; }
|
public DateTime CreatedAt { get; set; }
|
||||||
public object Event { get; set; }
|
public object? Event { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -8,7 +8,7 @@ namespace TwitchChatTTS.Twitch.Socket.Messages
|
|||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
public string Status { get; set; }
|
public string Status { get; set; }
|
||||||
public DateTime ConnectedAt { get; set; }
|
public DateTime ConnectedAt { get; set; }
|
||||||
public int KeepaliveTimeoutSeconds { get; set; }
|
public int? KeepaliveTimeoutSeconds { get; set; }
|
||||||
public string? ReconnectUrl { get; set; }
|
public string? ReconnectUrl { get; set; }
|
||||||
public string? RecoveryUrl { get; set; }
|
public string? RecoveryUrl { get; set; }
|
||||||
}
|
}
|
||||||
|
119
Twitch/Socket/TwitchConnectionManager.cs
Normal file
119
Twitch/Socket/TwitchConnectionManager.cs
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
using CommonSocketLibrary.Abstract;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Serilog;
|
||||||
|
using TwitchChatTTS.Twitch.Socket.Messages;
|
||||||
|
|
||||||
|
namespace TwitchChatTTS.Twitch.Socket
|
||||||
|
{
|
||||||
|
public interface ITwitchConnectionManager
|
||||||
|
{
|
||||||
|
TwitchWebsocketClient GetWorkingClient();
|
||||||
|
TwitchWebsocketClient GetBackupClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TwitchConnectionManager : ITwitchConnectionManager
|
||||||
|
{
|
||||||
|
private TwitchWebsocketClient? _identified;
|
||||||
|
private TwitchWebsocketClient? _backup;
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
private readonly object _lock;
|
||||||
|
|
||||||
|
public TwitchConnectionManager(IServiceProvider serviceProvider, ILogger logger)
|
||||||
|
{
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
_logger = logger;
|
||||||
|
|
||||||
|
_lock = new object();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public TwitchWebsocketClient GetBackupClient()
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (_identified == null)
|
||||||
|
throw new InvalidOperationException("Cannot get backup Twitch client yet. Waiting for identification.");
|
||||||
|
if (_backup != null)
|
||||||
|
return _backup;
|
||||||
|
|
||||||
|
return CreateNewClient();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TwitchWebsocketClient GetWorkingClient()
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (_identified == null)
|
||||||
|
{
|
||||||
|
return CreateNewClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _identified;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TwitchWebsocketClient CreateNewClient()
|
||||||
|
{
|
||||||
|
if (_backup != null)
|
||||||
|
return _backup;
|
||||||
|
|
||||||
|
var client = (_serviceProvider.GetRequiredKeyedService<SocketClient<TwitchWebsocketMessage>>("twitch-create") as TwitchWebsocketClient)!;
|
||||||
|
client.Initialize();
|
||||||
|
_backup = client;
|
||||||
|
|
||||||
|
client.OnIdentified += async (s, e) =>
|
||||||
|
{
|
||||||
|
bool clientDisconnect = false;
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (_identified == client)
|
||||||
|
{
|
||||||
|
_logger.Error("Twitch client has been re-identified.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_backup != client)
|
||||||
|
{
|
||||||
|
_logger.Warning("Twitch client has been identified, but isn't backup. Disconnecting.");
|
||||||
|
clientDisconnect = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_identified != null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_identified = _backup;
|
||||||
|
_backup = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clientDisconnect)
|
||||||
|
await client.DisconnectAsync(new SocketDisconnectionEventArgs("Closed", "No need for a tertiary client."));
|
||||||
|
|
||||||
|
_logger.Information("Twitch client has been identified.");
|
||||||
|
};
|
||||||
|
client.OnDisconnected += (s, e) =>
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (_identified == client)
|
||||||
|
{
|
||||||
|
_identified = null;
|
||||||
|
}
|
||||||
|
else if (_backup == client)
|
||||||
|
{
|
||||||
|
_backup = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_logger.Error("Twitch client disconnection from unknown source.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_logger.Debug("Created a Twitch websocket client.");
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,26 +6,32 @@ using System.Net.WebSockets;
|
|||||||
using TwitchChatTTS.Twitch.Socket.Messages;
|
using TwitchChatTTS.Twitch.Socket.Messages;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using TwitchChatTTS.Twitch.Socket.Handlers;
|
using TwitchChatTTS.Twitch.Socket.Handlers;
|
||||||
|
using CommonSocketLibrary.Backoff;
|
||||||
|
|
||||||
namespace TwitchChatTTS.Twitch.Socket
|
namespace TwitchChatTTS.Twitch.Socket
|
||||||
{
|
{
|
||||||
public class TwitchWebsocketClient : SocketClient<TwitchWebsocketMessage>
|
public class TwitchWebsocketClient : SocketClient<TwitchWebsocketMessage>
|
||||||
{
|
{
|
||||||
|
private readonly IDictionary<string, ITwitchSocketHandler> _handlers;
|
||||||
|
private readonly IDictionary<string, Type> _messageTypes;
|
||||||
|
private readonly IDictionary<string, string> _subscriptions;
|
||||||
|
private readonly IBackoff _backoff;
|
||||||
|
private DateTime _lastReceivedMessageTimestamp;
|
||||||
|
private bool _disconnected;
|
||||||
|
private readonly object _lock;
|
||||||
|
|
||||||
|
public event EventHandler<EventArgs> OnIdentified;
|
||||||
|
|
||||||
public string URL;
|
public string URL;
|
||||||
|
public bool Connected { get; private set; }
|
||||||
private IDictionary<string, ITwitchSocketHandler> _handlers;
|
public bool Identified { get; private set; }
|
||||||
private IDictionary<string, Type> _messageTypes;
|
public string SessionId { get; private set; }
|
||||||
private readonly Configuration _configuration;
|
public bool ReceivedReconnecting { get; set; }
|
||||||
private System.Timers.Timer _reconnectTimer;
|
|
||||||
|
|
||||||
public bool Connected { get; set; }
|
|
||||||
public bool Identified { get; set; }
|
|
||||||
public string SessionId { get; set; }
|
|
||||||
|
|
||||||
|
|
||||||
public TwitchWebsocketClient(
|
public TwitchWebsocketClient(
|
||||||
Configuration configuration,
|
|
||||||
[FromKeyedServices("twitch")] IEnumerable<ITwitchSocketHandler> handlers,
|
[FromKeyedServices("twitch")] IEnumerable<ITwitchSocketHandler> handlers,
|
||||||
|
[FromKeyedServices("twitch")] IBackoff backoff,
|
||||||
ILogger logger
|
ILogger logger
|
||||||
) : base(logger, new JsonSerializerOptions()
|
) : base(logger, new JsonSerializerOptions()
|
||||||
{
|
{
|
||||||
@ -34,14 +40,12 @@ namespace TwitchChatTTS.Twitch.Socket
|
|||||||
})
|
})
|
||||||
{
|
{
|
||||||
_handlers = handlers.ToDictionary(h => h.Name, h => h);
|
_handlers = handlers.ToDictionary(h => h.Name, h => h);
|
||||||
_configuration = configuration;
|
_backoff = backoff;
|
||||||
|
_subscriptions = new Dictionary<string, string>();
|
||||||
_reconnectTimer = new System.Timers.Timer(TimeSpan.FromSeconds(30));
|
_lock = new object();
|
||||||
_reconnectTimer.AutoReset = false;
|
|
||||||
_reconnectTimer.Elapsed += async (sender, e) => await Reconnect();
|
|
||||||
_reconnectTimer.Enabled = false;
|
|
||||||
|
|
||||||
_messageTypes = new Dictionary<string, Type>();
|
_messageTypes = new Dictionary<string, Type>();
|
||||||
|
_messageTypes.Add("session_keepalive", typeof(object));
|
||||||
_messageTypes.Add("session_welcome", typeof(SessionWelcomeMessage));
|
_messageTypes.Add("session_welcome", typeof(SessionWelcomeMessage));
|
||||||
_messageTypes.Add("session_reconnect", typeof(SessionWelcomeMessage));
|
_messageTypes.Add("session_reconnect", typeof(SessionWelcomeMessage));
|
||||||
_messageTypes.Add("notification", typeof(NotificationMessage));
|
_messageTypes.Add("notification", typeof(NotificationMessage));
|
||||||
@ -50,23 +54,56 @@ namespace TwitchChatTTS.Twitch.Socket
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void AddSubscription(string broadcasterId, string type, string id)
|
||||||
|
{
|
||||||
|
if (_subscriptions.ContainsKey(broadcasterId + '|' + type))
|
||||||
|
_subscriptions[broadcasterId + '|' + type] = id;
|
||||||
|
else
|
||||||
|
_subscriptions.Add(broadcasterId + '|' + type, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string? GetSubscriptionId(string broadcasterId, string type)
|
||||||
|
{
|
||||||
|
if (_subscriptions.TryGetValue(broadcasterId + '|' + type, out var id))
|
||||||
|
return id;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveSubscription(string broadcasterId, string type)
|
||||||
|
{
|
||||||
|
_subscriptions.Remove(broadcasterId + '|' + type);
|
||||||
|
}
|
||||||
|
|
||||||
public void Initialize()
|
public void Initialize()
|
||||||
{
|
{
|
||||||
_logger.Information($"Initializing OBS websocket client.");
|
_logger.Information($"Initializing OBS websocket client.");
|
||||||
OnConnected += (sender, e) =>
|
OnConnected += (sender, e) =>
|
||||||
{
|
{
|
||||||
Connected = true;
|
Connected = true;
|
||||||
_reconnectTimer.Enabled = false;
|
|
||||||
_logger.Information("Twitch websocket client connected.");
|
_logger.Information("Twitch websocket client connected.");
|
||||||
|
_disconnected = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
OnDisconnected += (sender, e) =>
|
OnDisconnected += async (sender, e) =>
|
||||||
{
|
{
|
||||||
_reconnectTimer.Enabled = Identified;
|
lock (_lock)
|
||||||
_logger.Information($"Twitch websocket client disconnected [status: {e.Status}][reason: {e.Reason}] " + (Identified ? "Will be attempting to reconnect every 30 seconds." : "Will not be attempting to reconnect."));
|
{
|
||||||
|
if (_disconnected)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_disconnected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.Information($"Twitch websocket client disconnected [status: {e.Status}][reason: {e.Reason}]");
|
||||||
|
|
||||||
Connected = false;
|
Connected = false;
|
||||||
Identified = false;
|
Identified = false;
|
||||||
|
|
||||||
|
if (!ReceivedReconnecting)
|
||||||
|
{
|
||||||
|
_logger.Information("Attempting to reconnect to Twitch websocket server.");
|
||||||
|
await Reconnect(_backoff, async () => await Connect());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,42 +116,14 @@ namespace TwitchChatTTS.Twitch.Socket
|
|||||||
}
|
}
|
||||||
|
|
||||||
_logger.Debug($"Twitch websocket client attempting to connect to {URL}");
|
_logger.Debug($"Twitch websocket client attempting to connect to {URL}");
|
||||||
try
|
await ConnectAsync(URL);
|
||||||
{
|
|
||||||
await ConnectAsync(URL);
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
_logger.Warning("Connecting to twitch failed. Skipping Twitch websockets.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Reconnect()
|
public void Identify(string sessionId)
|
||||||
{
|
{
|
||||||
if (Connected)
|
Identified = true;
|
||||||
{
|
SessionId = sessionId;
|
||||||
try
|
OnIdentified?.Invoke(this, EventArgs.Empty);
|
||||||
{
|
|
||||||
await DisconnectAsync(new SocketDisconnectionEventArgs(WebSocketCloseStatus.Empty.ToString(), ""));
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
_logger.Error("Failed to disconnect from Twitch websocket server.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await Connect();
|
|
||||||
}
|
|
||||||
catch (WebSocketException wse) when (wse.Message.Contains("502"))
|
|
||||||
{
|
|
||||||
_logger.Error("Twitch websocket server cannot be found.");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.Error(ex, "Failed to reconnect to Twitch websocket server.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected TwitchWebsocketMessage GenerateMessage<T>(string messageType, T data)
|
protected TwitchWebsocketMessage GenerateMessage<T>(string messageType, T data)
|
||||||
@ -134,14 +143,17 @@ namespace TwitchChatTTS.Twitch.Socket
|
|||||||
|
|
||||||
protected override async Task OnResponseReceived(TwitchWebsocketMessage? message)
|
protected override async Task OnResponseReceived(TwitchWebsocketMessage? message)
|
||||||
{
|
{
|
||||||
if (message == null || message.Metadata == null) {
|
if (message == null || message.Metadata == null)
|
||||||
|
{
|
||||||
_logger.Information("Twitch message is null");
|
_logger.Information("Twitch message is null");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_lastReceivedMessageTimestamp = DateTime.UtcNow;
|
||||||
|
|
||||||
string content = message.Payload?.ToString() ?? string.Empty;
|
string content = message.Payload?.ToString() ?? string.Empty;
|
||||||
if (message.Metadata.MessageType != "session_keepalive")
|
if (message.Metadata.MessageType != "session_keepalive")
|
||||||
_logger.Information("Twitch RX #" + message.Metadata.MessageType + ": " + content);
|
_logger.Debug("Twitch RX #" + message.Metadata.MessageType + ": " + content);
|
||||||
|
|
||||||
if (!_messageTypes.TryGetValue(message.Metadata.MessageType, out var type) || type == null)
|
if (!_messageTypes.TryGetValue(message.Metadata.MessageType, out var type) || type == null)
|
||||||
{
|
{
|
||||||
@ -156,6 +168,11 @@ namespace TwitchChatTTS.Twitch.Socket
|
|||||||
}
|
}
|
||||||
|
|
||||||
var data = JsonSerializer.Deserialize(content, type, _options);
|
var data = JsonSerializer.Deserialize(content, type, _options);
|
||||||
|
if (data == null)
|
||||||
|
{
|
||||||
|
_logger.Warning("Twitch websocket message payload is null.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
await handler.Execute(this, data);
|
await handler.Execute(this, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,7 +197,7 @@ namespace TwitchChatTTS.Twitch.Socket
|
|||||||
await _socket!.SendAsync(array, WebSocketMessageType.Text, current + size >= total, _cts!.Token);
|
await _socket!.SendAsync(array, WebSocketMessageType.Text, current + size >= total, _cts!.Token);
|
||||||
current += size;
|
current += size;
|
||||||
}
|
}
|
||||||
_logger.Information("TX #" + type + ": " + content);
|
_logger.Debug("Twitch TX #" + type + ": " + content);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
// using System.Text.RegularExpressions;
|
|
||||||
// using HermesSocketLibrary.Request.Message;
|
|
||||||
// using TwitchChatTTS.Hermes;
|
|
||||||
|
|
||||||
// namespace TwitchChatTTS.Twitch
|
|
||||||
// {
|
|
||||||
// public class TTSContext
|
|
||||||
// {
|
|
||||||
// public string DefaultVoice;
|
|
||||||
// public IEnumerable<TTSVoice>? EnabledVoices;
|
|
||||||
// public IDictionary<string, TTSUsernameFilter>? UsernameFilters;
|
|
||||||
// public IEnumerable<TTSWordFilter>? WordFilters;
|
|
||||||
// public IList<VoiceDetails>? AvailableVoices { get => _availableVoices; set { _availableVoices = value; EnabledVoicesRegex = GenerateEnabledVoicesRegex(); } }
|
|
||||||
// public IDictionary<long, string>? SelectedVoices;
|
|
||||||
// public Regex? EnabledVoicesRegex;
|
|
||||||
|
|
||||||
// private IList<VoiceDetails>? _availableVoices;
|
|
||||||
|
|
||||||
|
|
||||||
// private Regex? GenerateEnabledVoicesRegex() {
|
|
||||||
// if (AvailableVoices == null || AvailableVoices.Count() <= 0) {
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var enabledVoicesString = string.Join("|", AvailableVoices.Select(v => v.Name));
|
|
||||||
// return new Regex($@"\b({enabledVoicesString})\:(.*?)(?=\Z|\b(?:{enabledVoicesString})\:)", RegexOptions.IgnoreCase);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
@ -24,35 +24,40 @@ public class TwitchApiClient
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<EventResponse<EventSubscriptionMessage>?> CreateEventSubscription(string type, string version, string userId)
|
public async Task<EventResponse<NotificationInfo>?> CreateEventSubscription(string type, string version, string sessionId, string userId, string? broadcasterId = null)
|
||||||
{
|
{
|
||||||
var conditions = new Dictionary<string, string>() { { "user_id", userId }, { "broadcaster_user_id", userId }, { "moderator_user_id", userId } };
|
var conditions = new Dictionary<string, string>() { { "user_id", userId }, { "broadcaster_user_id", broadcasterId ?? userId }, { "moderator_user_id", broadcasterId ?? userId } };
|
||||||
var subscriptionData = new EventSubscriptionMessage(type, version, "https://hermes.goblincaves.com/api/account/authorize", "isdnmjfopsdfmsf4390", conditions);
|
|
||||||
var response = await _web.Post("https://api.twitch.tv/helix/eventsub/subscriptions", subscriptionData);
|
|
||||||
if (response.StatusCode == HttpStatusCode.Accepted)
|
|
||||||
{
|
|
||||||
_logger.Debug("Twitch API call [type: create event subscription]: " + await response.Content.ReadAsStringAsync());
|
|
||||||
return await response.Content.ReadFromJsonAsync(typeof(EventResponse<EventSubscriptionMessage>)) as EventResponse<EventSubscriptionMessage>;
|
|
||||||
}
|
|
||||||
_logger.Warning("Twitch api failed to create event subscription: " + await response.Content.ReadAsStringAsync());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<EventResponse<EventSubscriptionMessage>?> CreateEventSubscription(string type, string version, string sessionId, string userId)
|
|
||||||
{
|
|
||||||
var conditions = new Dictionary<string, string>() { { "user_id", userId }, { "broadcaster_user_id", userId }, { "moderator_user_id", userId } };
|
|
||||||
var subscriptionData = new EventSubscriptionMessage(type, version, sessionId, conditions);
|
var subscriptionData = new EventSubscriptionMessage(type, version, sessionId, conditions);
|
||||||
var response = await _web.Post("https://api.twitch.tv/helix/eventsub/subscriptions", subscriptionData);
|
var response = await _web.Post("https://api.twitch.tv/helix/eventsub/subscriptions", subscriptionData);
|
||||||
if (response.StatusCode == HttpStatusCode.Accepted)
|
if (response.StatusCode == HttpStatusCode.Accepted)
|
||||||
{
|
{
|
||||||
_logger.Debug("Twitch API call [type: create event subscription]: " + await response.Content.ReadAsStringAsync());
|
_logger.Debug("Twitch API call [type: create event subscription]: " + await response.Content.ReadAsStringAsync());
|
||||||
return await response.Content.ReadFromJsonAsync(typeof(EventResponse<EventSubscriptionMessage>)) as EventResponse<EventSubscriptionMessage>;
|
return await response.Content.ReadFromJsonAsync(typeof(EventResponse<NotificationInfo>)) as EventResponse<NotificationInfo>;
|
||||||
}
|
}
|
||||||
_logger.Error("Twitch api failed to create event subscription: " + await response.Content.ReadAsStringAsync());
|
_logger.Error("Twitch api failed to create event subscription for websocket: " + await response.Content.ReadAsStringAsync());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Initialize(TwitchBotToken token) {
|
public async Task DeleteEventSubscription(string subscriptionId)
|
||||||
|
{
|
||||||
|
await _web.Delete("https://api.twitch.tv/helix/eventsub/subscriptions?id=" + subscriptionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<EventResponse<NotificationInfo>?> GetSubscriptions(string? status = null, string? broadcasterId = null, string? after = null)
|
||||||
|
{
|
||||||
|
List<string> queryParams = new List<string>();
|
||||||
|
if (!string.IsNullOrWhiteSpace(status))
|
||||||
|
queryParams.Add("status=" + status);
|
||||||
|
if (!string.IsNullOrWhiteSpace(broadcasterId))
|
||||||
|
queryParams.Add("user_id=" + broadcasterId);
|
||||||
|
if (!string.IsNullOrWhiteSpace(after))
|
||||||
|
queryParams.Add("after=" + after);
|
||||||
|
var query = queryParams.Any() ? '?' + string.Join('&', queryParams) : string.Empty;
|
||||||
|
return await _web.GetJson<EventResponse<NotificationInfo>>("https://api.twitch.tv/helix/eventsub/subscriptions" + query);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Initialize(TwitchBotToken token)
|
||||||
|
{
|
||||||
_web.AddHeader("Authorization", "Bearer " + token.AccessToken);
|
_web.AddHeader("Authorization", "Bearer " + token.AccessToken);
|
||||||
_web.AddHeader("Client-Id", token.ClientId);
|
_web.AddHeader("Client-Id", token.ClientId);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user