Using Serilog. Added partial OBS batch request support. Added update checking. Added more commands. Added enabled/disabled TTS voices. And more.

This commit is contained in:
Tom
2024-06-17 00:19:31 +00:00
parent d4004d6230
commit 706cd06930
67 changed files with 1933 additions and 925 deletions

View File

@ -1,6 +1,6 @@
using System.Text.Json;
using TwitchChatTTS.Helpers;
using Microsoft.Extensions.Logging;
using Serilog;
using TwitchChatTTS;
using TwitchLib.Api.Core.Exceptions;
using TwitchLib.Client.Events;
@ -14,133 +14,164 @@ using TwitchLib.PubSub.Interfaces;
using TwitchLib.Client.Interfaces;
using TwitchChatTTS.OBS.Socket;
public class TwitchApiClient {
public class TwitchApiClient
{
private readonly Configuration _configuration;
private readonly ILogger<TwitchApiClient> _logger;
private readonly TwitchBotToken _token;
private readonly ILogger _logger;
private TwitchBotAuth _token;
private readonly ITwitchClient _client;
private readonly ITwitchPubSub _publisher;
private readonly WebClientWrap Web;
private readonly WebClientWrap _web;
private readonly IServiceProvider _serviceProvider;
private bool Initialized;
private bool _initialized;
private string _broadcasterId;
public TwitchApiClient(
Configuration configuration,
ILogger<TwitchApiClient> logger,
TwitchBotToken token,
TwitchBotAuth token,
ITwitchClient twitchClient,
ITwitchPubSub twitchPublisher,
IServiceProvider serviceProvider
) {
IServiceProvider serviceProvider,
ILogger logger
)
{
_configuration = configuration;
_logger = logger;
_token = token;
_client = twitchClient;
_publisher = twitchPublisher;
_serviceProvider = serviceProvider;
Initialized = false;
_logger = logger;
_initialized = false;
Web = new WebClientWrap(new JsonSerializerOptions() {
_web = new WebClientWrap(new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = false,
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
});
if (!string.IsNullOrWhiteSpace(_configuration.Hermes?.Token))
Web.AddHeader("x-api-key", _configuration.Hermes.Token.Trim());
_web.AddHeader("x-api-key", _configuration.Hermes.Token.Trim());
}
public async Task Authorize() {
try {
var authorize = await Web.GetJson<TwitchBotAuth>("https://hermes.goblincaves.com/api/account/reauthorize");
if (authorize != null && _token.BroadcasterId == authorize.BroadcasterId) {
public async Task<bool> Authorize(string broadcasterId)
{
try
{
var authorize = await _web.GetJson<TwitchBotAuth>("https://hermes.goblincaves.com/api/account/reauthorize");
if (authorize != null && broadcasterId == authorize.BroadcasterId)
{
_token.AccessToken = authorize.AccessToken;
_token.RefreshToken = authorize.RefreshToken;
_logger.LogInformation("Updated Twitch API tokens.");
} else if (authorize != null) {
_logger.LogError("Twitch API Authorization failed.");
_token.UserId = authorize.UserId;
_token.BroadcasterId = authorize.BroadcasterId;
_logger.Information("Updated Twitch API tokens.");
}
} catch (HttpResponseException e) {
if (string.IsNullOrWhiteSpace(_configuration.Hermes?.Token))
_logger.LogError("No Hermes API key found. Enter it into the configuration file.");
else
_logger.LogError("Invalid Hermes API key. Double check the token. HTTP Error Code: " + e.HttpResponse.StatusCode);
} catch (JsonException) {
} catch (Exception e) {
_logger.LogError(e, "Failed to authorize to Twitch API.");
else if (authorize != null)
{
_logger.Error("Twitch API Authorization failed: " + authorize.AccessToken + " | " + authorize.RefreshToken + " | " + authorize.UserId + " | " + authorize.BroadcasterId);
return false;
}
_broadcasterId = broadcasterId;
return true;
}
catch (HttpResponseException e)
{
if (string.IsNullOrWhiteSpace(_configuration.Hermes?.Token))
_logger.Error("No Hermes API key found. Enter it into the configuration file.");
else
_logger.Error("Invalid Hermes API key. Double check the token. HTTP Error Code: " + e.HttpResponse.StatusCode);
}
catch (JsonException)
{
}
catch (Exception e)
{
_logger.Error(e, "Failed to authorize to Twitch API.");
}
return false;
}
public async Task Connect() {
public async Task Connect()
{
_client.Connect();
await _publisher.ConnectAsync();
}
public void InitializeClient(string username, IEnumerable<string> channels) {
public void InitializeClient(string username, IEnumerable<string> channels)
{
ConnectionCredentials credentials = new ConnectionCredentials(username, _token?.AccessToken);
_client.Initialize(credentials, channels.Distinct().ToList());
if (Initialized) {
_logger.LogDebug("Twitch API client has already been initialized.");
if (_initialized)
{
_logger.Debug("Twitch API client has already been initialized.");
return;
}
Initialized = true;
_initialized = true;
_client.OnJoinedChannel += async Task (object? s, OnJoinedChannelArgs e) => {
_logger.LogInformation("Joined channel: " + e.Channel);
_client.OnJoinedChannel += async Task (object? s, OnJoinedChannelArgs e) =>
{
_logger.Information("Joined channel: " + e.Channel);
};
_client.OnConnected += async Task (object? s, OnConnectedArgs e) => {
_logger.LogInformation("-----------------------------------------------------------");
_client.OnConnected += async Task (object? s, OnConnectedArgs e) =>
{
_logger.Information("-----------------------------------------------------------");
};
_client.OnIncorrectLogin += async Task (object? s, OnIncorrectLoginArgs e) => {
_logger.LogError(e.Exception, "Incorrect Login on Twitch API client.");
_client.OnIncorrectLogin += async Task (object? s, OnIncorrectLoginArgs e) =>
{
_logger.Error(e.Exception, "Incorrect Login on Twitch API client.");
_logger.LogInformation("Attempting to re-authorize.");
await Authorize();
_logger.Information("Attempting to re-authorize.");
await Authorize(_broadcasterId);
};
_client.OnConnectionError += async Task (object? s, OnConnectionErrorArgs e) => {
_logger.LogError("Connection Error: " + e.Error.Message + " (" + e.Error.GetType().Name + ")");
_client.OnConnectionError += async Task (object? s, OnConnectionErrorArgs e) =>
{
_logger.Error("Connection Error: " + e.Error.Message + " (" + e.Error.GetType().Name + ")");
_logger.LogInformation("Attempting to re-authorize.");
await Authorize();
_logger.Information("Attempting to re-authorize.");
await Authorize(_broadcasterId);
};
_client.OnError += async Task (object? s, OnErrorEventArgs e) => {
_logger.LogError(e.Exception, "Twitch API client error.");
_client.OnError += async Task (object? s, OnErrorEventArgs e) =>
{
_logger.Error(e.Exception, "Twitch API client error.");
};
}
public void InitializePublisher() {
_publisher.OnPubSubServiceConnected += async (s, e) => {
public void InitializePublisher()
{
_publisher.OnPubSubServiceConnected += async (s, e) =>
{
_publisher.ListenToChannelPoints(_token.BroadcasterId);
_publisher.ListenToFollows(_token.BroadcasterId);
await _publisher.SendTopicsAsync(_token.AccessToken);
_logger.LogInformation("Twitch PubSub has been connected.");
_logger.Information("Twitch PubSub has been connected.");
};
_publisher.OnFollow += (s, e) => {
var client = _serviceProvider.GetRequiredKeyedService<SocketClient<WebSocketMessage>>("obs") as OBSSocketClient;
if (_configuration.Twitch?.TtsWhenOffline != true && client?.Live == false)
return;
_logger.LogInformation("Follow: " + e.DisplayName);
};
_publisher.OnChannelPointsRewardRedeemed += (s, e) => {
_publisher.OnFollow += (s, e) =>
{
var client = _serviceProvider.GetRequiredKeyedService<SocketClient<WebSocketMessage>>("obs") as OBSSocketClient;
if (_configuration.Twitch?.TtsWhenOffline != true && client?.Live == false)
return;
_logger.LogInformation($"Channel Point Reward Redeemed: {e.RewardRedeemed.Redemption.Reward.Title} (id: {e.RewardRedeemed.Redemption.Id})");
_logger.Information("Follow: " + e.DisplayName);
};
if (_configuration.Twitch?.Redeems == null) {
_logger.LogDebug("No redeems found in the configuration.");
_publisher.OnChannelPointsRewardRedeemed += (s, e) =>
{
var client = _serviceProvider.GetRequiredKeyedService<SocketClient<WebSocketMessage>>("obs") as OBSSocketClient;
if (_configuration.Twitch?.TtsWhenOffline != true && client?.Live == false)
return;
_logger.Information($"Channel Point Reward Redeemed [redeem: {e.RewardRedeemed.Redemption.Reward.Title}][id: {e.RewardRedeemed.Redemption.Id}]");
if (_configuration.Twitch?.Redeems == null)
return;
}
var redeemName = e.RewardRedeemed.Redemption.Reward.Title.ToLower().Trim().Replace(" ", "-");
if (!_configuration.Twitch.Redeems.TryGetValue(redeemName, out RedeemConfiguration? redeem))
@ -148,19 +179,28 @@ public class TwitchApiClient {
if (redeem == null)
return;
// Write or append to file if needed.
var outputFile = string.IsNullOrWhiteSpace(redeem.OutputFilePath) ? null : redeem.OutputFilePath.Trim();
if (outputFile == null) {
_logger.LogDebug($"No output file was provided for redeem '{e.RewardRedeemed.Redemption.Reward.Title}'.");
} else {
if (outputFile == null)
{
_logger.Debug($"No output file was provided for redeem [redeem: {e.RewardRedeemed.Redemption.Reward.Title}][id: {e.RewardRedeemed.Redemption.Id}]");
}
else
{
var outputContent = string.IsNullOrWhiteSpace(redeem.OutputContent) ? null : redeem.OutputContent.Trim().Replace("%USER%", e.RewardRedeemed.Redemption.User.DisplayName).Replace("\\n", "\n");
if (outputContent == null) {
_logger.LogWarning($"No output content was provided for redeem '{e.RewardRedeemed.Redemption.Reward.Title}'.");
} else {
if (redeem.OutputAppend == true) {
if (outputContent == null)
{
_logger.Warning($"No output content was provided for redeem [redeem: {e.RewardRedeemed.Redemption.Reward.Title}][id: {e.RewardRedeemed.Redemption.Id}]");
}
else
{
if (redeem.OutputAppend == true)
{
File.AppendAllText(outputFile, outputContent + "\n");
} else {
}
else
{
File.WriteAllText(outputFile, outputContent);
}
}
@ -168,40 +208,23 @@ public class TwitchApiClient {
// Play audio file if needed.
var audioFile = string.IsNullOrWhiteSpace(redeem.AudioFilePath) ? null : redeem.AudioFilePath.Trim();
if (audioFile == null) {
_logger.LogDebug($"No audio file was provided for redeem '{e.RewardRedeemed.Redemption.Reward.Title}'.");
return;
if (audioFile == null)
{
_logger.Debug($"No audio file was provided for redeem [redeem: {e.RewardRedeemed.Redemption.Reward.Title}][id: {e.RewardRedeemed.Redemption.Id}]");
}
if (!File.Exists(audioFile)) {
_logger.LogWarning($"Cannot find audio file @ {audioFile} for redeem '{e.RewardRedeemed.Redemption.Reward.Title}'.");
return;
else if (!File.Exists(audioFile))
{
_logger.Warning($"Cannot find audio file [location: {audioFile}] for redeem [redeem: {e.RewardRedeemed.Redemption.Reward.Title}][id: {e.RewardRedeemed.Redemption.Id}]");
}
else
{
AudioPlaybackEngine.Instance.PlaySound(audioFile);
}
AudioPlaybackEngine.Instance.PlaySound(audioFile);
};
/*int psConnectionFailures = 0;
publisher.OnPubSubServiceError += async (s, e) => {
Console.WriteLine("PubSub ran into a service error. Attempting to connect again.");
await Task.Delay(Math.Min(3000 + (1 << psConnectionFailures), 120000));
var connect = await WebHelper.Get("https://hermes.goblincaves.com/api/account/reauthorize");
if ((int) connect.StatusCode == 200 || (int) connect.StatusCode == 201) {
psConnectionFailures = 0;
} else {
psConnectionFailures++;
}
var twitchBotData2 = await WebHelper.GetJson<TwitchBotToken>("https://hermes.goblincaves.com/api/token/bot");
if (twitchBotData2 == null) {
Console.WriteLine("The API is down. Contact the owner.");
return;
}
twitchBotData.access_token = twitchBotData2.access_token;
await pubsub.ConnectAsync();
};*/
}
public void AddOnNewMessageReceived(AsyncEventHandler<OnMessageReceivedArgs> handler) {
public void AddOnNewMessageReceived(AsyncEventHandler<OnMessageReceivedArgs> handler)
{
_client.OnMessageReceived += handler;
}
}