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:
@ -1,9 +1,4 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
[Serializable]
|
||||
public class Account {
|
||||
[AllowNull]
|
||||
public string Id { get; set; }
|
||||
[AllowNull]
|
||||
public string Username { get; set; }
|
||||
}
|
@ -1,31 +1,44 @@
|
||||
using TwitchChatTTS.Helpers;
|
||||
using TwitchChatTTS;
|
||||
using TwitchChatTTS.Hermes;
|
||||
using System.Text.Json;
|
||||
using HermesSocketLibrary.Requests.Messages;
|
||||
using TwitchChatTTS.Hermes;
|
||||
|
||||
public class HermesClient {
|
||||
public class HermesApiClient
|
||||
{
|
||||
private WebClientWrap _web;
|
||||
|
||||
public HermesClient(Configuration configuration) {
|
||||
if (string.IsNullOrWhiteSpace(configuration.Hermes?.Token)) {
|
||||
public HermesApiClient(Configuration configuration)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(configuration.Hermes?.Token))
|
||||
{
|
||||
throw new Exception("Ensure you have written your API key in \".token\" file, in the same folder as this application.");
|
||||
}
|
||||
|
||||
_web = new WebClientWrap(new JsonSerializerOptions() {
|
||||
_web = new WebClientWrap(new JsonSerializerOptions()
|
||||
{
|
||||
PropertyNameCaseInsensitive = false,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
|
||||
});
|
||||
_web.AddHeader("x-api-key", configuration.Hermes.Token);
|
||||
}
|
||||
|
||||
public async Task<Account> FetchHermesAccountDetails() {
|
||||
public async Task<TTSVersion> GetTTSVersion()
|
||||
{
|
||||
var version = await _web.GetJson<TTSVersion>("https://hermes.goblincaves.com/api/info/version");
|
||||
return version;
|
||||
}
|
||||
|
||||
public async Task<Account> FetchHermesAccountDetails()
|
||||
{
|
||||
var account = await _web.GetJson<Account>("https://hermes.goblincaves.com/api/account");
|
||||
if (account == null || account.Id == null || account.Username == null)
|
||||
throw new NullReferenceException("Invalid value found while fetching for hermes account data.");
|
||||
return account;
|
||||
}
|
||||
|
||||
public async Task<TwitchBotToken> FetchTwitchBotToken() {
|
||||
public async Task<TwitchBotToken> FetchTwitchBotToken()
|
||||
{
|
||||
var token = await _web.GetJson<TwitchBotToken>("https://hermes.goblincaves.com/api/token/bot");
|
||||
if (token == null || token.ClientId == null || token.AccessToken == null || token.RefreshToken == null || token.ClientSecret == null)
|
||||
throw new Exception("Failed to fetch Twitch API token from Hermes.");
|
||||
@ -33,7 +46,8 @@ public class HermesClient {
|
||||
return token;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<TTSUsernameFilter>> FetchTTSUsernameFilters() {
|
||||
public async Task<IEnumerable<TTSUsernameFilter>> FetchTTSUsernameFilters()
|
||||
{
|
||||
var filters = await _web.GetJson<IEnumerable<TTSUsernameFilter>>("https://hermes.goblincaves.com/api/settings/tts/filter/users");
|
||||
if (filters == null)
|
||||
throw new Exception("Failed to fetch TTS username filters from Hermes.");
|
||||
@ -41,23 +55,35 @@ public class HermesClient {
|
||||
return filters;
|
||||
}
|
||||
|
||||
public async Task<string> FetchTTSDefaultVoice() {
|
||||
var data = await _web.GetJson<TTSVoice>("https://hermes.goblincaves.com/api/settings/tts/default");
|
||||
public async Task<string> FetchTTSDefaultVoice()
|
||||
{
|
||||
var data = await _web.GetJson<string>("https://hermes.goblincaves.com/api/settings/tts/default");
|
||||
if (data == null)
|
||||
throw new Exception("Failed to fetch TTS default voice from Hermes.");
|
||||
|
||||
return data.Label;
|
||||
return data;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<TTSVoice>> FetchTTSEnabledVoices() {
|
||||
var voices = await _web.GetJson<IEnumerable<TTSVoice>>("https://hermes.goblincaves.com/api/settings/tts");
|
||||
public async Task<IEnumerable<TTSChatterSelectedVoice>> FetchTTSChatterSelectedVoices()
|
||||
{
|
||||
var voices = await _web.GetJson<IEnumerable<TTSChatterSelectedVoice>>("https://hermes.goblincaves.com/api/settings/tts/selected");
|
||||
if (voices == null)
|
||||
throw new Exception("Failed to fetch TTS chatter selected voices from Hermes.");
|
||||
|
||||
return voices;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<string>> FetchTTSEnabledVoices()
|
||||
{
|
||||
var voices = await _web.GetJson<IEnumerable<string>>("https://hermes.goblincaves.com/api/settings/tts");
|
||||
if (voices == null)
|
||||
throw new Exception("Failed to fetch TTS enabled voices from Hermes.");
|
||||
|
||||
return voices;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<TTSWordFilter>> FetchTTSWordFilters() {
|
||||
public async Task<IEnumerable<TTSWordFilter>> FetchTTSWordFilters()
|
||||
{
|
||||
var filters = await _web.GetJson<IEnumerable<TTSWordFilter>>("https://hermes.goblincaves.com/api/settings/tts/filter/words");
|
||||
if (filters == null)
|
||||
throw new Exception("Failed to fetch TTS word filters from Hermes.");
|
||||
|
@ -1,7 +1,7 @@
|
||||
using CommonSocketLibrary.Abstract;
|
||||
using CommonSocketLibrary.Common;
|
||||
using HermesSocketLibrary.Socket.Data;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Handlers
|
||||
{
|
||||
@ -10,7 +10,8 @@ namespace TwitchChatTTS.Hermes.Socket.Handlers
|
||||
private ILogger _logger { get; }
|
||||
public int OperationCode { get; set; } = 0;
|
||||
|
||||
public HeartbeatHandler(ILogger<HeartbeatHandler> logger) {
|
||||
public HeartbeatHandler(ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@ -18,18 +19,22 @@ namespace TwitchChatTTS.Hermes.Socket.Handlers
|
||||
{
|
||||
if (message is not HeartbeatMessage obj || obj == null)
|
||||
return;
|
||||
|
||||
if (sender is not HermesSocketClient client) {
|
||||
|
||||
if (sender is not HermesSocketClient client)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogTrace("Received heartbeat.");
|
||||
_logger.Verbose("Received heartbeat.");
|
||||
|
||||
client.LastHeartbeat = DateTime.UtcNow;
|
||||
client.LastHeartbeatReceived = DateTime.UtcNow;
|
||||
|
||||
await sender.Send(0, new HeartbeatMessage() {
|
||||
DateTime = DateTime.UtcNow
|
||||
});
|
||||
if (obj.Respond)
|
||||
await sender.Send(0, new HeartbeatMessage()
|
||||
{
|
||||
DateTime = DateTime.UtcNow,
|
||||
Respond = false
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +1,20 @@
|
||||
using CommonSocketLibrary.Abstract;
|
||||
using CommonSocketLibrary.Common;
|
||||
using HermesSocketLibrary.Socket.Data;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Serilog;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Handlers
|
||||
{
|
||||
public class LoginAckHandler : IWebSocketHandler
|
||||
{
|
||||
private ILogger _logger { get; }
|
||||
private IServiceProvider _serviceProvider;
|
||||
private ILogger _logger;
|
||||
public int OperationCode { get; set; } = 2;
|
||||
|
||||
public LoginAckHandler(ILogger<LoginAckHandler> logger) {
|
||||
public LoginAckHandler(IServiceProvider serviceProvider, ILogger logger)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@ -18,17 +22,45 @@ namespace TwitchChatTTS.Hermes.Socket.Handlers
|
||||
{
|
||||
if (message is not LoginAckMessage obj || obj == null)
|
||||
return;
|
||||
|
||||
if (sender is not HermesSocketClient client) {
|
||||
|
||||
if (sender is not HermesSocketClient client)
|
||||
return;
|
||||
|
||||
if (obj.AnotherClient)
|
||||
{
|
||||
_logger.Warning("Another client has connected to the same account.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var user = _serviceProvider.GetRequiredService<User>();
|
||||
client.UserId = obj.UserId;
|
||||
_logger.Information($"Logged in as {user.TwitchUsername} (id: {client.UserId}).");
|
||||
}
|
||||
|
||||
if (obj.AnotherClient) {
|
||||
_logger.LogWarning("Another client has connected to the same account.");
|
||||
} else {
|
||||
client.UserId = obj.UserId;
|
||||
_logger.LogInformation($"Logged in as {client.UserId}.");
|
||||
}
|
||||
await client.Send(3, new RequestMessage()
|
||||
{
|
||||
Type = "get_tts_voices",
|
||||
Data = null
|
||||
});
|
||||
|
||||
var token = _serviceProvider.GetRequiredService<User>();
|
||||
await client.Send(3, new RequestMessage()
|
||||
{
|
||||
Type = "get_tts_users",
|
||||
Data = new Dictionary<string, object>() { { "user", token.HermesUserId } }
|
||||
});
|
||||
|
||||
await client.Send(3, new RequestMessage()
|
||||
{
|
||||
Type = "get_chatter_ids",
|
||||
Data = null
|
||||
});
|
||||
|
||||
await client.Send(3, new RequestMessage()
|
||||
{
|
||||
Type = "get_emotes",
|
||||
Data = null
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -5,7 +5,8 @@ using CommonSocketLibrary.Common;
|
||||
using HermesSocketLibrary.Requests.Messages;
|
||||
using HermesSocketLibrary.Socket.Data;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog;
|
||||
using TwitchChatTTS.Seven;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Handlers
|
||||
{
|
||||
@ -14,9 +15,13 @@ namespace TwitchChatTTS.Hermes.Socket.Handlers
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly JsonSerializerOptions _options;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private readonly object _voicesAvailableLock = new object();
|
||||
|
||||
public int OperationCode { get; set; } = 4;
|
||||
|
||||
public RequestAckHandler(IServiceProvider serviceProvider, JsonSerializerOptions options, ILogger<RequestAckHandler> logger) {
|
||||
public RequestAckHandler(IServiceProvider serviceProvider, JsonSerializerOptions options, ILogger logger)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
@ -32,74 +37,144 @@ namespace TwitchChatTTS.Hermes.Socket.Handlers
|
||||
if (context == null)
|
||||
return;
|
||||
|
||||
if (obj.Request.Type == "get_tts_voices") {
|
||||
_logger.LogDebug("Updating all available voices.");
|
||||
if (obj.Request.Type == "get_tts_voices")
|
||||
{
|
||||
_logger.Verbose("Updating all available voices for TTS.");
|
||||
var voices = JsonSerializer.Deserialize<IEnumerable<VoiceDetails>>(obj.Data.ToString(), _options);
|
||||
if (voices == null)
|
||||
return;
|
||||
|
||||
context.VoicesAvailable = voices.ToDictionary(e => e.Id, e => e.Name);
|
||||
_logger.LogInformation("Updated all available voices.");
|
||||
} else if (obj.Request.Type == "create_tts_user") {
|
||||
_logger.LogDebug("Creating new tts voice.");
|
||||
if (!long.TryParse(obj.Request.Data["@user"], out long userId))
|
||||
|
||||
lock (_voicesAvailableLock)
|
||||
{
|
||||
context.VoicesAvailable = voices.ToDictionary(e => e.Id, e => e.Name);
|
||||
}
|
||||
_logger.Information("Updated all available voices for TTS.");
|
||||
}
|
||||
else if (obj.Request.Type == "create_tts_user")
|
||||
{
|
||||
_logger.Verbose("Adding new tts voice for user.");
|
||||
if (!long.TryParse(obj.Request.Data["user"].ToString(), out long chatterId))
|
||||
return;
|
||||
string broadcasterId = obj.Request.Data["@broadcaster"].ToString();
|
||||
// TODO: validate broadcaster id.
|
||||
string voice = obj.Request.Data["@voice"].ToString();
|
||||
|
||||
context.VoicesSelected.Add(userId, voice);
|
||||
_logger.LogInformation("Created new tts user.");
|
||||
} else if (obj.Request.Type == "update_tts_user") {
|
||||
_logger.LogDebug("Updating user's voice");
|
||||
if (!long.TryParse(obj.Request.Data["@user"], out long userId))
|
||||
string userId = obj.Request.Data["user"].ToString();
|
||||
string voice = obj.Request.Data["voice"].ToString();
|
||||
|
||||
context.VoicesSelected.Add(chatterId, voice);
|
||||
_logger.Information($"Added new TTS voice [voice: {voice}] for user [user id: {userId}]");
|
||||
}
|
||||
else if (obj.Request.Type == "update_tts_user")
|
||||
{
|
||||
_logger.Verbose("Updating user's voice");
|
||||
if (!long.TryParse(obj.Request.Data["chatter"].ToString(), out long chatterId))
|
||||
return;
|
||||
string broadcasterId = obj.Request.Data["@broadcaster"].ToString();
|
||||
string voice = obj.Request.Data["@voice"].ToString();
|
||||
|
||||
context.VoicesSelected[userId] = voice;
|
||||
_logger.LogInformation($"Updated user's voice to {voice}.");
|
||||
} else if (obj.Request.Type == "create_tts_voice") {
|
||||
_logger.LogDebug("Creating new tts voice.");
|
||||
string? voice = obj.Request.Data["@voice"];
|
||||
string userId = obj.Request.Data["user"].ToString();
|
||||
string voice = obj.Request.Data["voice"].ToString();
|
||||
|
||||
context.VoicesSelected[chatterId] = voice;
|
||||
_logger.Information($"Updated TTS voice [voice: {voice}] for user [user id: {userId}]");
|
||||
}
|
||||
else if (obj.Request.Type == "create_tts_voice")
|
||||
{
|
||||
_logger.Verbose("Creating new tts voice.");
|
||||
string? voice = obj.Request.Data["voice"].ToString();
|
||||
string? voiceId = obj.Data.ToString();
|
||||
if (voice == null || voiceId == null)
|
||||
return;
|
||||
|
||||
context.VoicesAvailable.Add(voiceId, voice);
|
||||
_logger.LogInformation($"Created new tts voice named {voice} (id: {voiceId}).");
|
||||
} else if (obj.Request.Type == "delete_tts_voice") {
|
||||
_logger.LogDebug("Deleting tts voice.");
|
||||
lock (_voicesAvailableLock)
|
||||
{
|
||||
var list = context.VoicesAvailable.ToDictionary(k => k.Key, v => v.Value);
|
||||
list.Add(voiceId, voice);
|
||||
context.VoicesAvailable = list;
|
||||
}
|
||||
_logger.Information($"Created new tts voice [voice: {voice}][id: {voiceId}].");
|
||||
}
|
||||
else if (obj.Request.Type == "delete_tts_voice")
|
||||
{
|
||||
_logger.Verbose("Deleting tts voice.");
|
||||
var voice = obj.Request.Data["voice"].ToString();
|
||||
if (!context.VoicesAvailable.TryGetValue(voice, out string voiceName) || voiceName == null)
|
||||
return;
|
||||
|
||||
var voice = obj.Request.Data["@voice"];
|
||||
if (!context.VoicesAvailable.TryGetValue(voice, out string voiceName) || voiceName == null) {
|
||||
return;
|
||||
lock (_voicesAvailableLock)
|
||||
{
|
||||
var dict = context.VoicesAvailable.ToDictionary(k => k.Key, v => v.Value);
|
||||
dict.Remove(voice);
|
||||
context.VoicesAvailable.Remove(voice);
|
||||
}
|
||||
|
||||
context.VoicesAvailable.Remove(voice);
|
||||
_logger.LogInformation("Deleted a voice, named " + voiceName + ".");
|
||||
} else if (obj.Request.Type == "update_tts_voice") {
|
||||
_logger.LogDebug("Updating tts voice.");
|
||||
string voiceId = obj.Request.Data["@idd"].ToString();
|
||||
string voice = obj.Request.Data["@voice"].ToString();
|
||||
|
||||
if (!context.VoicesAvailable.TryGetValue(voiceId, out string voiceName) || voiceName == null) {
|
||||
_logger.Information($"Deleted a voice [voice: {voiceName}]");
|
||||
}
|
||||
else if (obj.Request.Type == "update_tts_voice")
|
||||
{
|
||||
_logger.Verbose("Updating TTS voice.");
|
||||
string voiceId = obj.Request.Data["idd"].ToString();
|
||||
string voice = obj.Request.Data["voice"].ToString();
|
||||
|
||||
if (!context.VoicesAvailable.TryGetValue(voiceId, out string voiceName) || voiceName == null)
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
context.VoicesAvailable[voiceId] = voice;
|
||||
_logger.LogInformation("Update tts voice: " + voice);
|
||||
} else if (obj.Request.Type == "get_tts_users") {
|
||||
_logger.LogDebug("Attempting to update all chatters' selected voice.");
|
||||
_logger.Information($"Updated TTS voice [voice: {voice}][id: {voiceId}]");
|
||||
}
|
||||
else if (obj.Request.Type == "get_tts_users")
|
||||
{
|
||||
_logger.Verbose("Updating all chatters' selected voice.");
|
||||
var users = JsonSerializer.Deserialize<IDictionary<long, string>>(obj.Data.ToString(), _options);
|
||||
if (users == null)
|
||||
return;
|
||||
|
||||
|
||||
var temp = new ConcurrentDictionary<long, string>();
|
||||
foreach (var entry in users)
|
||||
temp.TryAdd(entry.Key, entry.Value);
|
||||
context.VoicesSelected = temp;
|
||||
_logger.LogInformation($"Fetched {temp.Count()} chatters' selected voice.");
|
||||
_logger.Information($"Updated {temp.Count()} chatters' selected voice.");
|
||||
}
|
||||
else if (obj.Request.Type == "get_chatter_ids")
|
||||
{
|
||||
_logger.Verbose("Fetching all chatters' id.");
|
||||
var chatters = JsonSerializer.Deserialize<IEnumerable<long>>(obj.Data.ToString(), _options);
|
||||
if (chatters == null)
|
||||
return;
|
||||
|
||||
var client = _serviceProvider.GetRequiredService<ChatMessageHandler>();
|
||||
client.Chatters = [.. chatters];
|
||||
_logger.Information($"Fetched {chatters.Count()} chatters' id.");
|
||||
}
|
||||
else if (obj.Request.Type == "get_emotes")
|
||||
{
|
||||
_logger.Verbose("Updating emotes.");
|
||||
var emotes = JsonSerializer.Deserialize<IEnumerable<EmoteInfo>>(obj.Data.ToString(), _options);
|
||||
if (emotes == null)
|
||||
return;
|
||||
|
||||
var emoteDb = _serviceProvider.GetRequiredService<EmoteDatabase>();
|
||||
var count = 0;
|
||||
foreach (var emote in emotes)
|
||||
{
|
||||
if (emoteDb.Get(emote.Name) == null)
|
||||
{
|
||||
emoteDb.Add(emote.Name, emote.Id);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
_logger.Information($"Fetched {count} emotes from various sources.");
|
||||
}
|
||||
else if (obj.Request.Type == "update_tts_voice_state")
|
||||
{
|
||||
_logger.Verbose("Updating TTS voice states.");
|
||||
string voiceId = obj.Request.Data["voice"].ToString();
|
||||
bool state = obj.Request.Data["state"].ToString() == "true";
|
||||
|
||||
if (!context.VoicesAvailable.TryGetValue(voiceId, out string voiceName) || voiceName == null)
|
||||
{
|
||||
_logger.Warning($"Failed to find voice [id: {voiceId}]");
|
||||
return;
|
||||
}
|
||||
|
||||
if (state)
|
||||
context.VoicesEnabled.Add(voiceId);
|
||||
else
|
||||
context.VoicesEnabled.Remove(voiceId);
|
||||
_logger.Information($"Updated voice state [voice: {voiceName}][new state: {(state ? "enabled" : "disabled")}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,108 @@
|
||||
using System.Text.Json;
|
||||
using System.Timers;
|
||||
using CommonSocketLibrary.Abstract;
|
||||
using CommonSocketLibrary.Common;
|
||||
using HermesSocketLibrary.Socket.Data;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket
|
||||
{
|
||||
public class HermesSocketClient : WebSocketClient {
|
||||
public DateTime LastHeartbeat { get; set; }
|
||||
public string? UserId { get; set; }
|
||||
public class HermesSocketClient : WebSocketClient
|
||||
{
|
||||
private Configuration _configuration;
|
||||
public DateTime LastHeartbeatReceived { get; set; }
|
||||
public DateTime LastHeartbeatSent { get; set; }
|
||||
public string? UserId { get; set; }
|
||||
private System.Timers.Timer _heartbeatTimer;
|
||||
private System.Timers.Timer _reconnectTimer;
|
||||
|
||||
public HermesSocketClient(
|
||||
ILogger<HermesSocketClient> logger,
|
||||
Configuration configuration,
|
||||
[FromKeyedServices("hermes")] HandlerManager<WebSocketClient, IWebSocketHandler> handlerManager,
|
||||
[FromKeyedServices("hermes")] HandlerTypeManager<WebSocketClient, IWebSocketHandler> typeManager
|
||||
) : base(logger, handlerManager, typeManager, new JsonSerializerOptions() {
|
||||
[FromKeyedServices("hermes")] HandlerTypeManager<WebSocketClient, IWebSocketHandler> typeManager,
|
||||
ILogger logger
|
||||
) : base(logger, handlerManager, typeManager, new JsonSerializerOptions()
|
||||
{
|
||||
PropertyNameCaseInsensitive = false,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
|
||||
}) {
|
||||
|
||||
})
|
||||
{
|
||||
_configuration = configuration;
|
||||
|
||||
_heartbeatTimer = new System.Timers.Timer(TimeSpan.FromSeconds(15));
|
||||
_heartbeatTimer.Elapsed += async (sender, e) => await HandleHeartbeat(e);
|
||||
|
||||
_reconnectTimer = new System.Timers.Timer(TimeSpan.FromSeconds(15));
|
||||
_reconnectTimer.Elapsed += async (sender, e) => await Reconnect(e);
|
||||
|
||||
LastHeartbeatReceived = LastHeartbeatSent = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
protected override async Task OnConnection()
|
||||
{
|
||||
_heartbeatTimer.Enabled = true;
|
||||
}
|
||||
|
||||
private async Task HandleHeartbeat(ElapsedEventArgs e)
|
||||
{
|
||||
var signalTime = e.SignalTime.ToUniversalTime();
|
||||
|
||||
if (signalTime - LastHeartbeatReceived > TimeSpan.FromSeconds(60))
|
||||
{
|
||||
if (LastHeartbeatReceived > LastHeartbeatSent)
|
||||
{
|
||||
_logger.Verbose("Sending heartbeat...");
|
||||
LastHeartbeatSent = DateTime.UtcNow;
|
||||
try
|
||||
{
|
||||
await Send(0, new HeartbeatMessage() { DateTime = LastHeartbeatSent });
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
else if (signalTime - LastHeartbeatReceived > TimeSpan.FromSeconds(120))
|
||||
{
|
||||
try
|
||||
{
|
||||
await DisconnectAsync();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
UserId = null;
|
||||
_heartbeatTimer.Enabled = false;
|
||||
|
||||
_logger.Information("Logged off due to disconnection. Attempting to reconnect...");
|
||||
_reconnectTimer.Enabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Reconnect(ElapsedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync($"wss://hermes-ws.goblincaves.com");
|
||||
Connected = true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Connected)
|
||||
{
|
||||
_logger.Information("Reconnected.");
|
||||
_reconnectTimer.Enabled = false;
|
||||
_heartbeatTimer.Enabled = true;
|
||||
LastHeartbeatReceived = DateTime.UtcNow;
|
||||
|
||||
if (_configuration.Hermes?.Token != null)
|
||||
await Send(1, new HermesLoginMessage() { ApiKey = _configuration.Hermes.Token });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,35 +1,41 @@
|
||||
using CommonSocketLibrary.Common;
|
||||
using CommonSocketLibrary.Socket.Manager;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Managers
|
||||
{
|
||||
public class HermesHandlerManager : WebSocketHandlerManager
|
||||
{
|
||||
public HermesHandlerManager(ILogger<HermesHandlerManager> logger, IServiceProvider provider) : base(logger) {
|
||||
public HermesHandlerManager(ILogger logger, IServiceProvider provider) : base(logger)
|
||||
{
|
||||
//Add(provider.GetRequiredService<HeartbeatHandler>());
|
||||
try {
|
||||
try
|
||||
{
|
||||
var basetype = typeof(IWebSocketHandler);
|
||||
var assembly = GetType().Assembly;
|
||||
var types = assembly.GetTypes().Where(t => t.IsClass && basetype.IsAssignableFrom(t) && t.AssemblyQualifiedName?.Contains(".Hermes.") == true);
|
||||
|
||||
foreach (var type in types) {
|
||||
foreach (var type in types)
|
||||
{
|
||||
var key = "hermes-" + type.Name.Replace("Handlers", "Hand#lers")
|
||||
.Replace("Handler", "")
|
||||
.Replace("Hand#lers", "Handlers")
|
||||
.ToLower();
|
||||
var handler = provider.GetKeyedService<IWebSocketHandler>(key);
|
||||
if (handler == null) {
|
||||
logger.LogError("Failed to find hermes websocket handler: " + type.AssemblyQualifiedName);
|
||||
if (handler == null)
|
||||
{
|
||||
logger.Error("Failed to find hermes websocket handler: " + type.AssemblyQualifiedName);
|
||||
continue;
|
||||
}
|
||||
|
||||
Logger.LogDebug($"Linked type {type.AssemblyQualifiedName} to hermes websocket handlers.");
|
||||
|
||||
Logger.Debug($"Linked type {type.AssemblyQualifiedName} to hermes websocket handlers.");
|
||||
Add(handler);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logger.LogError(e, "Failed to load hermes websocket handler types.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error(e, "Failed to load hermes websocket handler types.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,14 +3,14 @@ using CommonSocketLibrary.Abstract;
|
||||
using CommonSocketLibrary.Common;
|
||||
using CommonSocketLibrary.Socket.Manager;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog;
|
||||
|
||||
namespace TwitchChatTTS.Hermes.Socket.Managers
|
||||
{
|
||||
public class HermesHandlerTypeManager : WebSocketHandlerTypeManager
|
||||
{
|
||||
public HermesHandlerTypeManager(
|
||||
ILogger<HermesHandlerTypeManager> factory,
|
||||
ILogger factory,
|
||||
[FromKeyedServices("hermes")] HandlerManager<WebSocketClient, IWebSocketHandler> handlers
|
||||
) : base(factory, handlers)
|
||||
{
|
||||
@ -20,12 +20,12 @@ namespace TwitchChatTTS.Hermes.Socket.Managers
|
||||
{
|
||||
if (handlerType == null)
|
||||
return null;
|
||||
|
||||
|
||||
var name = handlerType.Namespace + "." + handlerType.Name;
|
||||
name = name.Replace(".Handlers.", ".Data.")
|
||||
.Replace("Handler", "Message")
|
||||
.Replace("TwitchChatTTS.Hermes.", "HermesSocketLibrary.");
|
||||
|
||||
|
||||
return Assembly.Load("HermesSocketLibrary").GetType(name);
|
||||
}
|
||||
}
|
||||
|
10
Hermes/TTSVersion.cs
Normal file
10
Hermes/TTSVersion.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace TwitchChatTTS.Hermes
|
||||
{
|
||||
public class TTSVersion
|
||||
{
|
||||
public int MajorVersion { get; set; }
|
||||
public int MinorVersion { get; set; }
|
||||
public string Download { get; set; }
|
||||
public string Changelog { get; set; }
|
||||
}
|
||||
}
|
@ -1,6 +1,13 @@
|
||||
public class TTSVoice {
|
||||
public string Label { get; set; }
|
||||
public int Value { get; set; }
|
||||
public string? Gender { get; set; }
|
||||
public string? Language { get; set; }
|
||||
public class TTSVoice
|
||||
{
|
||||
public string Label { get; set; }
|
||||
public int Value { get; set; }
|
||||
public string? Gender { get; set; }
|
||||
public string? Language { get; set; }
|
||||
}
|
||||
|
||||
public class TTSChatterSelectedVoice
|
||||
{
|
||||
public long ChatterId { get; set; }
|
||||
public string Voice { get; set; }
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
namespace TwitchChatTTS.Hermes
|
||||
{
|
||||
public class TTSWordFilter
|
||||
{
|
||||
public string? Id { get; set; }
|
||||
public string? Search { get; set; }
|
||||
public string? Replace { get; set; }
|
||||
public string? UserId { get; set; }
|
||||
|
||||
public bool IsRegex { get; set; }
|
||||
|
||||
|
||||
public TTSWordFilter() {
|
||||
IsRegex = true;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
[Serializable]
|
||||
public class TwitchBotAuth {
|
||||
public string? UserId { get; set; }
|
||||
public string? AccessToken { get; set; }
|
||||
public string? RefreshToken { get; set; }
|
||||
public string? BroadcasterId { get; set; }
|
||||
public string? UserId { get; set; }
|
||||
public string? AccessToken { get; set; }
|
||||
public string? RefreshToken { get; set; }
|
||||
public string? BroadcasterId { get; set; }
|
||||
}
|
@ -1,8 +1,7 @@
|
||||
[Serializable]
|
||||
public class TwitchBotToken {
|
||||
public string? ClientId { get; set; }
|
||||
public string? ClientSecret { get; set; }
|
||||
public string? AccessToken { get; set; }
|
||||
public string? RefreshToken { get; set; }
|
||||
public string? BroadcasterId { get; set; }
|
||||
public string? ClientId { get; set; }
|
||||
public string? ClientSecret { get; set; }
|
||||
public string? AccessToken { get; set; }
|
||||
public string? RefreshToken { get; set; }
|
||||
public string? BroadcasterId { get; set; }
|
||||
}
|
@ -1,8 +1,7 @@
|
||||
[Serializable]
|
||||
public class TwitchConnection {
|
||||
public string? Id { get; set; }
|
||||
public string? Secret { get; set; }
|
||||
public string? BroadcasterId { get; set; }
|
||||
public string? Username { get; set; }
|
||||
public string? UserId { get; set; }
|
||||
public string? Id { get; set; }
|
||||
public string? Secret { get; set; }
|
||||
public string? BroadcasterId { get; set; }
|
||||
public string? Username { get; set; }
|
||||
public string? UserId { get; set; }
|
||||
}
|
Reference in New Issue
Block a user