2024-03-12 14:05:27 -04:00
using System.Runtime.InteropServices ;
using System.Web ;
using Microsoft.Extensions.DependencyInjection ;
using Microsoft.Extensions.Hosting ;
2024-06-16 20:19:31 -04:00
using Serilog ;
2024-03-12 14:05:27 -04:00
using NAudio.Wave.SampleProviders ;
using TwitchLib.Client.Events ;
2024-06-24 18:11:36 -04:00
using TwitchChatTTS.Twitch.Redemptions ;
using org.mariuszgromada.math.mxparser ;
2024-07-12 13:36:09 -04:00
using TwitchChatTTS.Hermes.Socket ;
using TwitchChatTTS.Chat.Groups.Permissions ;
using TwitchChatTTS.Chat.Groups ;
2024-07-16 00:48:55 -04:00
using TwitchChatTTS.OBS.Socket.Manager ;
using TwitchChatTTS.Seven.Socket ;
using TwitchChatTTS.Chat.Emotes ;
using CommonSocketLibrary.Abstract ;
using CommonSocketLibrary.Common ;
2024-03-12 14:05:27 -04:00
namespace TwitchChatTTS
{
public class TTS : IHostedService
{
2024-06-16 20:19:31 -04:00
public const int MAJOR_VERSION = 3 ;
2024-07-16 00:48:55 -04:00
public const int MINOR_VERSION = 9 ;
2024-06-16 20:19:31 -04:00
2024-07-06 23:42:33 -04:00
private readonly User _user ;
private readonly HermesApiClient _hermesApiClient ;
private readonly SevenApiClient _sevenApiClient ;
2024-07-16 00:48:55 -04:00
private readonly OBSManager _obsManager ;
private readonly SevenManager _sevenManager ;
private readonly HermesSocketClient _hermes ;
2024-06-24 18:11:36 -04:00
private readonly RedemptionManager _redemptionManager ;
2024-07-12 13:36:09 -04:00
private readonly IChatterGroupManager _chatterGroupManager ;
private readonly IGroupPermissionManager _permissionManager ;
2024-03-15 08:27:35 -04:00
private readonly Configuration _configuration ;
private readonly TTSPlayer _player ;
private readonly IServiceProvider _serviceProvider ;
2024-06-24 18:11:36 -04:00
private readonly ILogger _logger ;
2024-03-12 14:05:27 -04:00
2024-06-24 18:11:36 -04:00
public TTS (
User user ,
HermesApiClient hermesApiClient ,
SevenApiClient sevenApiClient ,
2024-07-16 00:48:55 -04:00
OBSManager obsManager ,
SevenManager sevenManager ,
[FromKeyedServices("hermes")] SocketClient < WebSocketMessage > hermes ,
2024-06-24 18:11:36 -04:00
RedemptionManager redemptionManager ,
2024-07-12 13:36:09 -04:00
IChatterGroupManager chatterGroupManager ,
IGroupPermissionManager permissionManager ,
2024-06-24 18:11:36 -04:00
Configuration configuration ,
TTSPlayer player ,
IServiceProvider serviceProvider ,
ILogger logger
)
2024-06-16 20:19:31 -04:00
{
2024-07-06 23:42:33 -04:00
_user = user ;
_hermesApiClient = hermesApiClient ;
_sevenApiClient = sevenApiClient ;
2024-07-16 00:48:55 -04:00
_obsManager = obsManager ;
_sevenManager = sevenManager ;
_hermes = ( hermes as HermesSocketClient ) ! ;
2024-06-24 18:11:36 -04:00
_redemptionManager = redemptionManager ;
2024-07-12 13:36:09 -04:00
_chatterGroupManager = chatterGroupManager ;
_permissionManager = permissionManager ;
2024-03-15 08:27:35 -04:00
_configuration = configuration ;
_player = player ;
_serviceProvider = serviceProvider ;
2024-06-24 18:11:36 -04:00
_logger = logger ;
2024-03-12 14:05:27 -04:00
}
2024-06-16 20:19:31 -04:00
public async Task StartAsync ( CancellationToken cancellationToken )
{
2024-03-12 14:05:27 -04:00
Console . Title = "TTS - Twitch Chat" ;
2024-07-06 23:42:33 -04:00
License . iConfirmCommercialUse ( "abcdef" ) ;
2024-06-16 20:19:31 -04:00
2024-07-16 00:48:55 -04:00
if ( string . IsNullOrWhiteSpace ( _configuration . Hermes ? . Token ) )
2024-07-12 13:36:09 -04:00
{
2024-07-06 23:42:33 -04:00
_logger . Error ( "Hermes API token not set in the configuration file." ) ;
return ;
}
2024-03-15 08:27:35 -04:00
2024-07-12 13:36:09 -04:00
var hermesVersion = await _hermesApiClient . GetLatestTTSVersion ( ) ;
if ( hermesVersion = = null )
{
_logger . Warning ( "Failed to fetch latest TTS version. Skipping version check." ) ;
}
else if ( hermesVersion . MajorVersion > TTS . MAJOR_VERSION | | hermesVersion . MajorVersion = = TTS . MAJOR_VERSION & & hermesVersion . MinorVersion > TTS . MINOR_VERSION )
2024-06-16 20:19:31 -04:00
{
_logger . Information ( $"A new update for TTS is avaiable! Version {hermesVersion.MajorVersion}.{hermesVersion.MinorVersion} is available at {hermesVersion.Download}" ) ;
var changes = hermesVersion . Changelog . Split ( "\n" ) ;
if ( changes ! = null & & changes . Any ( ) )
_logger . Information ( "Changelogs:\n - " + string . Join ( "\n - " , changes ) + "\n\n" ) ;
await Task . Delay ( 15 * 1000 ) ;
}
2024-03-15 08:27:35 -04:00
2024-07-16 00:48:55 -04:00
await InitializeHermesWebsocket ( ) ;
2024-06-16 20:19:31 -04:00
try
{
2024-07-16 00:48:55 -04:00
await FetchUserData ( _user , _hermesApiClient ) ;
2024-06-16 20:19:31 -04:00
}
catch ( Exception ex )
{
2024-07-16 00:48:55 -04:00
_logger . Error ( ex , "Failed to initialize properly. Restart app please." ) ;
2024-06-16 20:19:31 -04:00
await Task . Delay ( 30 * 1000 ) ;
}
2024-03-15 08:27:35 -04:00
2024-07-06 23:42:33 -04:00
var twitchapiclient = await InitializeTwitchApiClient ( _user . TwitchUsername , _user . TwitchUserId . ToString ( ) ) ;
2024-06-16 20:19:31 -04:00
if ( twitchapiclient = = null )
{
await Task . Delay ( 30 * 1000 ) ;
return ;
}
2024-03-15 08:27:35 -04:00
2024-07-06 23:42:33 -04:00
var emoteSet = await _sevenApiClient . FetchChannelEmoteSet ( _user . TwitchUserId . ToString ( ) ) ;
2024-07-16 00:48:55 -04:00
if ( emoteSet ! = null )
_user . SevenEmoteSetId = emoteSet . Id ;
2024-07-12 13:36:09 -04:00
2024-07-06 23:42:33 -04:00
await InitializeEmotes ( _sevenApiClient , emoteSet ) ;
2024-07-12 13:36:09 -04:00
await InitializeSevenTv ( ) ;
2024-03-12 14:05:27 -04:00
await InitializeObs ( ) ;
2024-07-16 00:48:55 -04:00
// _logger.Information("Sending a request to server...");
// await _hermesManager.Send(3, new RequestMessage() {
// Type = "get_redeemable_actions",
// Data = new Dictionary<string, object>()
// });
// _logger.Warning("OS VERSION: " + Environment.OSVersion + " | " + Environment.OSVersion.Platform);
// return;
2024-06-16 20:19:31 -04:00
AudioPlaybackEngine . Instance . AddOnMixerInputEnded ( ( object? s , SampleProviderEventArgs e ) = >
{
if ( e . SampleProvider = = _player . Playing )
{
_player . Playing = null ;
}
} ) ;
Task . Run ( async ( ) = >
{
while ( true )
{
try
{
if ( cancellationToken . IsCancellationRequested )
{
2024-07-12 13:36:09 -04:00
_logger . Warning ( "TTS Buffer - Cancellation requested." ) ;
2024-06-16 20:19:31 -04:00
return ;
2024-03-12 14:05:27 -04:00
}
2024-06-16 20:19:31 -04:00
var m = _player . ReceiveBuffer ( ) ;
if ( m = = null )
{
await Task . Delay ( 200 ) ;
continue ;
}
string url = $"https://api.streamelements.com/kappa/v2/speech?voice={m.Voice}&text={HttpUtility.UrlEncode(m.Message)}" ;
var sound = new NetworkWavSound ( url ) ;
var provider = new CachedWavProvider ( sound ) ;
var data = AudioPlaybackEngine . Instance . ConvertSound ( provider ) ;
var resampled = new WdlResamplingSampleProvider ( data , AudioPlaybackEngine . Instance . SampleRate ) ;
2024-06-24 18:11:36 -04:00
_logger . Verbose ( "Fetched TTS audio data." ) ;
2024-06-16 20:19:31 -04:00
m . Audio = resampled ;
_player . Ready ( m ) ;
2024-03-12 14:05:27 -04:00
}
2024-06-16 20:19:31 -04:00
catch ( COMException e )
{
2024-06-24 18:11:36 -04:00
_logger . Error ( e , "Failed to send request for TTS [HResult: " + e . HResult + "]" ) ;
2024-06-16 20:19:31 -04:00
}
catch ( Exception e )
{
_logger . Error ( e , "Failed to send request for TTS." ) ;
}
}
} ) ;
2024-03-12 14:05:27 -04:00
2024-06-16 20:19:31 -04:00
Task . Run ( async ( ) = >
{
while ( true )
{
try
{
if ( cancellationToken . IsCancellationRequested )
{
2024-07-06 23:42:33 -04:00
_logger . Warning ( "TTS Queue - Cancellation requested." ) ;
2024-06-16 20:19:31 -04:00
return ;
2024-03-12 14:05:27 -04:00
}
2024-06-16 20:19:31 -04:00
while ( _player . IsEmpty ( ) | | _player . Playing ! = null )
{
await Task . Delay ( 200 ) ;
continue ;
}
var m = _player . ReceiveReady ( ) ;
if ( m = = null )
{
continue ;
}
if ( ! string . IsNullOrWhiteSpace ( m . File ) & & File . Exists ( m . File ) )
{
2024-07-06 23:42:33 -04:00
_logger . Debug ( "Playing audio file via TTS: " + m . File ) ;
2024-06-16 20:19:31 -04:00
AudioPlaybackEngine . Instance . PlaySound ( m . File ) ;
continue ;
}
2024-07-06 23:42:33 -04:00
_logger . Debug ( "Playing message via TTS: " + m . Message ) ;
2024-07-12 13:36:09 -04:00
2024-06-16 20:19:31 -04:00
if ( m . Audio ! = null )
2024-07-12 13:36:09 -04:00
{
_player . Playing = m . Audio ;
2024-06-16 20:19:31 -04:00
AudioPlaybackEngine . Instance . AddMixerInput ( m . Audio ) ;
2024-07-12 13:36:09 -04:00
}
2024-03-12 14:05:27 -04:00
}
2024-06-16 20:19:31 -04:00
catch ( Exception e )
{
_logger . Error ( e , "Failed to play a TTS audio message" ) ;
}
}
} ) ;
2024-03-12 14:05:27 -04:00
2024-06-16 20:19:31 -04:00
_logger . Information ( "Twitch websocket client connecting..." ) ;
await twitchapiclient . Connect ( ) ;
2024-03-12 14:05:27 -04:00
}
public async Task StopAsync ( CancellationToken cancellationToken )
{
if ( cancellationToken . IsCancellationRequested )
2024-06-16 20:19:31 -04:00
_logger . Warning ( "Application has stopped due to cancellation token." ) ;
2024-03-12 14:05:27 -04:00
else
2024-06-16 20:19:31 -04:00
_logger . Warning ( "Application has stopped." ) ;
2024-03-15 08:27:35 -04:00
}
2024-07-16 00:48:55 -04:00
private async Task FetchUserData ( User user , HermesApiClient hermes )
2024-06-16 20:19:31 -04:00
{
var hermesAccount = await hermes . FetchHermesAccountDetails ( ) ;
user . HermesUserId = hermesAccount . Id ;
user . HermesUsername = hermesAccount . Username ;
user . TwitchUsername = hermesAccount . Username ;
var twitchBotToken = await hermes . FetchTwitchBotToken ( ) ;
user . TwitchUserId = long . Parse ( twitchBotToken . BroadcasterId ) ;
2024-06-24 18:11:36 -04:00
_logger . Information ( $"Username: {user.TwitchUsername} [id: {user.TwitchUserId}]" ) ;
2024-06-16 20:19:31 -04:00
2024-07-16 00:48:55 -04:00
// user.DefaultTTSVoice = await hermes.FetchTTSDefaultVoice();
// _logger.Information("TTS Default Voice: " + user.DefaultTTSVoice);
2024-06-16 20:19:31 -04:00
2024-07-16 00:48:55 -04:00
// var wordFilters = await hermes.FetchTTSWordFilters();
// user.RegexFilters = wordFilters.ToList();
// _logger.Information($"{user.RegexFilters.Count()} TTS word filters.");
2024-06-16 20:19:31 -04:00
var voicesSelected = await hermes . FetchTTSChatterSelectedVoices ( ) ;
user . VoicesSelected = voicesSelected . ToDictionary ( s = > s . ChatterId , s = > s . Voice ) ;
2024-07-16 00:48:55 -04:00
_logger . Information ( $"{user.VoicesSelected.Count} chatters have selected a specific TTS voice, among {user.VoicesSelected.Values.Distinct().Count()} distinct TTS voices." ) ;
2024-06-16 20:19:31 -04:00
var voicesEnabled = await hermes . FetchTTSEnabledVoices ( ) ;
if ( voicesEnabled = = null | | ! voicesEnabled . Any ( ) )
2024-07-16 00:48:55 -04:00
user . VoicesEnabled = new HashSet < string > ( [ user . DefaultTTSVoice ] ) ;
2024-06-16 20:19:31 -04:00
else
user . VoicesEnabled = new HashSet < string > ( voicesEnabled . Select ( v = > v ) ) ;
_logger . Information ( $"{user.VoicesEnabled.Count} TTS voices have been enabled." ) ;
var defaultedChatters = voicesSelected . Where ( item = > item . Voice = = null | | ! user . VoicesEnabled . Contains ( item . Voice ) ) ;
2024-06-24 18:11:36 -04:00
if ( defaultedChatters . Any ( ) )
_logger . Information ( $"{defaultedChatters.Count()} chatter(s) will have their TTS voice set to default due to having selected a disabled TTS voice." ) ;
2024-07-16 00:48:55 -04:00
// var redemptionActions = await hermes.FetchRedeemableActions();
// var redemptions = await hermes.FetchRedemptions();
// _redemptionManager.Initialize(redemptions, redemptionActions.ToDictionary(a => a.Name, a => a));
// _logger.Information($"Redemption Manager has been initialized with {redemptionActions.Count()} actions & {redemptions.Count()} redemptions.");
2024-07-12 13:36:09 -04:00
var groups = await hermes . FetchGroups ( ) ;
var groupsById = groups . ToDictionary ( g = > g . Id , g = > g ) ;
foreach ( var group in groups )
_chatterGroupManager . Add ( group ) ;
_logger . Information ( $"{groups.Count()} groups have been loaded." ) ;
var groupChatters = await hermes . FetchGroupChatters ( ) ;
_logger . Debug ( $"{groupChatters.Count()} group users have been fetched." ) ;
var permissions = await hermes . FetchGroupPermissions ( ) ;
foreach ( var permission in permissions )
{
_logger . Debug ( $"Adding group permission [permission id: {permission.Id}][group id: {permission.GroupId}][path: {permission.Path}][allow: {permission.Allow?.ToString() ?? " null "}]" ) ;
if ( ! groupsById . TryGetValue ( permission . GroupId , out var group ) )
{
_logger . Warning ( $"Failed to find group by id [permission id: {permission.Id}][group id: {permission.GroupId}][path: {permission.Path}]" ) ;
continue ;
}
var path = $"{group.Name}.{permission.Path}" ;
_permissionManager . Set ( path , permission . Allow ) ;
_logger . Debug ( $"Added group permission [id: {permission.Id}][group id: {permission.GroupId}][path: {permission.Path}]" ) ;
}
_logger . Information ( $"{permissions.Count()} group permissions have been loaded." ) ;
foreach ( var chatter in groupChatters )
if ( groupsById . TryGetValue ( chatter . GroupId , out var group ) )
_chatterGroupManager . Add ( chatter . ChatterId , group . Name ) ;
_logger . Information ( $"Users in each group have been loaded." ) ;
2024-06-16 20:19:31 -04:00
}
private async Task InitializeHermesWebsocket ( )
{
try
{
2024-07-16 00:48:55 -04:00
_hermes . Initialize ( ) ;
await _hermes . Connect ( ) ;
2024-03-15 08:27:35 -04:00
}
2024-07-16 00:48:55 -04:00
catch ( Exception e )
2024-06-16 20:19:31 -04:00
{
2024-07-16 00:48:55 -04:00
_logger . Error ( e , "Connecting to hermes failed. Skipping hermes websockets." ) ;
2024-03-15 08:27:35 -04:00
}
2024-06-16 20:19:31 -04:00
}
2024-03-15 08:27:35 -04:00
2024-07-12 13:36:09 -04:00
private async Task InitializeSevenTv ( )
2024-06-16 20:19:31 -04:00
{
try
{
2024-07-16 00:48:55 -04:00
_sevenManager . Initialize ( ) ;
await _sevenManager . Connect ( ) ;
2024-06-16 20:19:31 -04:00
}
2024-07-16 00:48:55 -04:00
catch ( Exception e )
2024-06-16 20:19:31 -04:00
{
2024-07-16 00:48:55 -04:00
_logger . Error ( e , "Connecting to 7tv failed. Skipping 7tv websockets." ) ;
2024-03-12 14:05:27 -04:00
}
}
2024-06-16 20:19:31 -04:00
private async Task InitializeObs ( )
{
try
{
2024-07-16 00:48:55 -04:00
_obsManager . Initialize ( ) ;
await _obsManager . Connect ( ) ;
2024-06-16 20:19:31 -04:00
}
catch ( Exception )
{
_logger . Warning ( "Connecting to obs failed. Skipping obs websockets." ) ;
2024-03-12 14:05:27 -04:00
}
}
2024-06-16 20:19:31 -04:00
private async Task < TwitchApiClient ? > InitializeTwitchApiClient ( string username , string broadcasterId )
{
_logger . Debug ( "Initializing twitch client." ) ;
2024-03-15 08:27:35 -04:00
var twitchapiclient = _serviceProvider . GetRequiredService < TwitchApiClient > ( ) ;
2024-06-16 20:19:31 -04:00
if ( ! await twitchapiclient . Authorize ( broadcasterId ) )
{
_logger . Error ( "Cannot connect to Twitch API." ) ;
return null ;
}
2024-03-12 14:05:27 -04:00
2024-07-16 00:48:55 -04:00
var channels = _configuration . Twitch ? . Channels ? ? [ username ] ;
2024-06-16 20:19:31 -04:00
_logger . Information ( "Twitch channels: " + string . Join ( ", " , channels ) ) ;
2024-03-15 08:27:35 -04:00
twitchapiclient . InitializeClient ( username , channels ) ;
2024-03-12 14:05:27 -04:00
twitchapiclient . InitializePublisher ( ) ;
2024-03-15 08:27:35 -04:00
var handler = _serviceProvider . GetRequiredService < ChatMessageHandler > ( ) ;
2024-06-16 20:19:31 -04:00
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 ;
2024-07-16 00:48:55 -04:00
await _hermes . SendEmoteUsage ( e . ChatMessage . Id , result . ChatterId , result . Emotes ) ;
2024-06-16 20:19:31 -04:00
}
catch ( Exception ex )
{
2024-07-12 13:36:09 -04:00
_logger . Error ( ex , "Unable to either execute a command or to send emote usage message." ) ;
2024-06-16 20:19:31 -04:00
}
2024-03-12 14:05:27 -04:00
} ) ;
return twitchapiclient ;
}
2024-06-16 20:19:31 -04:00
2024-07-16 00:48:55 -04:00
private async Task InitializeEmotes ( SevenApiClient sevenapi , EmoteSet ? channelEmotes )
2024-06-16 20:19:31 -04:00
{
2024-07-16 00:48:55 -04:00
var emotes = _serviceProvider . GetRequiredService < IEmoteDatabase > ( ) ;
2024-06-16 20:19:31 -04:00
var globalEmotes = await sevenapi . FetchGlobalSevenEmotes ( ) ;
if ( channelEmotes ! = null & & channelEmotes . Emotes . Any ( ) )
{
_logger . Information ( $"Loaded {channelEmotes.Emotes.Count()} 7tv channel emotes." ) ;
foreach ( var entry in channelEmotes . Emotes )
emotes . Add ( entry . Name , entry . Id ) ;
}
if ( globalEmotes ! = null & & globalEmotes . Any ( ) )
{
_logger . Information ( $"Loaded {globalEmotes.Count()} 7tv global emotes." ) ;
foreach ( var entry in globalEmotes )
emotes . Add ( entry . Name , entry . Id ) ;
}
2024-03-12 14:05:27 -04:00
}
}
}