Added groups & permissions. Fixed TTS user creation. Better connection handling. Fixed 7tv reconnection.

This commit is contained in:
Tom
2024-07-16 04:48:55 +00:00
parent 9fb966474f
commit e6b3819356
45 changed files with 947 additions and 567 deletions

View File

@ -1,9 +0,0 @@
namespace TwitchChatTTS.OBS.Socket.Context
{
public class HelloContext
{
public string? Host { get; set; }
public short? Port { get; set; }
public string? Password { get; set; }
}
}

View File

@ -12,5 +12,9 @@ namespace TwitchChatTTS.OBS.Socket.Data
RequestId = id;
RequestData = data;
}
public RequestMessage(string type, Dictionary<string, object> data) : this(type, string.Empty, data) { }
public RequestMessage(string type) : this(type, string.Empty, new()) { }
}
}

View File

@ -2,16 +2,19 @@ using CommonSocketLibrary.Abstract;
using CommonSocketLibrary.Common;
using Serilog;
using TwitchChatTTS.OBS.Socket.Data;
using TwitchChatTTS.OBS.Socket.Manager;
namespace TwitchChatTTS.OBS.Socket.Handlers
{
public class EventMessageHandler : IWebSocketHandler
{
private readonly OBSManager _manager;
private readonly ILogger _logger;
public int OperationCode { get; } = 5;
public EventMessageHandler(ILogger logger)
public EventMessageHandler(OBSManager manager, ILogger logger)
{
_manager = manager;
_logger = logger;
}
@ -23,28 +26,23 @@ namespace TwitchChatTTS.OBS.Socket.Handlers
switch (message.EventType)
{
case "StreamStateChanged":
case "RecordStateChanged":
if (sender is not OBSSocketClient client)
return;
string? raw_state = message.EventData["outputState"].ToString();
string? state = raw_state?.Substring(21).ToLower();
client.Live = message.EventData["outputActive"].ToString() == "True";
_manager.Streaming = message.EventData["outputActive"].ToString().ToLower() == "true";
_logger.Warning("Stream " + (state != null && state.EndsWith("ing") ? "is " : "has ") + state + ".");
if (client.Live == false && state != null && !state.EndsWith("ing"))
if (_manager.Streaming == false && state != null && !state.EndsWith("ing"))
{
OnStreamEnd();
// Stream ended
}
break;
default:
_logger.Debug(message.EventType + " EVENT: " + string.Join(" | ", message.EventData?.Select(x => x.Key + "=" + x.Value?.ToString()) ?? new string[0]));
_logger.Debug(message.EventType + " EVENT: " + string.Join(" | ", message.EventData?.Select(x => x.Key + "=" + x.Value?.ToString()) ?? Array.Empty<string>()));
break;
}
}
private void OnStreamEnd()
{
}
}
}

View File

