2024-03-12 14:05:27 -04:00
using System.Text.Json ;
using TwitchChatTTS.Helpers ;
2024-06-16 20:19:31 -04:00
using Serilog ;
2024-03-12 14:05:27 -04:00
using TwitchChatTTS ;
using TwitchLib.Api.Core.Exceptions ;
using TwitchLib.Client.Events ;
using TwitchLib.Client.Models ;
using TwitchLib.Communication.Events ;
2024-03-15 08:27:35 -04:00
using TwitchLib.PubSub.Interfaces ;
using TwitchLib.Client.Interfaces ;
2024-06-24 18:11:36 -04:00
using TwitchChatTTS.Twitch.Redemptions ;
2024-03-12 14:05:27 -04:00
2024-06-16 20:19:31 -04:00
public class TwitchApiClient
{
2024-06-24 18:11:36 -04:00
private readonly RedemptionManager _redemptionManager ;
private readonly HermesApiClient _hermesApiClient ;
2024-03-15 08:27:35 -04:00
private readonly ITwitchClient _client ;
private readonly ITwitchPubSub _publisher ;
2024-07-16 00:48:55 -04:00
private readonly User _user ;
private readonly Configuration _configuration ;
private readonly TwitchBotAuth _token ;
2024-06-24 18:11:36 -04:00
private readonly ILogger _logger ;
2024-07-16 00:48:55 -04:00
private readonly WebClientWrap _web ;
2024-06-16 20:19:31 -04:00
private bool _initialized ;
private string _broadcasterId ;
2024-03-15 08:27:35 -04:00
public TwitchApiClient (
2024-07-16 00:48:55 -04:00
ITwitchClient twitchClient ,
ITwitchPubSub twitchPublisher ,
2024-06-24 18:11:36 -04:00
RedemptionManager redemptionManager ,
HermesApiClient hermesApiClient ,
2024-07-16 00:48:55 -04:00
User user ,
2024-03-15 08:27:35 -04:00
Configuration configuration ,
2024-06-16 20:19:31 -04:00
TwitchBotAuth token ,
ILogger logger
)
{
2024-06-24 18:11:36 -04:00
_redemptionManager = redemptionManager ;
_hermesApiClient = hermesApiClient ;
2024-03-15 08:27:35 -04:00
_client = twitchClient ;
_publisher = twitchPublisher ;
2024-07-16 00:48:55 -04:00
_user = user ;
_configuration = configuration ;
_token = token ;
2024-06-16 20:19:31 -04:00
_logger = logger ;
_initialized = false ;
2024-06-24 18:11:36 -04:00
_broadcasterId = string . Empty ;
2024-03-12 14:05:27 -04:00
2024-06-16 20:19:31 -04:00
_web = new WebClientWrap ( new JsonSerializerOptions ( )
{
2024-03-12 14:05:27 -04:00
PropertyNameCaseInsensitive = false ,
PropertyNamingPolicy = JsonNamingPolicy . SnakeCaseLower
} ) ;
2024-03-15 08:27:35 -04:00
if ( ! string . IsNullOrWhiteSpace ( _configuration . Hermes ? . Token ) )
2024-06-16 20:19:31 -04:00
_web . AddHeader ( "x-api-key" , _configuration . Hermes . Token . Trim ( ) ) ;
2024-03-12 14:05:27 -04:00
}
2024-06-16 20:19:31 -04:00
public async Task < bool > Authorize ( string broadcasterId )
{
try
{
2024-07-06 23:42:33 -04:00
_logger . Debug ( $"Attempting to authorize Twitch API [id: {broadcasterId}]" ) ;
2024-07-12 13:36:09 -04:00
var authorize = await _web . GetJson < TwitchBotAuth > ( $"https://{HermesApiClient.BASE_URL}/api/account/reauthorize" ) ;
2024-06-16 20:19:31 -04:00
if ( authorize ! = null & & broadcasterId = = authorize . BroadcasterId )
{
2024-03-15 08:27:35 -04:00
_token . AccessToken = authorize . AccessToken ;
_token . RefreshToken = authorize . RefreshToken ;
2024-06-16 20:19:31 -04:00
_token . UserId = authorize . UserId ;
_token . BroadcasterId = authorize . BroadcasterId ;
2024-07-06 23:42:33 -04:00
_token . ExpiresIn = authorize . ExpiresIn ;
_token . UpdatedAt = DateTime . Now ;
2024-06-16 20:19:31 -04:00
_logger . Information ( "Updated Twitch API tokens." ) ;
2024-07-06 23:42:33 -04:00
_logger . Debug ( $"Twitch API Auth data [user id: {_token.UserId}][id: {_token.BroadcasterId}][expires in: {_token.ExpiresIn}][expires at: {_token.ExpiresAt.ToShortTimeString()}]" ) ;
2024-06-16 20:19:31 -04:00
}
else if ( authorize ! = null )
{
_logger . Error ( "Twitch API Authorization failed: " + authorize . AccessToken + " | " + authorize . RefreshToken + " | " + authorize . UserId + " | " + authorize . BroadcasterId ) ;
return false ;
2024-03-12 14:05:27 -04:00
}
2024-06-16 20:19:31 -04:00
_broadcasterId = broadcasterId ;
2024-07-06 23:42:33 -04:00
_logger . Debug ( $"Authorized Twitch API [id: {broadcasterId}]" ) ;
2024-06-16 20:19:31 -04:00
return true ;
}
catch ( HttpResponseException e )
{
2024-07-16 00:48:55 -04:00
if ( string . IsNullOrWhiteSpace ( _configuration . Hermes ! . Token ) )
2024-06-16 20:19:31 -04:00
_logger . Error ( "No Hermes API key found. Enter it into the configuration file." ) ;
2024-03-12 14:05:27 -04:00
else
2024-06-16 20:19:31 -04:00
_logger . Error ( "Invalid Hermes API key. Double check the token. HTTP Error Code: " + e . HttpResponse . StatusCode ) ;
}
catch ( JsonException )
{
2024-07-06 23:42:33 -04:00
_logger . Debug ( $"Failed to Authorize Twitch API due to JSON error [id: {broadcasterId}]" ) ;
2024-06-16 20:19:31 -04:00
}
catch ( Exception e )
{
_logger . Error ( e , "Failed to authorize to Twitch API." ) ;
2024-03-12 14:05:27 -04:00
}
2024-06-16 20:19:31 -04:00
return false ;
2024-03-12 14:05:27 -04:00
}
2024-06-16 20:19:31 -04:00
public async Task Connect ( )
{
2024-03-15 08:27:35 -04:00
_client . Connect ( ) ;
await _publisher . ConnectAsync ( ) ;
2024-03-12 14:05:27 -04:00
}
2024-06-16 20:19:31 -04:00
public void InitializeClient ( string username , IEnumerable < string > channels )
{
2024-07-16 00:48:55 -04:00
ConnectionCredentials credentials = new ConnectionCredentials ( username , _token ! . AccessToken ) ;
2024-03-15 08:27:35 -04:00
_client . Initialize ( credentials , channels . Distinct ( ) . ToList ( ) ) ;
2024-03-12 14:05:27 -04:00
2024-06-16 20:19:31 -04:00
if ( _initialized )
{
_logger . Debug ( "Twitch API client has already been initialized." ) ;
2024-03-12 14:05:27 -04:00
return ;
}
2024-06-16 20:19:31 -04:00
_initialized = true ;
2024-03-12 14:05:27 -04:00
2024-06-16 20:19:31 -04:00
_client . OnJoinedChannel + = async Task ( object? s , OnJoinedChannelArgs e ) = >
{
_logger . Information ( "Joined channel: " + e . Channel ) ;
2024-03-12 14:05:27 -04:00
} ;
2024-06-16 20:19:31 -04:00
_client . OnConnected + = async Task ( object? s , OnConnectedArgs e ) = >
{
2024-07-16 00:48:55 -04:00
_logger . Information ( "Twitch API client connected." ) ;
2024-03-12 14:05:27 -04:00
} ;
2024-06-16 20:19:31 -04:00
_client . OnIncorrectLogin + = async Task ( object? s , OnIncorrectLoginArgs e ) = >
{
_logger . Error ( e . Exception , "Incorrect Login on Twitch API client." ) ;
2024-03-12 14:05:27 -04:00
2024-06-16 20:19:31 -04:00
_logger . Information ( "Attempting to re-authorize." ) ;
await Authorize ( _broadcasterId ) ;
2024-07-16 00:48:55 -04:00
_client . SetConnectionCredentials ( new ConnectionCredentials ( _user . TwitchUsername , _token ! . AccessToken ) ) ;
await Task . Delay ( TimeSpan . FromSeconds ( 3 ) ) ;
await _client . ReconnectAsync ( ) ;
2024-03-12 14:05:27 -04:00
} ;
2024-06-16 20:19:31 -04:00
_client . OnConnectionError + = async Task ( object? s , OnConnectionErrorArgs e ) = >
{
_logger . Error ( "Connection Error: " + e . Error . Message + " (" + e . Error . GetType ( ) . Name + ")" ) ;
2024-03-15 08:27:35 -04:00
2024-06-16 20:19:31 -04:00
_logger . Information ( "Attempting to re-authorize." ) ;
await Authorize ( _broadcasterId ) ;
2024-03-12 14:05:27 -04:00
} ;
2024-06-16 20:19:31 -04:00
_client . OnError + = async Task ( object? s , OnErrorEventArgs e ) = >
{
_logger . Error ( e . Exception , "Twitch API client error." ) ;
2024-03-12 14:05:27 -04:00
} ;
2024-07-16 00:48:55 -04:00
_client . OnDisconnected + = async Task ( s , e ) = > _logger . Warning ( "Twitch API client disconnected." ) ;
2024-03-12 14:05:27 -04:00
}
2024-06-16 20:19:31 -04:00
public void InitializePublisher ( )
{
_publisher . OnPubSubServiceConnected + = async ( s , e ) = >
{
2024-03-15 08:27:35 -04:00
_publisher . ListenToChannelPoints ( _token . BroadcasterId ) ;
_publisher . ListenToFollows ( _token . BroadcasterId ) ;
2024-03-12 14:05:27 -04:00
2024-03-15 08:27:35 -04:00
await _publisher . SendTopicsAsync ( _token . AccessToken ) ;
2024-06-16 20:19:31 -04:00
_logger . Information ( "Twitch PubSub has been connected." ) ;
2024-03-12 14:05:27 -04:00
} ;
2024-06-16 20:19:31 -04:00
_publisher . OnFollow + = ( s , e ) = >
{
2024-06-24 18:11:36 -04:00
_logger . Information ( $"New Follower [name: {e.DisplayName}][username: {e.Username}]" ) ;
2024-03-12 14:05:27 -04:00
} ;
2024-06-24 18:11:36 -04:00
_publisher . OnChannelPointsRewardRedeemed + = async ( s , e ) = >
2024-06-16 20:19:31 -04:00
{
2024-06-24 18:11:36 -04:00
_logger . Information ( $"Channel Point Reward Redeemed [redeem: {e.RewardRedeemed.Redemption.Reward.Title}][redeem id: {e.RewardRedeemed.Redemption.Reward.Id}][transaction: {e.RewardRedeemed.Redemption.Id}]" ) ;
2024-03-12 14:05:27 -04:00
2024-07-16 00:48:55 -04:00
try
2024-06-16 20:19:31 -04:00
{
2024-07-16 00:48:55 -04:00
var actions = _redemptionManager . Get ( e . RewardRedeemed . Redemption . Reward . Id ) ;
if ( ! actions . Any ( ) )
2024-06-16 20:19:31 -04:00
{
2024-07-16 00:48:55 -04:00
_logger . Debug ( $"No redemable actions for this redeem was found [redeem: {e.RewardRedeemed.Redemption.Reward.Title}][redeem id: {e.RewardRedeemed.Redemption.Reward.Id}][transaction: {e.RewardRedeemed.Redemption.Id}]" ) ;
return ;
2024-03-12 14:05:27 -04:00
}
2024-07-16 00:48:55 -04:00
_logger . Debug ( $"Found {actions.Count} actions for this Twitch channel point redemption [redeem: {e.RewardRedeemed.Redemption.Reward.Title}][redeem id: {e.RewardRedeemed.Redemption.Reward.Id}][transaction: {e.RewardRedeemed.Redemption.Id}]" ) ;
foreach ( var action in actions )
try
{
await _redemptionManager . Execute ( action , e . RewardRedeemed . Redemption . User . DisplayName , long . Parse ( e . RewardRedeemed . Redemption . User . Id ) ) ;
}
catch ( Exception ex )
{
_logger . Error ( ex , $"Failed to execute redeeemable action [action: {action.Name}][action type: {action.Type}][redeem: {e.RewardRedeemed.Redemption.Reward.Title}][redeem id: {e.RewardRedeemed.Redemption.Reward.Id}][transaction: {e.RewardRedeemed.Redemption.Id}]" ) ;
}
}
catch ( Exception ex )
{
_logger . Error ( ex , $"Failed to fetch the redeemable actions for a redemption [redeem: {e.RewardRedeemed.Redemption.Reward.Title}][redeem id: {e.RewardRedeemed.Redemption.Reward.Id}][transaction: {e.RewardRedeemed.Redemption.Id}]" ) ;
}
2024-06-24 18:11:36 -04:00
} ;
2024-03-12 14:05:27 -04:00
2024-06-24 18:11:36 -04:00
_publisher . OnPubSubServiceClosed + = async ( s , e ) = >
{
_logger . Warning ( "Twitch PubSub ran into a service close. Attempting to connect again." ) ;
//await Task.Delay(Math.Min(3000 + (1 << psConnectionFailures), 120000));
var authorized = await Authorize ( _broadcasterId ) ;
var twitchBotData = await _hermesApiClient . FetchTwitchBotToken ( ) ;
if ( twitchBotData = = null )
2024-06-16 20:19:31 -04:00
{
2024-06-24 18:11:36 -04:00
Console . WriteLine ( "The API is down. Contact the owner." ) ;
return ;
2024-03-12 14:05:27 -04:00
}
2024-06-24 18:11:36 -04:00
await _publisher . ConnectAsync ( ) ;
2024-06-16 20:19:31 -04:00
} ;
2024-03-12 14:05:27 -04:00
}
2024-06-16 20:19:31 -04:00
public void AddOnNewMessageReceived ( AsyncEventHandler < OnMessageReceivedArgs > handler )
{
2024-03-15 08:27:35 -04:00
_client . OnMessageReceived + = handler ;
2024-03-12 14:05:27 -04:00
}
}