Added hermes websocket support. Added chat command support. Added selectable voice command via websocket. Added websocket heartbeat management.

This commit is contained in:
Tom
2024-03-15 12:27:35 +00:00
parent b5cc6b5706
commit d4004d6230
53 changed files with 1227 additions and 461 deletions

View File

@ -73,7 +73,6 @@ namespace TwitchChatTTS.Seven
public IList<Emote> Emotes { get; set; }
public int EmoteCount { get; set; }
public int Capacity { get; set; }
}
public class Emote {

View File

@ -1,18 +1,18 @@
using System.Text.Json;
using TwitchChatTTS.Helpers;
using Microsoft.Extensions.Logging;
using TwitchChatTTS;
using TwitchChatTTS.Seven;
public class SevenApiClient {
public static readonly string API_URL = "https://7tv.io/v3";
public static readonly string WEBSOCKET_URL = "wss://events.7tv.io/v3";
private WebClientWrap Web { get; }
private Configuration Configuration { get; }
private ILogger<SevenApiClient> Logger { get; }
private long? Id { get; }
public SevenApiClient(Configuration configuration, ILogger<SevenApiClient> logger, TwitchBotToken token) {
Configuration = configuration;
public SevenApiClient(ILogger<SevenApiClient> logger, TwitchBotToken token) {
Logger = logger;
Id = long.TryParse(token?.BroadcasterId, out long id) ? id : -1;
@ -23,16 +23,16 @@ public class SevenApiClient {
}
public async Task<EmoteDatabase?> GetSevenEmotes() {
if (Id is null)
if (Id == null)
throw new NullReferenceException(nameof(Id));
try {
var details = await Web.GetJson<UserDetails>("https://7tv.io/v3/users/twitch/" + Id);
if (details is null)
var details = await Web.GetJson<UserDetails>($"{API_URL}/users/twitch/" + Id);
if (details == null)
return null;
var emotes = new EmoteDatabase();
if (details.EmoteSet is not null)
if (details.EmoteSet != null)
foreach (var emote in details.EmoteSet.Emotes)
emotes.Add(emote.Name, emote.Id);
Logger.LogInformation($"Loaded {details.EmoteSet?.Emotes.Count() ?? 0} emotes from 7tv.");

View File

@ -2,8 +2,6 @@ namespace TwitchChatTTS.Seven.Socket.Context
{
public class ReconnectContext
{
public string? Protocol;
public string Url;
public string? SessionId;
}
}

View File

@ -1,12 +0,0 @@
namespace TwitchChatTTS.Seven.Socket.Context
{
public class SevenHelloContext
{
public IEnumerable<SevenSubscriptionConfiguration>? Subscriptions;
}
public class SevenSubscriptionConfiguration {
public string? Type;
public IDictionary<string, string>? Condition;
}
}

View File

@ -10,12 +10,12 @@ namespace TwitchChatTTS.Seven.Socket.Handlers
public class DispatchHandler : IWebSocketHandler
{
private ILogger Logger { get; }
private IServiceProvider ServiceProvider { get; }
private EmoteDatabase Emotes { get; }
public int OperationCode { get; set; } = 0;
public DispatchHandler(ILogger<DispatchHandler> logger, IServiceProvider serviceProvider) {
public DispatchHandler(ILogger<DispatchHandler> logger, EmoteDatabase emotes) {
Logger = logger;
ServiceProvider = serviceProvider;
Emotes = emotes;
}
public async Task Execute<Data>(SocketClient<WebSocketMessage> sender, Data message)
@ -23,23 +23,31 @@ namespace TwitchChatTTS.Seven.Socket.Handlers
if (message is not DispatchMessage obj || obj == null)
return;
Do(obj?.Body?.Pulled, cf => cf.OldValue);
Do(obj?.Body?.Pushed, cf => cf.Value);
ApplyChanges(obj?.Body?.Pulled, cf => cf.OldValue, true);
ApplyChanges(obj?.Body?.Pushed, cf => cf.Value, false);
}
private void Do(IEnumerable<ChangeField>? fields, Func<ChangeField, object> getter) {
if (fields is null)
private void ApplyChanges(IEnumerable<ChangeField>? fields, Func<ChangeField, object> getter, bool removing) {
if (fields == null)
return;
//ServiceProvider.GetRequiredService<EmoteDatabase>()
foreach (var val in fields) {
if (getter(val) == null)
var value = getter(val);
if (value == null)
continue;
var o = JsonSerializer.Deserialize<EmoteField>(val.OldValue.ToString(), new JsonSerializerOptions() {
var o = JsonSerializer.Deserialize<EmoteField>(value.ToString(), new JsonSerializerOptions() {
PropertyNameCaseInsensitive = false,
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
});
if (removing) {
Emotes.Remove(o.Name);
Logger.LogInformation($"Removed 7tv emote: {o.Name} (id: {o.Id})");
} else {
Emotes.Add(o.Name, o.Id);
Logger.LogInformation($"Added 7tv emote: {o.Name} (id: {o.Id})");
}
}
}
}

View File

@ -10,6 +10,7 @@ namespace TwitchChatTTS.Seven.Socket.Handlers
public class EndOfStreamHandler : IWebSocketHandler
{
private ILogger Logger { get; }
private Configuration Configuration { get; }
private IServiceProvider ServiceProvider { get; }
private string[] ErrorCodes { get; }
private int[] ReconnectDelay { get; }
@ -17,8 +18,9 @@ namespace TwitchChatTTS.Seven.Socket.Handlers
public int OperationCode { get; set; } = 7;
public EndOfStreamHandler(ILogger<EndOfStreamHandler> logger, IServiceProvider serviceProvider) {
public EndOfStreamHandler(ILogger<EndOfStreamHandler> logger, Configuration configuration, IServiceProvider serviceProvider) {
Logger = logger;
Configuration = configuration;
ServiceProvider = serviceProvider;
ErrorCodes = [
@ -71,17 +73,23 @@ namespace TwitchChatTTS.Seven.Socket.Handlers
return;
}
if (string.IsNullOrWhiteSpace(Configuration.Seven?.UserId))
return;
var context = ServiceProvider.GetRequiredService<ReconnectContext>();
await Task.Delay(ReconnectDelay[code]);
Logger.LogInformation($"7tv client reconnecting.");
await sender.ConnectAsync($"{context.Protocol ?? "wss"}://{context.Url}");
if (context.SessionId is null) {
await sender.Send(33, new object());
//var base_url = "@" + string.Join(",", Configuration.Seven.SevenId.Select(sub => sub.Type + "<" + string.Join(",", sub.Condition?.Select(e => e.Key + "=" + e.Value) ?? new string[0]) + ">"));
var base_url = $"@emote_set.*<object_id={Configuration.Seven.UserId.Trim()}>";
string url = $"{SevenApiClient.WEBSOCKET_URL}{base_url}";
Logger.LogDebug($"7tv websocket reconnecting to {url}.");
await sender.ConnectAsync(url);
if (context.SessionId != null) {
await sender.Send(34, new ResumeMessage() { SessionId = context.SessionId });
Logger.LogInformation("Resumed connection to 7tv websocket.");
} else {
await sender.Send(34, new ResumeMessage() {
SessionId = context.SessionId
});
Logger.LogDebug("7tv websocket session id not available.");
}
}
}

View File

@ -1,7 +1,6 @@
using CommonSocketLibrary.Abstract;
using CommonSocketLibrary.Common;
using Microsoft.Extensions.Logging;
using TwitchChatTTS.Seven.Socket.Context;
using TwitchChatTTS.Seven.Socket.Data;
namespace TwitchChatTTS.Seven.Socket.Handlers
@ -9,12 +8,12 @@ namespace TwitchChatTTS.Seven.Socket.Handlers
public class SevenHelloHandler : IWebSocketHandler
{
private ILogger Logger { get; }
private SevenHelloContext Context { get; }
private Configuration Configuration { get; }
public int OperationCode { get; set; } = 1;
public SevenHelloHandler(ILogger<SevenHelloHandler> logger, SevenHelloContext context) {
public SevenHelloHandler(ILogger<SevenHelloHandler> logger, Configuration configuration) {
Logger = logger;
Context = context;
Configuration = configuration;
}
public async Task Execute<Data>(SocketClient<WebSocketMessage> sender, Data message)
@ -27,30 +26,7 @@ namespace TwitchChatTTS.Seven.Socket.Handlers
seven.Connected = true;
seven.ConnectionDetails = obj;
// if (Context.Subscriptions == null || !Context.Subscriptions.Any()) {
// Logger.LogWarning("No subscriptions have been set for the 7tv websocket client.");
// return;
// }
//await Task.Delay(TimeSpan.FromMilliseconds(1000));
//await sender.Send(33, new IdentifyMessage());
//await Task.Delay(TimeSpan.FromMilliseconds(5000));
//await sender.SendRaw("{\"op\":35,\"d\":{\"type\":\"emote_set.*\",\"condition\":{\"object_id\":\"64505914b9fc508169ffe7cc\"}}}");
//await sender.SendRaw(File.ReadAllText("test.txt"));
// foreach (var sub in Context.Subscriptions) {
// if (string.IsNullOrWhiteSpace(sub.Type)) {
// Logger.LogWarning("Non-existent or empty subscription type found on the 7tv websocket client.");
// continue;
// }
// Logger.LogDebug($"Subscription Type: {sub.Type} | Condition: {string.Join(", ", sub.Condition?.Select(e => e.Key + "=" + e.Value) ?? new string[0])}");
// await sender.Send(35, new SubscribeMessage() {
// Type = sub.Type,
// Condition = sub.Condition
// });
// }
Logger.LogInformation("Connected to 7tv websockets.");
}
}
}

View File

@ -3,7 +3,7 @@ using CommonSocketLibrary.Socket.Manager;
using CommonSocketLibrary.Common;
using Microsoft.Extensions.DependencyInjection;
namespace TwitchChatTTS.Seven.Socket.Manager
namespace TwitchChatTTS.Seven.Socket.Managers
{
public class SevenHandlerManager : WebSocketHandlerManager
{

View File

@ -4,7 +4,7 @@ using CommonSocketLibrary.Socket.Manager;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace TwitchChatTTS.Seven.Socket.Manager
namespace TwitchChatTTS.Seven.Socket.Managers
{
public class SevenHandlerTypeManager : WebSocketHandlerTypeManager
{