Added hermes websocket support. Added chat command support. Added selectable voice command via websocket. Added websocket heartbeat management.
This commit is contained in:
@ -4,22 +4,22 @@ using TwitchChatTTS.OBS.Socket;
|
||||
using CommonSocketLibrary.Abstract;
|
||||
using CommonSocketLibrary.Common;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TwitchChatTTS.Twitch;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TwitchChatTTS;
|
||||
using TwitchChatTTS.Seven;
|
||||
using TwitchChatTTS.Chat.Commands;
|
||||
|
||||
|
||||
public class ChatMessageHandler {
|
||||
private ILogger<ChatMessageHandler> Logger { get; }
|
||||
private Configuration Configuration { get; }
|
||||
public EmoteCounter EmoteCounter { get; }
|
||||
private EmoteDatabase Emotes { get; }
|
||||
private TTSPlayer Player { get; }
|
||||
private OBSSocketClient? Client { get; }
|
||||
private TTSContext Context { get; }
|
||||
private ILogger<ChatMessageHandler> _logger { get; }
|
||||
private Configuration _configuration { get; }
|
||||
public EmoteCounter _emoteCounter { get; }
|
||||
private EmoteDatabase _emotes { get; }
|
||||
private TTSPlayer _player { get; }
|
||||
private ChatCommandManager _commands { get; }
|
||||
private OBSSocketClient? _obsClient { get; }
|
||||
private IServiceProvider _serviceProvider { get; }
|
||||
|
||||
private Regex? voicesRegex;
|
||||
private Regex sfxRegex;
|
||||
|
||||
|
||||
@ -29,46 +29,53 @@ public class ChatMessageHandler {
|
||||
EmoteCounter emoteCounter,
|
||||
EmoteDatabase emotes,
|
||||
TTSPlayer player,
|
||||
ChatCommandManager commands,
|
||||
[FromKeyedServices("obs")] SocketClient<WebSocketMessage> client,
|
||||
TTSContext context
|
||||
IServiceProvider serviceProvider
|
||||
) {
|
||||
Logger = logger;
|
||||
Configuration = configuration;
|
||||
EmoteCounter = emoteCounter;
|
||||
Emotes = emotes;
|
||||
Player = player;
|
||||
Client = client as OBSSocketClient;
|
||||
Context = context;
|
||||
_logger = logger;
|
||||
_configuration = configuration;
|
||||
_emoteCounter = emoteCounter;
|
||||
_emotes = emotes;
|
||||
_player = player;
|
||||
_commands = commands;
|
||||
_obsClient = client as OBSSocketClient;
|
||||
_serviceProvider = serviceProvider;
|
||||
|
||||
voicesRegex = GenerateEnabledVoicesRegex();
|
||||
sfxRegex = new Regex(@"\(([A-Za-z0-9_-]+)\)");
|
||||
}
|
||||
|
||||
|
||||
public MessageResult Handle(OnMessageReceivedArgs e) {
|
||||
if (Configuration.Twitch?.TtsWhenOffline != true && Client?.Live != true)
|
||||
public async Task<MessageResult> Handle(OnMessageReceivedArgs e) {
|
||||
if (_configuration.Twitch?.TtsWhenOffline != true && _obsClient?.Live == false)
|
||||
return MessageResult.Blocked;
|
||||
|
||||
|
||||
var user = _serviceProvider.GetRequiredService<User>();
|
||||
var m = e.ChatMessage;
|
||||
var msg = e.ChatMessage.Message;
|
||||
|
||||
// Skip TTS messages
|
||||
if (m.IsVip || m.IsModerator || m.IsBroadcaster) {
|
||||
if (msg.ToLower().StartsWith("!skip ") || msg.ToLower() == "!skip")
|
||||
return MessageResult.Skip;
|
||||
|
||||
if (msg.ToLower().StartsWith("!skipall ") || msg.ToLower() == "!skipall")
|
||||
return MessageResult.SkipAll;
|
||||
var chatterId = long.Parse(m.UserId);
|
||||
|
||||
var blocked = user.ChatterFilters.TryGetValue(m.Username, out TTSUsernameFilter? filter) && filter.Tag == "blacklisted";
|
||||
|
||||
if (!blocked || m.IsBroadcaster) {
|
||||
try {
|
||||
var commandResult = await _commands.Execute(msg, m);
|
||||
if (commandResult != ChatCommandResult.Unknown) {
|
||||
return MessageResult.Command;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
_logger.LogError(ex, "Failed at executing command.");
|
||||
}
|
||||
}
|
||||
|
||||
if (Context.UsernameFilters.TryGetValue(m.Username, out TTSUsernameFilter? filter) && filter.Tag == "blacklisted") {
|
||||
Logger.LogTrace($"Blocked message by {m.Username}: {msg}");
|
||||
if (blocked) {
|
||||
_logger.LogTrace($"Blocked message by {m.Username}: {msg}");
|
||||
return MessageResult.Blocked;
|
||||
}
|
||||
|
||||
// Replace filtered words.
|
||||
if (Context.WordFilters is not null) {
|
||||
foreach (var wf in Context.WordFilters) {
|
||||
if (user.RegexFilters != null) {
|
||||
foreach (var wf in user.RegexFilters) {
|
||||
if (wf.Search == null || wf.Replace == null)
|
||||
continue;
|
||||
|
||||
@ -87,6 +94,7 @@ public class ChatMessageHandler {
|
||||
}
|
||||
|
||||
// Filter highly repetitive words (like emotes) from the message.
|
||||
int totalEmoteUsed = 0;
|
||||
var emotesUsed = new HashSet<string>();
|
||||
var words = msg.Split(" ");
|
||||
var wordCounter = new Dictionary<string, int>();
|
||||
@ -98,24 +106,31 @@ public class ChatMessageHandler {
|
||||
wordCounter.Add(w, 1);
|
||||
}
|
||||
|
||||
var emoteId = Emotes?.Get(w);
|
||||
if (emoteId != null)
|
||||
emotesUsed.Add("7tv-" + emoteId);
|
||||
var emoteId = _emotes?.Get(w);
|
||||
if (emoteId == null)
|
||||
emoteId = m.EmoteSet.Emotes.FirstOrDefault(e => e.Name == w)?.Id;
|
||||
if (emoteId != null) {
|
||||
emotesUsed.Add(emoteId);
|
||||
totalEmoteUsed++;
|
||||
}
|
||||
|
||||
if (wordCounter[w] <= 4 && (emoteId == null || emotesUsed.Count <= 4))
|
||||
if (wordCounter[w] <= 4 && (emoteId == null || totalEmoteUsed <= 5))
|
||||
filteredMsg += w + " ";
|
||||
}
|
||||
msg = filteredMsg;
|
||||
|
||||
// Adding twitch emotes to the counter.
|
||||
foreach (var emote in e.ChatMessage.EmoteSet.Emotes)
|
||||
emotesUsed.Add("twitch-" + emote.Id);
|
||||
foreach (var emote in e.ChatMessage.EmoteSet.Emotes) {
|
||||
_logger.LogTrace("Twitch emote name used: " + emote.Name);
|
||||
emotesUsed.Add(emote.Id);
|
||||
}
|
||||
|
||||
if (long.TryParse(e.ChatMessage.UserId, out long userId))
|
||||
EmoteCounter.Add(userId, emotesUsed);
|
||||
_emoteCounter.Add(userId, emotesUsed);
|
||||
if (emotesUsed.Any())
|
||||
Logger.LogDebug("Emote counters for user #" + userId + ": " + string.Join(" | ", emotesUsed.Select(e => e + "=" + EmoteCounter.Get(userId, e))));
|
||||
_logger.LogDebug("Emote counters for user #" + userId + ": " + string.Join(" | ", emotesUsed.Select(e => e + "=" + _emoteCounter.Get(userId, e))));
|
||||
|
||||
// Determine the priority of this message
|
||||
int priority = 0;
|
||||
if (m.IsStaff) {
|
||||
priority = int.MinValue;
|
||||
@ -130,19 +145,30 @@ public class ChatMessageHandler {
|
||||
} else if (m.IsHighlighted) {
|
||||
priority = -1;
|
||||
}
|
||||
priority = (int) Math.Round(Math.Min(priority, -m.SubscribedMonthCount * (m.Badges.Any(b => b.Key == "subscriber") ? 1.2 : 1)));
|
||||
priority = Math.Min(priority, -m.SubscribedMonthCount * (m.IsSubscriber ? 2 : 1));
|
||||
|
||||
var matches = voicesRegex?.Matches(msg).ToArray() ?? new Match[0];
|
||||
int defaultEnd = matches.FirstOrDefault()?.Index ?? msg.Length;
|
||||
if (defaultEnd > 0) {
|
||||
HandlePartialMessage(priority, Context.DefaultVoice, msg.Substring(0, defaultEnd).Trim(), e);
|
||||
// Determine voice selected.
|
||||
string voiceSelected = user.DefaultTTSVoice;
|
||||
if (user.VoicesSelected?.ContainsKey(userId) == true) {
|
||||
var voiceId = user.VoicesSelected[userId];
|
||||
if (user.VoicesAvailable.TryGetValue(voiceId, out string? voiceName) && voiceName != null) {
|
||||
voiceSelected = voiceName;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine additional voices used
|
||||
var voicesRegex = user.GenerateEnabledVoicesRegex();
|
||||
var matches = voicesRegex?.Matches(msg).ToArray();
|
||||
if (matches == null || matches.FirstOrDefault() == null || matches.FirstOrDefault().Index == 0) {
|
||||
HandlePartialMessage(priority, voiceSelected, msg.Trim(), e);
|
||||
return MessageResult.None;
|
||||
}
|
||||
|
||||
HandlePartialMessage(priority, voiceSelected, msg.Substring(0, matches.FirstOrDefault().Index).Trim(), e);
|
||||
foreach (Match match in matches) {
|
||||
var message = match.Groups[2].ToString();
|
||||
if (string.IsNullOrWhiteSpace(message)) {
|
||||
if (string.IsNullOrWhiteSpace(message))
|
||||
continue;
|
||||
}
|
||||
|
||||
var voice = match.Groups[1].ToString();
|
||||
voice = voice[0].ToString().ToUpper() + voice.Substring(1).ToLower();
|
||||
@ -162,8 +188,8 @@ public class ChatMessageHandler {
|
||||
var badgesString = string.Join(", ", e.ChatMessage.Badges.Select(b => b.Key + " = " + b.Value));
|
||||
|
||||
if (parts.Length == 1) {
|
||||
Logger.LogInformation($"Voice: {voice}; Priority: {priority}; Message: {message}; Month: {m.SubscribedMonthCount}; {badgesString}");
|
||||
Player.Add(new TTSMessage() {
|
||||
_logger.LogInformation($"Voice: {voice}; Priority: {priority}; Message: {message}; Month: {m.SubscribedMonthCount}; {badgesString}");
|
||||
_player.Add(new TTSMessage() {
|
||||
Voice = voice,
|
||||
Message = message,
|
||||
Moderator = m.IsModerator,
|
||||
@ -189,8 +215,8 @@ public class ChatMessageHandler {
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(parts[i * 2])) {
|
||||
Logger.LogInformation($"Voice: {voice}; Priority: {priority}; Message: {parts[i * 2]}; Month: {m.SubscribedMonthCount}; {badgesString}");
|
||||
Player.Add(new TTSMessage() {
|
||||
_logger.LogInformation($"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,
|
||||
@ -202,8 +228,8 @@ public class ChatMessageHandler {
|
||||
});
|
||||
}
|
||||
|
||||
Logger.LogInformation($"Voice: {voice}; Priority: {priority}; SFX: {sfxName}; Month: {m.SubscribedMonthCount}; {badgesString}");
|
||||
Player.Add(new TTSMessage() {
|
||||
_logger.LogInformation($"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",
|
||||
@ -217,8 +243,8 @@ public class ChatMessageHandler {
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(parts.Last())) {
|
||||
Logger.LogInformation($"Voice: {voice}; Priority: {priority}; Message: {parts.Last()}; Month: {m.SubscribedMonthCount}; {badgesString}");
|
||||
Player.Add(new TTSMessage() {
|
||||
_logger.LogInformation($"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,
|
||||
@ -230,13 +256,4 @@ public class ChatMessageHandler {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private Regex? GenerateEnabledVoicesRegex() {
|
||||
if (Context.EnabledVoices == null || Context.EnabledVoices.Count() <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var enabledVoicesString = string.Join("|", Context.EnabledVoices.Select(v => v.Label));
|
||||
return new Regex($@"\b({enabledVoicesString})\:(.*?)(?=\Z|\b(?:{enabledVoicesString})\:)", RegexOptions.IgnoreCase);
|
||||
}
|
||||
}
|
54
Chat/Commands/AddTTSVoiceCommand.cs
Normal file
54
Chat/Commands/AddTTSVoiceCommand.cs
Normal file
@ -0,0 +1,54 @@
|
||||
using CommonSocketLibrary.Abstract;
|
||||
using CommonSocketLibrary.Common;
|
||||
using HermesSocketLibrary.Socket.Data;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TwitchChatTTS.Chat.Commands.Parameters;
|
||||
using TwitchLib.Client.Models;
|
||||
|
||||
namespace TwitchChatTTS.Chat.Commands
|
||||
{
|
||||
public class AddTTSVoiceCommand : ChatCommand
|
||||
{
|
||||
private IServiceProvider _serviceProvider;
|
||||
private ILogger<AddTTSVoiceCommand> _logger;
|
||||
|
||||
public AddTTSVoiceCommand(
|
||||
[FromKeyedServices("parameter-unvalidated")] ChatCommandParameter ttsVoiceParameter,
|
||||
IServiceProvider serviceProvider,
|
||||
ILogger<AddTTSVoiceCommand> logger
|
||||
) : base("addttsvoice", "Select a TTS voice as the default for that user.") {
|
||||
_serviceProvider = serviceProvider;
|
||||
_logger = logger;
|
||||
|
||||
AddParameter(ttsVoiceParameter);
|
||||
}
|
||||
|
||||
public override async Task<bool> CheckPermissions(ChatMessage message, long broadcasterId)
|
||||
{
|
||||
return message.IsModerator || message.IsBroadcaster || message.UserId == "126224566";
|
||||
}
|
||||
|
||||
public override async Task Execute(IList<string> args, ChatMessage message, long broadcasterId)
|
||||
{
|
||||
var client = _serviceProvider.GetRequiredKeyedService<SocketClient<WebSocketMessage>>("hermes");
|
||||
if (client == null)
|
||||
return;
|
||||
var context = _serviceProvider.GetRequiredService<User>();
|
||||
if (context == null || context.VoicesAvailable == null)
|
||||
return;
|
||||
|
||||
var voiceName = args.First();
|
||||
var voiceNameLower = voiceName.ToLower();
|
||||
var exists = context.VoicesAvailable.Any(v => v.Value.ToLower() == voiceNameLower);
|
||||
if (exists)
|
||||
return;
|
||||
|
||||
await client.Send(3, new RequestMessage() {
|
||||
Type = "create_tts_voice",
|
||||
Data = new Dictionary<string, string>() { { "@voice", voiceName } }
|
||||
});
|
||||
_logger.LogInformation($"Added a new TTS voice by {message.Username} (id: {message.UserId}): {voiceName}.");
|
||||
}
|
||||
}
|
||||
}
|
27
Chat/Commands/ChatCommand.cs
Normal file
27
Chat/Commands/ChatCommand.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using TwitchChatTTS.Chat.Commands.Parameters;
|
||||
using TwitchLib.Client.Models;
|
||||
|
||||
namespace TwitchChatTTS.Chat.Commands
|
||||
{
|
||||
public abstract class ChatCommand
|
||||
{
|
||||
public string Name { get; }
|
||||
public string Description { get; }
|
||||
public IList<ChatCommandParameter> Parameters { get => _parameters.AsReadOnly(); }
|
||||
private IList<ChatCommandParameter> _parameters;
|
||||
|
||||
public ChatCommand(string name, string description) {
|
||||
Name = name;
|
||||
Description = description;
|
||||
_parameters = new List<ChatCommandParameter>();
|
||||
}
|
||||
|
||||
protected void AddParameter(ChatCommandParameter parameter) {
|
||||
if (parameter != null)
|
||||
_parameters.Add(parameter);
|
||||
}
|
||||
|
||||
public abstract Task<bool> CheckPermissions(ChatMessage message, long broadcasterId);
|
||||
public abstract Task Execute(IList<string> args, ChatMessage message, long broadcasterId);
|
||||
}
|
||||
}
|
100
Chat/Commands/ChatCommandManager.cs
Normal file
100
Chat/Commands/ChatCommandManager.cs
Normal file
@ -0,0 +1,100 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TwitchLib.Client.Models;
|
||||
|
||||
namespace TwitchChatTTS.Chat.Commands
|
||||
{
|
||||
public class ChatCommandManager
|
||||
{
|
||||
private IDictionary<string, ChatCommand> _commands;
|
||||
private TwitchBotToken _token;
|
||||
private IServiceProvider _serviceProvider;
|
||||
private ILogger<ChatCommandManager> _logger;
|
||||
private string CommandStartSign { get; } = "!";
|
||||
|
||||
|
||||
public ChatCommandManager(TwitchBotToken token, IServiceProvider serviceProvider, ILogger<ChatCommandManager> logger) {
|
||||
_token = token;
|
||||
_serviceProvider = serviceProvider;
|
||||
_logger = logger;
|
||||
|
||||
_commands = new Dictionary<string, ChatCommand>();
|
||||
GenerateCommands();
|
||||
}
|
||||
|
||||
private void Add(ChatCommand command) {
|
||||
_commands.Add(command.Name.ToLower(), command);
|
||||
}
|
||||
|
||||
private void GenerateCommands() {
|
||||
var basetype = typeof(ChatCommand);
|
||||
var assembly = GetType().Assembly;
|
||||
var types = assembly.GetTypes().Where(t => t.IsClass && !t.IsAbstract && basetype.IsAssignableFrom(t) && t.AssemblyQualifiedName?.Contains(".Chat.") == true);
|
||||
|
||||
foreach (var type in types) {
|
||||
var key = "command-" + type.Name.Replace("Commands", "Comm#ands")
|
||||
.Replace("Command", "")
|
||||
.Replace("Comm#ands", "Commands")
|
||||
.ToLower();
|
||||
|
||||
var command = _serviceProvider.GetKeyedService<ChatCommand>(key);
|
||||
if (command == null) {
|
||||
_logger.LogError("Failed to add command: " + type.AssemblyQualifiedName);
|
||||
continue;
|
||||
}
|
||||
|
||||
_logger.LogDebug($"Added command {type.AssemblyQualifiedName}.");
|
||||
Add(command);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ChatCommandResult> Execute(string arg, ChatMessage message) {
|
||||
if (_token.BroadcasterId == null)
|
||||
return ChatCommandResult.Unknown;
|
||||
if (string.IsNullOrWhiteSpace(arg))
|
||||
return ChatCommandResult.Unknown;
|
||||
|
||||
arg = arg.Trim();
|
||||
|
||||
if (!arg.StartsWith(CommandStartSign))
|
||||
return ChatCommandResult.Unknown;
|
||||
|
||||
string[] parts = arg.Split(" ");
|
||||
string com = parts.First().Substring(CommandStartSign.Length).ToLower();
|
||||
string[] args = parts.Skip(1).ToArray();
|
||||
long broadcasterId = long.Parse(_token.BroadcasterId);
|
||||
|
||||
if (!_commands.TryGetValue(com, out ChatCommand? command) || command == null) {
|
||||
_logger.LogDebug($"Failed to find command named '{com}'.");
|
||||
return ChatCommandResult.Missing;
|
||||
}
|
||||
|
||||
if (!await command.CheckPermissions(message, broadcasterId)) {
|
||||
_logger.LogWarning($"Chatter is missing permission to execute command named '{com}'.");
|
||||
return ChatCommandResult.Permission;
|
||||
}
|
||||
|
||||
if (command.Parameters.Count(p => !p.Optional) > args.Length) {
|
||||
_logger.LogWarning($"Command syntax issue when executing command named '{com}' with the following args: {string.Join(" ", args)}");
|
||||
return ChatCommandResult.Syntax;
|
||||
}
|
||||
|
||||
for (int i = 0; i < Math.Min(args.Length, command.Parameters.Count); i++) {
|
||||
if (!command.Parameters[i].Validate(args[i])) {
|
||||
_logger.LogWarning($"Commmand '{com}' failed because of the #{i + 1} argument. Invalid value: {args[i]}");
|
||||
return ChatCommandResult.Syntax;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await command.Execute(args, message, broadcasterId);
|
||||
} catch (Exception e) {
|
||||
_logger.LogError(e, $"Command '{arg}' failed.");
|
||||
return ChatCommandResult.Fail;
|
||||
}
|
||||
|
||||
_logger.LogInformation($"Execute the {com} command with the following args: " + string.Join(" ", args));
|
||||
return ChatCommandResult.Success;
|
||||
}
|
||||
}
|
||||
}
|
12
Chat/Commands/ChatCommandResult.cs
Normal file
12
Chat/Commands/ChatCommandResult.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace TwitchChatTTS.Chat.Commands
|
||||
{
|
||||
public enum ChatCommandResult
|
||||
{
|
||||
Unknown = 0,
|
||||
Missing = 1,
|
||||
Success = 2,
|
||||
Permission = 3,
|
||||
Syntax = 4,
|
||||
Fail = 5
|
||||
}
|
||||
}
|
17
Chat/Commands/Parameters/ChatCommandParameter.cs
Normal file
17
Chat/Commands/Parameters/ChatCommandParameter.cs
Normal file
@ -0,0 +1,17 @@
|
||||
namespace TwitchChatTTS.Chat.Commands.Parameters
|
||||
{
|
||||
public abstract class ChatCommandParameter
|
||||
{
|
||||
public string Name { get; }
|
||||
public string Description { get; }
|
||||
public bool Optional { get; }
|
||||
|
||||
public ChatCommandParameter(string name, string description, bool optional = false) {
|
||||
Name = name;
|
||||
Description = description;
|
||||
Optional = optional;
|
||||
}
|
||||
|
||||
public abstract bool Validate(string value);
|
||||
}
|
||||
}
|
23
Chat/Commands/Parameters/TTSVoiceNameParameter.cs
Normal file
23
Chat/Commands/Parameters/TTSVoiceNameParameter.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
namespace TwitchChatTTS.Chat.Commands.Parameters
|
||||
{
|
||||
public class TTSVoiceNameParameter : ChatCommandParameter
|
||||
{
|
||||
private IServiceProvider _serviceProvider;
|
||||
|
||||
public TTSVoiceNameParameter(IServiceProvider serviceProvider, bool optional = false) : base("TTS Voice Name", "Name of a TTS voice", optional)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public override bool Validate(string value)
|
||||
{
|
||||
var user = _serviceProvider.GetRequiredService<User>();
|
||||
if (user.VoicesAvailable == null)
|
||||
return false;
|
||||
|
||||
value = value.ToLower();
|
||||
return user.VoicesAvailable.Any(e => e.Value.ToLower() == value);
|
||||
}
|
||||
}
|
||||
}
|
14
Chat/Commands/Parameters/UnvalidatedParameter.cs
Normal file
14
Chat/Commands/Parameters/UnvalidatedParameter.cs
Normal file
@ -0,0 +1,14 @@
|
||||
namespace TwitchChatTTS.Chat.Commands.Parameters
|
||||
{
|
||||
public class UnvalidatedParameter : ChatCommandParameter
|
||||
{
|
||||
public UnvalidatedParameter(bool optional = false) : base("TTS Voice Name", "Name of a TTS voice", optional)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool Validate(string value)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
54
Chat/Commands/RemoveTTSVoiceCommand.cs
Normal file
54
Chat/Commands/RemoveTTSVoiceCommand.cs
Normal file
@ -0,0 +1,54 @@
|
||||
using CommonSocketLibrary.Abstract;
|
||||
using CommonSocketLibrary.Common;
|
||||
using HermesSocketLibrary.Socket.Data;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TwitchChatTTS.Chat.Commands.Parameters;
|
||||
using TwitchLib.Client.Models;
|
||||
|
||||
namespace TwitchChatTTS.Chat.Commands
|
||||
{
|
||||
public class RemoveTTSVoiceCommand : ChatCommand
|
||||
{
|
||||
private IServiceProvider _serviceProvider;
|
||||
private ILogger<RemoveTTSVoiceCommand> _logger;
|
||||
|
||||
public RemoveTTSVoiceCommand(
|
||||
[FromKeyedServices("parameter-unvalidated")] ChatCommandParameter ttsVoiceParameter,
|
||||
IServiceProvider serviceProvider,
|
||||
ILogger<RemoveTTSVoiceCommand> logger
|
||||
) : base("removettsvoice", "Select a TTS voice as the default for that user.") {
|
||||
_serviceProvider = serviceProvider;
|
||||
_logger = logger;
|
||||
|
||||
AddParameter(ttsVoiceParameter);
|
||||
}
|
||||
|
||||
public override async Task<bool> CheckPermissions(ChatMessage message, long broadcasterId)
|
||||
{
|
||||
return message.IsModerator || message.IsBroadcaster || message.UserId == "126224566";
|
||||
}
|
||||
|
||||
public override async Task Execute(IList<string> args, ChatMessage message, long broadcasterId)
|
||||
{
|
||||
var client = _serviceProvider.GetRequiredKeyedService<SocketClient<WebSocketMessage>>("hermes");
|
||||
if (client == null)
|
||||
return;
|
||||
var context = _serviceProvider.GetRequiredService<User>();
|
||||
if (context == null || context.VoicesAvailable == null)
|
||||
return;
|
||||
|
||||
var voiceName = args.First().ToLower();
|
||||
var exists = context.VoicesAvailable.Any(v => v.Value.ToLower() == voiceName);
|
||||
if (!exists)
|
||||
return;
|
||||
|
||||
var voiceId = context.VoicesAvailable.FirstOrDefault(v => v.Value.ToLower() == voiceName).Key;
|
||||
await client.Send(3, new RequestMessage() {
|
||||
Type = "delete_tts_voice",
|
||||
Data = new Dictionary<string, string>() { { "@voice", voiceId } }
|
||||
});
|
||||
_logger.LogInformation($"Deleted a TTS voice by {message.Username} (id: {message.UserId}): {voiceName}.");
|
||||
}
|
||||
}
|
||||
}
|
37
Chat/Commands/SkipAllCommand.cs
Normal file
37
Chat/Commands/SkipAllCommand.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TwitchLib.Client.Models;
|
||||
|
||||
namespace TwitchChatTTS.Chat.Commands
|
||||
{
|
||||
public class SkipAllCommand : ChatCommand
|
||||
{
|
||||
private IServiceProvider _serviceProvider;
|
||||
private ILogger<SkipAllCommand> _logger;
|
||||
|
||||
public SkipAllCommand(IServiceProvider serviceProvider, ILogger<SkipAllCommand> logger)
|
||||
: base("skipall", "Skips all text to speech messages in queue and playing.") {
|
||||
_serviceProvider = serviceProvider;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override async Task<bool> CheckPermissions(ChatMessage message, long broadcasterId)
|
||||
{
|
||||
return message.IsModerator || message.IsVip || message.IsBroadcaster;
|
||||
}
|
||||
|
||||
public override async Task Execute(IList<string> args, ChatMessage message, long broadcasterId)
|
||||
{
|
||||
var player = _serviceProvider.GetRequiredService<TTSPlayer>();
|
||||
player.RemoveAll();
|
||||
|
||||
if (player.Playing == null)
|
||||
return;
|
||||
|
||||
AudioPlaybackEngine.Instance.RemoveMixerInput(player.Playing);
|
||||
player.Playing = null;
|
||||
|
||||
_logger.LogInformation("Skipped all queued and playing tts.");
|
||||
}
|
||||
}
|
||||
}
|
35
Chat/Commands/SkipCommand.cs
Normal file
35
Chat/Commands/SkipCommand.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TwitchLib.Client.Models;
|
||||
|
||||
namespace TwitchChatTTS.Chat.Commands
|
||||
{
|
||||
public class SkipCommand : ChatCommand
|
||||
{
|
||||
private IServiceProvider _serviceProvider;
|
||||
private ILogger<SkipCommand> _logger;
|
||||
|
||||
public SkipCommand(IServiceProvider serviceProvider, ILogger<SkipCommand> logger)
|
||||
: base("skip", "Skips the current text to speech message.") {
|
||||
_serviceProvider = serviceProvider;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override async Task<bool> CheckPermissions(ChatMessage message, long broadcasterId)
|
||||
{
|
||||
return message.IsModerator || message.IsVip || message.IsBroadcaster;
|
||||
}
|
||||
|
||||
public override async Task Execute(IList<string> args, ChatMessage message, long broadcasterId)
|
||||
{
|
||||
var player = _serviceProvider.GetRequiredService<TTSPlayer>();
|
||||
if (player.Playing == null)
|
||||
return;
|
||||
|
||||
AudioPlaybackEngine.Instance.RemoveMixerInput(player.Playing);
|
||||
player.Playing = null;
|
||||
|
||||
_logger.LogInformation("Skipped current tts.");
|
||||
}
|
||||
}
|
||||
}
|
60
Chat/Commands/VoiceCommand.cs
Normal file
60
Chat/Commands/VoiceCommand.cs
Normal file
@ -0,0 +1,60 @@
|
||||
using CommonSocketLibrary.Abstract;
|
||||
using CommonSocketLibrary.Common;
|
||||
using HermesSocketLibrary.Socket.Data;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TwitchChatTTS.Chat.Commands.Parameters;
|
||||
using TwitchLib.Client.Models;
|
||||
|
||||
namespace TwitchChatTTS.Chat.Commands
|
||||
{
|
||||
public class VoiceCommand : ChatCommand
|
||||
{
|
||||
private IServiceProvider _serviceProvider;
|
||||
private ILogger<VoiceCommand> _logger;
|
||||
|
||||
public VoiceCommand(
|
||||
[FromKeyedServices("parameter-ttsvoicename")] ChatCommandParameter ttsVoiceParameter,
|
||||
IServiceProvider serviceProvider,
|
||||
ILogger<VoiceCommand> logger
|
||||
) : base("voice", "Select a TTS voice as the default for that user.") {
|
||||
_serviceProvider = serviceProvider;
|
||||
_logger = logger;
|
||||
|
||||
AddParameter(ttsVoiceParameter);
|
||||
}
|
||||
|
||||
public override async Task<bool> CheckPermissions(ChatMessage message, long broadcasterId)
|
||||
{
|
||||
return message.IsModerator || message.IsBroadcaster || message.IsSubscriber || message.Bits >= 100 || message.UserId == "126224566";
|
||||
}
|
||||
|
||||
public override async Task Execute(IList<string> args, ChatMessage message, long broadcasterId)
|
||||
{
|
||||
var client = _serviceProvider.GetRequiredKeyedService<SocketClient<WebSocketMessage>>("hermes");
|
||||
if (client == null)
|
||||
return;
|
||||
var context = _serviceProvider.GetRequiredService<User>();
|
||||
if (context == null || context.VoicesSelected == null || context.VoicesAvailable == null)
|
||||
return;
|
||||
|
||||
long chatterId = long.Parse(message.UserId);
|
||||
var voiceName = args.First().ToLower();
|
||||
var voice = context.VoicesAvailable.First(v => v.Value.ToLower() == voiceName);
|
||||
|
||||
if (context.VoicesSelected.ContainsKey(chatterId)) {
|
||||
await client.Send(3, new RequestMessage() {
|
||||
Type = "update_tts_user",
|
||||
Data = new Dictionary<string, string>() { { "@user", message.UserId }, { "@broadcaster", broadcasterId.ToString() }, { "@voice", voice.Key } }
|
||||
});
|
||||
_logger.LogInformation($"Updated {message.Username}'s (id: {message.UserId}) tts voice to {voice.Value} (id: {voice.Key}).");
|
||||
} else {
|
||||
await client.Send(3, new RequestMessage() {
|
||||
Type = "create_tts_user",
|
||||
Data = new Dictionary<string, string>() { { "@user", message.UserId }, { "@broadcaster", broadcasterId.ToString() }, { "@voice", voice.Key } }
|
||||
});
|
||||
_logger.LogInformation($"Added {message.Username}'s (id: {message.UserId}) tts voice as {voice.Value} (id: {voice.Key}).");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
public enum MessageResult {
|
||||
Skip = 1,
|
||||
SkipAll = 2,
|
||||
Blocked = 3,
|
||||
None = 0
|
||||
None = 0,
|
||||
NotReady = 1,
|
||||
Blocked = 2,
|
||||
Command = 3
|
||||
}
|
@ -24,7 +24,7 @@ public class AudioPlaybackEngine : IDisposable
|
||||
|
||||
private ISampleProvider ConvertToRightChannelCount(ISampleProvider? input)
|
||||
{
|
||||
if (input is null)
|
||||
if (input == null)
|
||||
throw new NullReferenceException(nameof(input));
|
||||
|
||||
if (input.WaveFormat.Channels == mixer.WaveFormat.Channels)
|
||||
|
@ -6,6 +6,8 @@ public class TTSPlayer {
|
||||
private Mutex _mutex;
|
||||
private Mutex _mutex2;
|
||||
|
||||
public ISampleProvider? Playing { get; set; }
|
||||
|
||||
public TTSPlayer() {
|
||||
_messages = new PriorityQueue<TTSMessage, int>();
|
||||
_buffer = new PriorityQueue<TTSMessage, int>();
|
||||
|
Reference in New Issue
Block a user