@ -4,19 +4,18 @@ using CommonSocketLibrary.Abstract;
using CommonSocketLibrary.Common;
using Serilog;
using TwitchChatTTS.OBS.Socket.Data;
using TwitchChatTTS.OBS.Socket.Context;
namespace TwitchChatTTS.OBS.Socket.Handlers
{
public class HelloHandler : IWebSocketHandler
{
private readonly HelloContext _context;
private readonly Configuration _configuration;
private readonly ILogger _logger;
public int OperationCode { get; } = 0;
public HelloHandler(HelloContext context, ILogger logger)
public HelloHandler(Configuration configuration, ILogger logger)
{
_context = context;
_configuration = configuration;
_logger = logger;
}
@ -24,9 +23,10 @@ namespace TwitchChatTTS.OBS.Socket.Handlers
{
if (data is not HelloMessage message || message == null)
return;
_logger.Verbose("OBS websocket password: " + _context.Password);
if (message.Authentication == null || string.IsNullOrWhiteSpace(_context.Password))
string? password = string.IsNullOrWhiteSpace(_configuration.Obs?.Password) ? null : _configuration.Obs.Password.Trim();
_logger.Verbose("OBS websocket password: " + password);
if (message.Authentication == null || string.IsNullOrWhiteSpace(password))
{
await sender.Send(1, new IdentifyMessage(message.RpcVersion, string.Empty, 1023 | 262144));
return;
@ -37,7 +37,7 @@ namespace TwitchChatTTS.OBS.Socket.Handlers
_logger.Verbose("Salt: " + salt);
_logger.Verbose("Challenge: " + challenge);
string secret = _context.Password + salt;
string secret = password + salt;
byte[] bytes = Encoding.UTF8.GetBytes(secret);
string hash = null;
using (var sha = SHA256.Create())

View File

@ -23,7 +23,7 @@ namespace TwitchChatTTS.OBS.Socket.Handlers
if (data is not IdentifiedMessage message || message == null)
return;
sender.Connected = true;
_manager.Connected = true;
_logger.Information("Connected to OBS via rpc version " + message.NegotiatedRpcVersion + ".");
try
@ -34,6 +34,8 @@ namespace TwitchChatTTS.OBS.Socket.Handlers
{
_logger.Error(e, "Failed to load OBS group info upon OBS identification.");
}
await _manager.UpdateStreamingState();
}
}
}

View File

@ -42,10 +42,7 @@ namespace TwitchChatTTS.OBS.Socket.Handlers
switch (request.RequestType)
{
case "GetOutputStatus":
if (sender is not OBSSocketClient client)
return;
_logger.Debug($"Fetched stream's live status [live: {client.Live}][obs request id: {message.RequestId}]");
_logger.Debug($"Fetched stream's live status [live: {_manager.Streaming}][obs request id: {message.RequestId}]");
break;
case "GetSceneItemId":
{
@ -227,6 +224,24 @@ namespace TwitchChatTTS.OBS.Socket.Handlers
_logger.Debug($"Received response from OBS for sleeping [sleep: {sleepMillis}][obs request id: {message.RequestId}]");
break;
}
case "GetStreamStatus":
{
if (message.ResponseData == null)
{
_logger.Warning($"OBS Response is null [obs request id: {message.RequestId}]");
return;
}
if (!message.ResponseData.TryGetValue("outputActive", out object? outputActive) || outputActive == null)
{
_logger.Warning($"Failed to fetch the scene item visibility [obs request id: {message.RequestId}]");
return;
}
_manager.Streaming = outputActive?.ToString()!.ToLower() == "true";
requestData.ResponseValues = message.ResponseData;
_logger.Information($"OBS is currently {(_manager.Streaming ? "" : "not ")}streaming.");
break;
}
default:
_logger.Warning($"OBS Request Response not being processed [type: {request.RequestType}][{string.Join(Environment.NewLine, message.ResponseData?.Select(kvp => kvp.Key + " = " + kvp.Value?.ToString()) ?? [])}]");
break;

View File

@ -12,11 +12,19 @@ namespace TwitchChatTTS.OBS.Socket.Manager
{
private readonly IDictionary<string, RequestData> _requests;
private readonly IDictionary<string, long> _sourceIds;
private string? URL;
private readonly Configuration _configuration;
private readonly IServiceProvider _serviceProvider;
private readonly ILogger _logger;
public OBSManager(IServiceProvider serviceProvider, ILogger logger)
public bool Connected { get; set; }
public bool Streaming { get; set; }
public OBSManager(Configuration configuration, IServiceProvider serviceProvider, ILogger logger)
{
_configuration = configuration;
_serviceProvider = serviceProvider;
_logger = logger;
@ -24,6 +32,27 @@ namespace TwitchChatTTS.OBS.Socket.Manager
_sourceIds = new Dictionary<string, long>();
}
public void Initialize()
{
_logger.Information($"Initializing OBS websocket client.");
var client = _serviceProvider.GetRequiredKeyedService<SocketClient<WebSocketMessage>>("obs");
client.OnConnected += (sender, e) =>
{
Connected = true;
_logger.Information("OBS websocket client connected.");
};
client.OnDisconnected += (sender, e) =>
{
Connected = false;
_logger.Information("OBS websocket client disconnected.");
};
if (!string.IsNullOrWhiteSpace(_configuration.Obs?.Host) && _configuration.Obs?.Port != null)
URL = $"ws://{_configuration.Obs.Host?.Trim()}:{_configuration.Obs.Port}";
}
public void AddSourceId(string sourceName, long sourceId)
{
@ -39,8 +68,35 @@ namespace TwitchChatTTS.OBS.Socket.Manager
_sourceIds.Clear();
}
public async Task Connect()
{
if (string.IsNullOrWhiteSpace(URL))
{
_logger.Warning("Lacking connection info for OBS websockets. Not connecting to OBS.");
return;
}
var client = _serviceProvider.GetRequiredKeyedService<SocketClient<WebSocketMessage>>("obs");
_logger.Debug($"OBS websocket client attempting to connect to {URL}");
try
{
await client.ConnectAsync(URL);
}
catch (Exception)
{
_logger.Warning("Connecting to obs failed. Skipping obs websockets.");
}
}
public async Task Send(IEnumerable<RequestMessage> messages)
{
if (!Connected)
{
_logger.Warning("OBS websocket client is not connected. Not sending a message.");
return;
}
string uid = GenerateUniqueIdentifier();
var list = messages.ToList();
_logger.Debug($"Sending OBS request batch of {list.Count} messages [obs request batch id: {uid}].");
@ -60,6 +116,12 @@ namespace TwitchChatTTS.OBS.Socket.Manager
public async Task Send(RequestMessage message, Action<Dictionary<string, object>>? callback = null)
{
if (!Connected)
{
_logger.Warning("OBS websocket client is not connected. Not sending a message.");
return;
}
string uid = GenerateUniqueIdentifier();
_logger.Debug($"Sending an OBS request [type: {message.RequestType}][obs request id: {uid}]");
@ -85,21 +147,26 @@ namespace TwitchChatTTS.OBS.Socket.Manager
return null;
}
public async Task UpdateStreamingState()
{
await Send(new RequestMessage("GetStreamStatus"));
}
public async Task UpdateTransformation(string sceneName, string sceneItemName, Action<OBSTransformationData> action)
{
if (action == null)
return;
await GetSceneItemById(sceneName, sceneItemName, async (sceneItemId) =>
await GetSceneItemByName(sceneName, sceneItemName, async (sceneItemId) =>
{
var m2 = new RequestMessage("GetSceneItemTransform", string.Empty, new Dictionary<string, object>() { { "sceneName", sceneName }, { "sceneItemId", sceneItemId } });
var m2 = new RequestMessage("GetSceneItemTransform", new Dictionary<string, object>() { { "sceneName", sceneName }, { "sceneItemId", sceneItemId } });
await Send(m2, async (d) =>
{
if (d == null || !d.TryGetValue("sceneItemTransform", out object? transformData) || transformData == null)
return;
_logger.Verbose($"Current transformation data [scene: {sceneName}][sceneItemName: {sceneItemName}][sceneItemId: {sceneItemId}][transform: {transformData}][obs request id: {m2.RequestId}]");
var transform = JsonSerializer.Deserialize<OBSTransformationData>(transformData.ToString(), new JsonSerializerOptions()
var transform = JsonSerializer.Deserialize<OBSTransformationData>(transformData.ToString()!, new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = false,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
@ -126,20 +193,6 @@ namespace TwitchChatTTS.OBS.Socket.Manager
transform.PositionY = transform.PositionY + h / 2;
}
// if (hasBounds)
// {
// // Take care of bounds, for most cases.
// // 'Crop to Bounding Box' might be unsupported.
// w = transform.BoundsWidth;
// h = transform.BoundsHeight;
// a = transform.BoundsAlignment;
// }
// else if (transform.CropBottom + transform.CropLeft + transform.CropRight + transform.CropTop > 0)
// {
// w -= transform.CropLeft + transform.CropRight;
// h -= transform.CropTop + transform.CropBottom;
// }
action?.Invoke(transform);
var m3 = new RequestMessage("SetSceneItemTransform", string.Empty, new Dictionary<string, object>() { { "sceneName", sceneName }, { "sceneItemId", sceneItemId }, { "sceneItemTransform", transform } });
@ -151,7 +204,7 @@ namespace TwitchChatTTS.OBS.Socket.Manager
public async Task ToggleSceneItemVisibility(string sceneName, string sceneItemName)
{
await GetSceneItemById(sceneName, sceneItemName, async (sceneItemId) =>
await GetSceneItemByName(sceneName, sceneItemName, async (sceneItemId) =>
{
var m1 = new RequestMessage("GetSceneItemEnabled", string.Empty, new Dictionary<string, object>() { { "sceneName", sceneName }, { "sceneItemId", sceneItemId } });
await Send(m1, async (d) =>
@ -167,7 +220,7 @@ namespace TwitchChatTTS.OBS.Socket.Manager
public async Task UpdateSceneItemVisibility(string sceneName, string sceneItemName, bool isVisible)
{
await GetSceneItemById(sceneName, sceneItemName, async (sceneItemId) =>
await GetSceneItemByName(sceneName, sceneItemName, async (sceneItemId) =>
{
var m = new RequestMessage("SetSceneItemEnabled", string.Empty, new Dictionary<string, object>() { { "sceneName", sceneName }, { "sceneItemId", sceneItemId }, { "sceneItemEnabled", isVisible } });
await Send(m);
@ -176,7 +229,7 @@ namespace TwitchChatTTS.OBS.Socket.Manager
public async Task UpdateSceneItemIndex(string sceneName, string sceneItemName, int index)
{
await GetSceneItemById(sceneName, sceneItemName, async (sceneItemId) =>
await GetSceneItemByName(sceneName, sceneItemName, async (sceneItemId) =>
{
var m = new RequestMessage("SetSceneItemIndex", string.Empty, new Dictionary<string, object>() { { "sceneName", sceneName }, { "sceneItemId", sceneItemId }, { "sceneItemIndex", index } });
await Send(m);
@ -220,7 +273,7 @@ namespace TwitchChatTTS.OBS.Socket.Manager
_logger.Debug($"Fetched the list of OBS scene items in all groups [groups: {string.Join(", ", groupNames)}]");
}
private async Task GetSceneItemById(string sceneName, string sceneItemName, Action<long> action)
private async Task GetSceneItemByName(string sceneName, string sceneItemName, Action<long> action)
{
if (_sourceIds.TryGetValue(sceneItemName, out long sourceId))
{
@ -245,18 +298,6 @@ namespace TwitchChatTTS.OBS.Socket.Manager
{
return Guid.NewGuid().ToString("N");
}
private void LogExceptions(Action action, string description)
{
try
{
action.Invoke();
}
catch (Exception e)
{
_logger.Error(e, description);
}
}
}
public class RequestData

View File

@ -8,17 +8,6 @@ namespace TwitchChatTTS.OBS.Socket
{
public class OBSSocketClient : WebSocketClient
{
private bool _live;
public bool? Live
{
get => Connected ? _live : null;
set
{
if (value.HasValue)
_live = value.Value;
}
}
public OBSSocketClient(
ILogger logger,
[FromKeyedServices("obs")] HandlerManager<WebSocketClient, IWebSocketHandler> handlerManager,
@ -29,7 +18,6 @@ namespace TwitchChatTTS.OBS.Socket
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
})
{
_live = false;
}
}
}