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 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.Seven.Socket ;
using TwitchChatTTS.Chat.Emotes ;
using CommonSocketLibrary.Abstract ;
using CommonSocketLibrary.Common ;
2024-07-19 12:56:41 -04:00
using TwitchChatTTS.OBS.Socket ;
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-19 12:56:41 -04:00
public const int MINOR_VERSION = 10 ;
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-19 12:56:41 -04:00
private readonly OBSSocketClient _obs ;
private readonly SevenSocketClient _seven ;
2024-07-16 00:48:55 -04:00
private readonly HermesSocketClient _hermes ;
2024-07-19 12:56:41 -04:00
private readonly IEmoteDatabase _emotes ;
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
[FromKeyedServices("hermes")] SocketClient < WebSocketMessage > hermes ,
2024-07-19 12:56:41 -04:00
[FromKeyedServices("obs")] SocketClient < WebSocketMessage > obs ,
[FromKeyedServices("7tv")] SocketClient < WebSocketMessage > seven ,
IEmoteDatabase emotes ,
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
_hermes = ( hermes as HermesSocketClient ) ! ;
2024-07-19 12:56:41 -04:00
_obs = ( obs as OBSSocketClient ) ! ;
_seven = ( seven as SevenSocketClient ) ! ;
_emotes = emotes ;
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-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 ( ) ;
2024-07-19 12:56:41 -04:00
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
}
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-19 12:56:41 -04:00
_seven . Initialize ( ) ;
await _seven . 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-19 12:56:41 -04:00
_obs . Initialize ( ) ;
await _obs . 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
{
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 )
2024-07-19 12:56:41 -04:00
_emotes . Add ( entry . Name , entry . Id ) ;
2024-06-16 20:19:31 -04:00
}
if ( globalEmotes ! = null & & globalEmotes . Any ( ) )
{
_logger . Information ( $"Loaded {globalEmotes.Count()} 7tv global emotes." ) ;
foreach ( var entry in globalEmotes )
2024-07-19 12:56:41 -04:00
_emotes . Add ( entry . Name , entry . Id ) ;
2024-06-16 20:19:31 -04:00
}
2024-03-12 14:05:27 -04:00
}
}
}