Compare commits

...

2 Commits

Author SHA1 Message Date
Tom
b35183249b Added Veadotube integration 2024-12-02 20:51:04 +00:00
Tom
48dd6858a1 Fixed certain redemptions 2024-11-15 02:29:23 +00:00
10 changed files with 325 additions and 3 deletions

View File

@ -1,3 +1,4 @@
using System.Text.Json;
using CommonSocketLibrary.Abstract; using CommonSocketLibrary.Abstract;
using CommonSocketLibrary.Common; using CommonSocketLibrary.Common;
using HermesSocketLibrary.Socket.Data; using HermesSocketLibrary.Socket.Data;
@ -35,7 +36,7 @@ namespace TwitchChatTTS.Hermes.Socket.Handlers
_logger.Debug($"Received a Hermes request message [type: {message.Request.Type}][data: {string.Join(',', message.Request.Data?.Select(entry => entry.Key + '=' + entry.Value) ?? Array.Empty<string>())}]"); _logger.Debug($"Received a Hermes request message [type: {message.Request.Type}][data: {string.Join(',', message.Request.Data?.Select(entry => entry.Key + '=' + entry.Value) ?? Array.Empty<string>())}]");
var json = message.Data?.ToString(); var json = message.Data?.ToString();
if (message.Request.Type == null || json == null) if (message.Request.Type == null)
{ {
return; return;
} }

View File

