2024-06-24 18:11:36 -04:00
using System.Reflection ;
2024-07-06 23:42:33 -04:00
using CommonSocketLibrary.Abstract ;
using CommonSocketLibrary.Common ;
using Microsoft.Extensions.DependencyInjection ;
2024-06-24 18:11:36 -04:00
using org.mariuszgromada.math.mxparser ;
using Serilog ;
using TwitchChatTTS.OBS.Socket.Data ;
using TwitchChatTTS.OBS.Socket.Manager ;
namespace TwitchChatTTS.Twitch.Redemptions
{
public class RedemptionManager
{
private readonly IDictionary < string , IList < RedeemableAction > > _store ;
2024-07-06 23:42:33 -04:00
private readonly User _user ;
2024-06-24 18:11:36 -04:00
private readonly OBSManager _obsManager ;
2024-07-06 23:42:33 -04:00
private readonly SocketClient < WebSocketMessage > _hermesClient ;
2024-06-24 18:11:36 -04:00
private readonly ILogger _logger ;
2024-07-06 23:42:33 -04:00
private readonly Random _random ;
2024-06-24 18:11:36 -04:00
private bool _isReady ;
2024-07-06 23:42:33 -04:00
public RedemptionManager (
User user ,
OBSManager obsManager ,
[FromKeyedServices("hermes")] SocketClient < WebSocketMessage > hermesClient ,
ILogger logger )
2024-06-24 18:11:36 -04:00
{
_store = new Dictionary < string , IList < RedeemableAction > > ( ) ;
2024-07-06 23:42:33 -04:00
_user = user ;
2024-06-24 18:11:36 -04:00
_obsManager = obsManager ;
2024-07-06 23:42:33 -04:00
_hermesClient = hermesClient ;
2024-06-24 18:11:36 -04:00
_logger = logger ;
2024-07-06 23:42:33 -04:00
_random = new Random ( ) ;
2024-06-24 18:11:36 -04:00
_isReady = false ;
}
private void Add ( string twitchRedemptionId , RedeemableAction action )
{
if ( ! _store . TryGetValue ( twitchRedemptionId , out var actions ) )
_store . Add ( twitchRedemptionId , actions = new List < RedeemableAction > ( ) ) ;
2024-07-06 23:42:33 -04:00
2024-06-24 18:11:36 -04:00
actions . Add ( action ) ;
2024-07-06 23:42:33 -04:00
_logger . Debug ( $"Added redemption action [name: {action.Name}][type: {action.Type}]" ) ;
2024-06-24 18:11:36 -04:00
}
2024-07-06 23:42:33 -04:00
public async Task Execute ( RedeemableAction action , string senderDisplayName , long senderId )
2024-06-24 18:11:36 -04:00
{
try
{
switch ( action . Type )
{
case "WRITE_TO_FILE" :
Directory . CreateDirectory ( Path . GetDirectoryName ( action . Data [ "file_path" ] ) ) ;
2024-07-06 23:42:33 -04:00
await File . WriteAllTextAsync ( action . Data [ "file_path" ] , ReplaceContentText ( action . Data [ "file_content" ] , senderDisplayName ) ) ;
2024-06-24 18:11:36 -04:00
_logger . Debug ( $"Overwritten text to file [file: {action.Data[" file_path "]}]" ) ;
break ;
case "APPEND_TO_FILE" :
Directory . CreateDirectory ( Path . GetDirectoryName ( action . Data [ "file_path" ] ) ) ;
2024-07-06 23:42:33 -04:00
await File . AppendAllTextAsync ( action . Data [ "file_path" ] , ReplaceContentText ( action . Data [ "file_content" ] , senderDisplayName ) ) ;
2024-06-24 18:11:36 -04:00
_logger . Debug ( $"Appended text to file [file: {action.Data[" file_path "]}]" ) ;
break ;
case "OBS_TRANSFORM" :
var type = typeof ( OBSTransformationData ) ;
await _obsManager . UpdateTransformation ( action . Data [ "scene_name" ] , action . Data [ "scene_item_name" ] , ( d ) = >
{
string [ ] properties = [ "rotation" , "position_x" , "position_y" ] ;
foreach ( var property in properties )
{
if ( ! action . Data . TryGetValue ( property , out var expressionString ) | | expressionString = = null )
continue ;
var propertyName = string . Join ( "" , property . Split ( '_' ) . Select ( p = > char . ToUpper ( p [ 0 ] ) + p . Substring ( 1 ) ) ) ;
PropertyInfo ? prop = type . GetProperty ( propertyName , BindingFlags . Public | BindingFlags . Instance ) ;
if ( prop = = null )
{
_logger . Warning ( $"Failed to find property for OBS transformation [scene: {action.Data[" scene_name "]}][source: {action.Data[" scene_item_name "]}][property: {propertyName}]" ) ;
continue ;
}
var currentValue = prop . GetValue ( d ) ;
if ( currentValue = = null )
{
_logger . Warning ( $"Found a null value from OBS transformation [scene: {action.Data[" scene_name "]}][source: {action.Data[" scene_item_name "]}][property: {propertyName}]" ) ;
}
Expression expression = new Expression ( expressionString ) ;
expression . addConstants ( new Constant ( "x" , ( double? ) currentValue ? ? 0.0d ) ) ;
if ( ! expression . checkSyntax ( ) )
{
_logger . Warning ( $"Could not parse math expression for OBS transformation [scene: {action.Data[" scene_name "]}][source: {action.Data[" scene_item_name "]}][expression: {expressionString}][property: {propertyName}]" ) ;
continue ;
}
var newValue = expression . calculate ( ) ;
prop . SetValue ( d , newValue ) ;
_logger . Debug ( $"OBS transformation [scene: {action.Data[" scene_name "]}][source: {action.Data[" scene_item_name "]}][property: {propertyName}][old value: {currentValue}][new value: {newValue}][expression: {expressionString}]" ) ;
}
_logger . Debug ( $"Finished applying the OBS transformation property changes [scene: {action.Data[" scene_name "]}][source: {action.Data[" scene_item_name "]}]" ) ;
} ) ;
break ;
2024-07-06 23:42:33 -04:00
case "TOGGLE_OBS_VISIBILITY" :
await _obsManager . ToggleSceneItemVisibility ( action . Data [ "scene_name" ] , action . Data [ "scene_item_name" ] ) ;
break ;
case "SPECIFIC_OBS_VISIBILITY" :
await _obsManager . UpdateSceneItemVisibility ( action . Data [ "scene_name" ] , action . Data [ "scene_item_name" ] , action . Data [ "obs_visible" ] . ToLower ( ) = = "true" ) ;
break ;
case "SPECIFIC_OBS_INDEX" :
await _obsManager . UpdateSceneItemIndex ( action . Data [ "scene_name" ] , action . Data [ "scene_item_name" ] , int . Parse ( action . Data [ "obs_index" ] ) ) ;
break ;
case "SLEEP" :
_logger . Debug ( "Sleeping on thread due to redemption for OBS." ) ;
await Task . Delay ( int . Parse ( action . Data [ "sleep" ] ) ) ;
break ;
case "SPECIFIC_TTS_VOICE" :
var voiceId = _user . VoicesAvailable . Keys . First ( id = > _user . VoicesAvailable [ id ] . ToLower ( ) = = action . Data [ "tts_voice" ] . ToLower ( ) ) ;
if ( voiceId = = null )
{
_logger . Warning ( $"Voice specified is not valid [voice: {action.Data[" tts_voice "]}]" ) ;
return ;
}
var voiceName = _user . VoicesAvailable [ voiceId ] ;
if ( ! _user . VoicesEnabled . Contains ( voiceName ) )
{
_logger . Warning ( $"Voice specified is not enabled [voice: {action.Data[" tts_voice "]}][voice id: {voiceId}]" ) ;
return ;
}
await _hermesClient . Send ( 3 , new HermesSocketLibrary . Socket . Data . RequestMessage ( )
{
Type = _user . VoicesSelected . ContainsKey ( senderId ) ? "update_tts_user" : "create_tts_user" ,
Data = new Dictionary < string , object > ( ) { { "chatter" , senderId } , { "voice" , voiceId } }
} ) ;
_logger . Debug ( $"Changed the TTS voice of a chatter [voice: {action.Data[" tts_voice "]}][display name: {senderDisplayName}][chatter id: {senderId}]" ) ;
break ;
case "RANDOM_TTS_VOICE" :
var voicesEnabled = _user . VoicesEnabled . ToList ( ) ;
if ( ! voicesEnabled . Any ( ) )
{
_logger . Warning ( $"There are no TTS voices enabled [voice pool size: {voicesEnabled.Count}]" ) ;
return ;
}
if ( voicesEnabled . Count < = 1 )
{
_logger . Warning ( $"There are not enough TTS voices enabled to randomize [voice pool size: {voicesEnabled.Count}]" ) ;
return ;
}
var randomVoice = voicesEnabled [ _random . Next ( voicesEnabled . Count ) ] ;
var randomVoiceId = _user . VoicesAvailable . Keys . First ( id = > _user . VoicesAvailable [ id ] = = randomVoice ) ;
await _hermesClient . Send ( 3 , new HermesSocketLibrary . Socket . Data . RequestMessage ( )
{
Type = _user . VoicesSelected . ContainsKey ( senderId ) ? "update_tts_user" : "create_tts_user" ,
Data = new Dictionary < string , object > ( ) { { "chatter" , senderId } , { "voice" , randomVoiceId } }
} ) ;
_logger . Debug ( $"Randomly changed the TTS voice of a chatter [voice: {randomVoice}][display name: {senderDisplayName}][chatter id: {senderId}]" ) ;
break ;
2024-06-24 18:11:36 -04:00
case "AUDIO_FILE" :
2024-07-06 23:42:33 -04:00
if ( ! File . Exists ( action . Data [ "file_path" ] ) )
{
2024-06-24 18:11:36 -04:00
_logger . Warning ( $"Cannot find audio file for Twitch channel point redeem [file: {action.Data[" file_path "]}]" ) ;
return ;
}
AudioPlaybackEngine . Instance . PlaySound ( action . Data [ "file_path" ] ) ;
_logger . Debug ( $"Played an audio file for channel point redeem [file: {action.Data[" file_path "]}]" ) ;
break ;
default :
2024-07-06 23:42:33 -04:00
_logger . Warning ( $"Unknown redeemable action has occured. Update needed? [type: {action.Type}]" ) ;
2024-06-24 18:11:36 -04:00
break ;
}
}
catch ( Exception ex )
{
_logger . Error ( ex , "Failed to execute a redemption action." ) ;
}
}
public IList < RedeemableAction > Get ( string twitchRedemptionId )
{
if ( ! _isReady )
throw new InvalidOperationException ( "Not ready" ) ;
if ( _store . TryGetValue ( twitchRedemptionId , out var actions ) )
return actions ;
return new List < RedeemableAction > ( 0 ) ;
}
2024-07-06 23:42:33 -04:00
public void Initialize ( IEnumerable < Redemption > redemptions , IDictionary < string , RedeemableAction > actions )
2024-06-24 18:11:36 -04:00
{
_store . Clear ( ) ;
2024-07-06 23:42:33 -04:00
var ordered = redemptions . OrderBy ( r = > r . Order ) ;
2024-06-24 18:11:36 -04:00
foreach ( var redemption in ordered )
2024-07-06 23:42:33 -04:00
{
try
{
if ( actions . TryGetValue ( redemption . ActionName , out var action ) & & action ! = null )
{
_logger . Debug ( $"Fetched a redemption action [redemption id: {redemption.Id}][redemption action: {redemption.ActionName}][order: {redemption.Order}]" ) ;
Add ( redemption . RedemptionId , action ) ;
}
else
_logger . Warning ( $"Could not find redemption action [redemption id: {redemption.Id}][redemption action: {redemption.ActionName}][order: {redemption.Order}]" ) ;
}
catch ( Exception e )
{
_logger . Error ( e , $"Failed to add a redemption [redemption id: {redemption.Id}][redemption action: {redemption.ActionName}][order: {redemption.Order}]" ) ;
}
}
2024-06-24 18:11:36 -04:00
_isReady = true ;
2024-07-06 23:42:33 -04:00
_logger . Debug ( "All redemptions added. Redemption Manager is ready." ) ;
2024-06-24 18:11:36 -04:00
}
2024-07-06 23:42:33 -04:00
private string ReplaceContentText ( string content , string username )
{
return content . Replace ( "%USER%" , username )
. Replace ( "\\n" , "\n" ) ;
2024-06-24 18:11:36 -04:00
}
}
}