2024-12-02 15:51:04 -05:00
using CommonSocketLibrary.Abstract ;
using Microsoft.Extensions.DependencyInjection ;
using Serilog ;
using System.Text.Json ;
using CommonSocketLibrary.Backoff ;
using System.Text ;
using System.Net.WebSockets ;
2024-12-02 21:39:27 -05:00
using TwitchChatTTS.Veadotube.Handlers ;
2024-12-02 15:51:04 -05:00
namespace TwitchChatTTS.Veadotube
{
public class VeadoSocketClient : SocketClient < object >
{
private VeadoInstanceInfo ? Instance ;
2024-12-02 21:39:27 -05:00
private IDictionary < string , IVeadotubeMessageHandler > _handlers ;
private IDictionary < string , string > _states ;
2024-12-02 15:51:04 -05:00
public bool Connected { get ; set ; }
public bool Identified { get ; set ; }
public bool Streaming { get ; set ; }
public VeadoSocketClient (
2024-12-02 21:39:27 -05:00
[FromKeyedServices("veadotube")] IEnumerable < IVeadotubeMessageHandler > handlers ,
//[FromKeyedServices("veadotube")] MessageTypeManager<IVeadotubeMessageHandler> typeManager,
2024-12-02 15:51:04 -05:00
ILogger logger
) : base ( logger , new JsonSerializerOptions ( )
{
PropertyNameCaseInsensitive = false ,
PropertyNamingPolicy = JsonNamingPolicy . CamelCase
} )
{
2024-12-02 21:39:27 -05:00
_handlers = handlers . ToDictionary ( h = > h . Name , h = > h ) ;
_states = new Dictionary < string , string > ( ) ;
2024-12-02 15:51:04 -05:00
}
protected override async Task < T > Deserialize < T > ( Stream stream )
{
using StreamReader reader = new StreamReader ( stream ) ;
string content = await reader . ReadToEndAsync ( ) ;
int index = content . IndexOf ( ':' ) ;
string json = content . Substring ( index + 1 ) . Replace ( "\0" , string . Empty ) ;
T ? value = JsonSerializer . Deserialize < T > ( json , _options ) ;
return value ! ;
}
public void Initialize ( )
{
_logger . Information ( $"Initializing Veadotube websocket client." ) ;
2024-12-02 21:39:27 -05:00
OnConnected + = async ( sender , e ) = >
2024-12-02 15:51:04 -05:00
{
Connected = true ;
_logger . Information ( "Veadotube websocket client connected." ) ;
2024-12-02 21:39:27 -05:00
await FetchStates ( ) ;
2024-12-02 15:51:04 -05:00
} ;
OnDisconnected + = async ( sender , e ) = >
{
_logger . Information ( $"Veadotube websocket client disconnected [status: {e.Status}][reason: {e.Reason}] " + ( Identified ? "Will be attempting to reconnect every 30 seconds." : "Will not be attempting to reconnect." ) ) ;
Connected = false ;
Identified = false ;
Streaming = false ;
await Reconnect ( new ExponentialBackoff ( 5000 , 300000 ) ) ;
} ;
}
public override async Task Connect ( )
{
if ( ! UpdateURL ( ) | | string . IsNullOrEmpty ( Instance ? . Server ) | | string . IsNullOrEmpty ( Instance . Name ) )
{
_logger . Warning ( "Lacking connection info for Veadotube websockets. Not connecting to Veadotube." ) ;
return ;
}
string url = $"ws://{Instance.Server}?n={Instance.Name}" ;
_logger . Debug ( $"Veadotube websocket client attempting to connect to {url}" ) ;
try
{
await ConnectAsync ( url ) ;
}
catch ( Exception )
{
_logger . Warning ( "Connecting to Veadotube failed. Skipping Veadotube websockets." ) ;
}
}
public async Task FetchStates ( )
{
await Send ( new VeadoPayloadMessage ( )
{
Event = "payload" ,
Type = "stateEvents" ,
Id = "mini" ,
Payload = new VeadoEventMessage ( )
{
Event = "list" ,
}
} ) ;
}
2024-12-02 21:39:27 -05:00
public string? GetStateId ( string state )
{
if ( _states . TryGetValue ( state , out var id ) )
return id ;
return null ;
}
2024-12-02 15:51:04 -05:00
public async Task SetCurrentState ( string stateId )
{
await Send ( new VeadoPayloadMessage ( )
{
Event = "payload" ,
Type = "stateEvents" ,
Id = "mini" ,
Payload = new VeadoNodeStateMessage ( )
{
Event = "set" ,
State = stateId
}
} ) ;
}
public async Task PushState ( string stateId )
{
await Send ( new VeadoPayloadMessage ( )
{
Event = "payload" ,
Type = "stateEvents" ,
Id = "mini" ,
Payload = new VeadoNodeStateMessage ( )
{
Event = "push" ,
State = stateId
}
} ) ;
}
public async Task PopState ( string stateId )
{
await Send ( new VeadoPayloadMessage ( )
{
Event = "payload" ,
Type = "stateEvents" ,
Id = "mini" ,
Payload = new VeadoNodeStateMessage ( )
{
Event = "pop" ,
State = stateId
}
} ) ;
}
private async Task Send < T > ( T data )
{
if ( _socket = = null | | data = = null )
return ;
if ( ! Connected )
{
_logger . Debug ( "Not sending Veadotube message due to no connection." ) ;
return ;
}
try
{
var content = "nodes:" + JsonSerializer . Serialize ( data , _options ) ;
var bytes = Encoding . UTF8 . GetBytes ( content ) ;
var array = new ArraySegment < byte > ( bytes ) ;
var total = bytes . Length ;
var current = 0 ;
while ( current < total )
{
var size = Encoding . UTF8 . GetBytes ( content . Substring ( current ) , array ) ;
await _socket . SendAsync ( array , WebSocketMessageType . Text , current + size > = total , _cts ! . Token ) ;
current + = size ;
}
_logger . Debug ( $"Veado TX [message type: {typeof(T).Name}]: " + content ) ;
}
catch ( Exception e )
{
if ( _socket . State . ToString ( ) . Contains ( "Close" ) | | _socket . State = = WebSocketState . Aborted )
{
await DisconnectAsync ( new SocketDisconnectionEventArgs ( _socket . CloseStatus . ToString ( ) ! , _socket . CloseStatusDescription ? ? string . Empty ) ) ;
_logger . Warning ( $"Socket state on closing = {_socket.State} | {_socket.CloseStatus?.ToString()} | {_socket.CloseStatusDescription}" ) ;
}
_logger . Error ( e , $"Failed to send a websocket message to Veado [message type: {typeof(T).Name}]" ) ;
}
}
2024-12-02 21:39:27 -05:00
public void UpdateState ( IDictionary < string , string > states )
{
_states = states ;
}
2024-12-02 15:51:04 -05:00
private bool UpdateURL ( )
{
string path = Environment . ExpandEnvironmentVariables ( "%userprofile%/.veadotube/instances" ) ;
try
{
if ( Directory . Exists ( path ) )
{
var directory = Directory . CreateDirectory ( path ) ;
var files = directory . GetFiles ( )
. Where ( f = > f . Name . StartsWith ( "mini-" ) )
. OrderByDescending ( f = > f . CreationTime ) ;
if ( files . Any ( ) )
{
_logger . Debug ( "Veadotube's instance file exists: " + files . First ( ) . FullName ) ;
var data = File . ReadAllText ( files . First ( ) . FullName ) ;
var instance = JsonSerializer . Deserialize < VeadoInstanceInfo > ( data ) ;
if ( instance ! = null )
{
Instance = instance ;
return true ;
}
}
}
}
catch ( Exception ex )
{
_logger . Error ( ex , "Failed to find Veadotube instance information." ) ;
}
return false ;
}
2024-12-02 21:39:27 -05:00
protected override async Task OnResponseReceived ( object? content )
2024-12-02 15:51:04 -05:00
{
2024-12-02 21:39:27 -05:00
var contentAsString = JsonSerializer . Serialize ( content , _options ) ;
2024-12-02 15:51:04 -05:00
_logger . Debug ( "VEADO RX: " + contentAsString ) ;
2024-12-02 21:39:27 -05:00
var data = JsonSerializer . Deserialize < VeadoPayloadMessage > ( contentAsString , _options ) ;
if ( data = = null )
{
return ;
}
var payload = JsonSerializer . Deserialize < VeadoEventMessage > ( data . Payload . ToString ( ) ! , _options ) ;
if ( _handlers . TryGetValue ( payload ? . Event ? ? string . Empty , out var handler ) )
{
await handler . Handle ( this , data ) ;
}
return ;
2024-12-02 15:51:04 -05:00
}
}
}