@ -15,8 +15,6 @@ namespace TwitchChatTTS.Hermes.Socket.Requests
public void Fulfill(string type, string requestId, string data, IDictionary<string, object>? requestData) public void Fulfill(string type, string requestId, string data, IDictionary<string, object>? requestData)
{ {
if (data == null)
return;
if (!_acknowledgements.TryGetValue(type, out var ack)) if (!_acknowledgements.TryGetValue(type, out var ack))
{ {
_logger.Warning($"Found unknown request type when acknowledging [type: {type}]"); _logger.Warning($"Found unknown request type when acknowledging [type: {type}]");

View File

@ -35,6 +35,7 @@ using TwitchChatTTS.Chat.Observers;
using TwitchChatTTS.Chat.Commands.Limits; using TwitchChatTTS.Chat.Commands.Limits;
using TwitchChatTTS.Hermes.Socket.Requests; using TwitchChatTTS.Hermes.Socket.Requests;
using TwitchChatTTS.Bus; using TwitchChatTTS.Bus;
using TwitchChatTTS.Veadotube;
// dotnet publish -r linux-x64 -p:PublishSingleFile=true --self-contained true // dotnet publish -r linux-x64 -p:PublishSingleFile=true --self-contained true
// dotnet publish -r win-x64 -p:PublishSingleFile=true --self-contained true // dotnet publish -r win-x64 -p:PublishSingleFile=true --self-contained true
@ -147,6 +148,10 @@ s.AddKeyedSingleton<IWebSocketHandler, EndOfStreamHandler>("7tv");
s.AddKeyedSingleton<MessageTypeManager<IWebSocketHandler>, SevenMessageTypeManager>("7tv"); s.AddKeyedSingleton<MessageTypeManager<IWebSocketHandler>, SevenMessageTypeManager>("7tv");
s.AddKeyedSingleton<SocketClient<WebSocketMessage>, SevenSocketClient>("7tv"); s.AddKeyedSingleton<SocketClient<WebSocketMessage>, SevenSocketClient>("7tv");
// Veadotube
s.AddKeyedSingleton<MessageTypeManager<IWebSocketHandler>, VeadoMessageTypeManager>("veadotube");
s.AddKeyedSingleton<SocketClient<object>, VeadoSocketClient>("veadotube");
// Nightbot // Nightbot
s.AddSingleton<NightbotApiClient>(); s.AddSingleton<NightbotApiClient>();

13
TTS.cs
View File

@ -14,6 +14,7 @@ using TwitchChatTTS.Twitch.Socket;
using TwitchChatTTS.Chat.Commands; using TwitchChatTTS.Chat.Commands;
using System.Text; using System.Text;
using TwitchChatTTS.Chat.Speech; using TwitchChatTTS.Chat.Speech;
using TwitchChatTTS.Veadotube;
namespace TwitchChatTTS namespace TwitchChatTTS
{ {
@ -29,6 +30,7 @@ namespace TwitchChatTTS
private readonly OBSSocketClient _obs; private readonly OBSSocketClient _obs;
private readonly SevenSocketClient _seven; private readonly SevenSocketClient _seven;
private readonly TwitchWebsocketClient _twitch; private readonly TwitchWebsocketClient _twitch;
private readonly VeadoSocketClient _veado;
private readonly ICommandFactory _commandFactory; private readonly ICommandFactory _commandFactory;
private readonly ICommandManager _commandManager; private readonly ICommandManager _commandManager;
private readonly IEmoteDatabase _emotes; private readonly IEmoteDatabase _emotes;
@ -45,6 +47,7 @@ namespace TwitchChatTTS
[FromKeyedServices("obs")] SocketClient<WebSocketMessage> obs, [FromKeyedServices("obs")] SocketClient<WebSocketMessage> obs,
[FromKeyedServices("7tv")] SocketClient<WebSocketMessage> seven, [FromKeyedServices("7tv")] SocketClient<WebSocketMessage> seven,
[FromKeyedServices("twitch")] SocketClient<TwitchWebsocketMessage> twitch, [FromKeyedServices("twitch")] SocketClient<TwitchWebsocketMessage> twitch,
[FromKeyedServices("veadotube")] SocketClient<object> veado,
ICommandFactory commandFactory, ICommandFactory commandFactory,
ICommandManager commandManager, ICommandManager commandManager,
IEmoteDatabase emotes, IEmoteDatabase emotes,
@ -61,6 +64,7 @@ namespace TwitchChatTTS
_obs = (obs as OBSSocketClient)!; _obs = (obs as OBSSocketClient)!;
_seven = (seven as SevenSocketClient)!; _seven = (seven as SevenSocketClient)!;
_twitch = (twitch as TwitchWebsocketClient)!; _twitch = (twitch as TwitchWebsocketClient)!;
_veado = (veado as VeadoSocketClient)!;
_commandFactory = commandFactory; _commandFactory = commandFactory;
_commandManager = commandManager; _commandManager = commandManager;
_emotes = emotes; _emotes = emotes;
@ -132,6 +136,15 @@ namespace TwitchChatTTS
} }
}); });
try
{
_veado.Initialize();
await _veado.Connect();
}
catch (Exception e) {
_logger.Warning(e, "Failed to connect to Veado websocket server.");
}
try try
{ {
await _twitch.Connect(); await _twitch.Connect();

View File

@ -10,6 +10,7 @@ using TwitchChatTTS.Bus.Data;
using TwitchChatTTS.Hermes.Socket; using TwitchChatTTS.Hermes.Socket;
using TwitchChatTTS.OBS.Socket; using TwitchChatTTS.OBS.Socket;
using TwitchChatTTS.OBS.Socket.Data; using TwitchChatTTS.OBS.Socket.Data;
using TwitchChatTTS.Veadotube;
namespace TwitchChatTTS.Twitch.Redemptions namespace TwitchChatTTS.Twitch.Redemptions
{ {
@ -20,6 +21,7 @@ namespace TwitchChatTTS.Twitch.Redemptions
private readonly User _user; private readonly User _user;
private readonly OBSSocketClient _obs; private readonly OBSSocketClient _obs;
private readonly HermesSocketClient _hermes; private readonly HermesSocketClient _hermes;
private readonly VeadoSocketClient _veado;
private readonly NightbotApiClient _nightbot; private readonly NightbotApiClient _nightbot;
private readonly AudioPlaybackEngine _playback; private readonly AudioPlaybackEngine _playback;
private readonly ILogger _logger; private readonly ILogger _logger;
@ -32,6 +34,7 @@ namespace TwitchChatTTS.Twitch.Redemptions
User user, User user,
[FromKeyedServices("obs")] SocketClient<WebSocketMessage> obs, [FromKeyedServices("obs")] SocketClient<WebSocketMessage> obs,
[FromKeyedServices("hermes")] SocketClient<WebSocketMessage> hermes, [FromKeyedServices("hermes")] SocketClient<WebSocketMessage> hermes,
[FromKeyedServices("veadotube")] SocketClient<object> veado,
NightbotApiClient nightbot, NightbotApiClient nightbot,
AudioPlaybackEngine playback, AudioPlaybackEngine playback,
ILogger logger) ILogger logger)
@ -41,6 +44,7 @@ namespace TwitchChatTTS.Twitch.Redemptions
_user = user; _user = user;
_obs = (obs as OBSSocketClient)!; _obs = (obs as OBSSocketClient)!;
_hermes = (hermes as HermesSocketClient)!; _hermes = (hermes as HermesSocketClient)!;
_veado = (veado as VeadoSocketClient)!;
_nightbot = nightbot; _nightbot = nightbot;
_playback = playback; _playback = playback;
_logger = logger; _logger = logger;
@ -220,6 +224,15 @@ namespace TwitchChatTTS.Twitch.Redemptions
case "NIGHTBOT_CLEAR_QUEUE": case "NIGHTBOT_CLEAR_QUEUE":
await _nightbot.ClearQueue(); await _nightbot.ClearQueue();
break; break;
case "VEADOTUBE_SET_STATE":
await _veado.SetCurrentState(action.Data["state"]);
break;
case "VEADOTUBE_PUSH_STATE":
await _veado.PushState(action.Data["state"]);
break;
case "VEADOTUBE_POP_STATE":
await _veado.PopState(action.Data["state"]);
break;
default: default:
_logger.Warning($"Unknown redeemable action has occured. Update needed? [type: {action.Type}][chatter: {senderDisplayName}][chatter id: {senderId}]"); _logger.Warning($"Unknown redeemable action has occured. Update needed? [type: {action.Type}][chatter: {senderDisplayName}][chatter id: {senderId}]");
break; break;

View File

@ -12,6 +12,7 @@
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="NAudio" Version="2.2.1" /> <PackageReference Include="NAudio" Version="2.2.1" />
<PackageReference Include="NAudio.Extras" Version="2.2.1" /> <PackageReference Include="NAudio.Extras" Version="2.2.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Serilog" Version="4.0.0" /> <PackageReference Include="Serilog" Version="4.0.0" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.2-dev-00338" /> <PackageReference Include="Serilog.AspNetCore" Version="8.0.2-dev-00338" />
<PackageReference Include="Serilog.Formatting.Compact" Version="3.0.0" /> <PackageReference Include="Serilog.Formatting.Compact" Version="3.0.0" />

View File

@ -0,0 +1,14 @@
using System.Text.Json.Serialization;
namespace TwitchChatTTS.Veadotube
{
public class VeadoInstanceInfo
{
[JsonPropertyName("time")]
public long Time { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("server")]
public string Server { get; set; }
}
}

38
Veadotube/VeadoMessage.cs Normal file
View File

@ -0,0 +1,38 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace TwitchChatTTS.Veadotube
{
public class VeadoPayloadMessage
{
public string Event { get; set; }
public string Type { get; set; }
public string Id { get; set; }
public object Payload { get; set; }
}
public class VeadoEventMessage
{
[JsonPropertyName("event")]
public string Event { get; set; }
}
public class VeadoNodeState {
public string Id { get; set; }
public string Name { get; set; }
}
public class VeadoNodeStateListMessage : VeadoEventMessage {
public IEnumerable<VeadoNodeState> Entries { get; set; }
}
public class VeadoNodeStateMessage : VeadoEventMessage {
public string State { get; set; }
}
public class VeadoNodeThumbMessage {
public int Width { get; set; }
public int Height { get; set; }
public string Png { get; set; }
}
}

View File

@ -0,0 +1,17 @@
using CommonSocketLibrary.Common;
using CommonSocketLibrary.Socket.Manager;
using Microsoft.Extensions.DependencyInjection;
using Serilog;
namespace TwitchChatTTS.Veadotube
{
public class VeadoMessageTypeManager : WebSocketMessageTypeManager
{
public VeadoMessageTypeManager(
[FromKeyedServices("veadotube")] IEnumerable<IWebSocketHandler> handlers,
ILogger logger
) : base(handlers, logger)
{
}
}
}

View File

@ -0,0 +1,222 @@
using CommonSocketLibrary.Common;
using CommonSocketLibrary.Abstract;
using Microsoft.Extensions.DependencyInjection;
using Serilog;
using System.Text.Json;
using CommonSocketLibrary.Backoff;
using System.Text;
using System.Net.WebSockets;
namespace TwitchChatTTS.Veadotube
{
public class VeadoSocketClient : SocketClient<object>
{
private VeadoInstanceInfo? Instance;
public bool Connected { get; set; }
public bool Identified { get; set; }
public bool Streaming { get; set; }
public VeadoSocketClient(
[FromKeyedServices("veadotube")] IEnumerable<IWebSocketHandler> handlers,
[FromKeyedServices("veadotube")] MessageTypeManager<IWebSocketHandler> typeManager,
ILogger logger
) : base(logger, new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = false,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
})
{
}
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.");
OnConnected += (sender, e) =>
{
Connected = true;
_logger.Information("Veadotube websocket client connected.");
};
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",
}
});
}
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}]");
}
}
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;
}
protected override Task OnResponseReceived(object? content)
{
var contentAsString = JsonSerializer.Serialize(content);
_logger.Debug("VEADO RX: " + contentAsString);
return Task.CompletedTask;
}
}
}