From 23ba9fc941c7faf7c2edb35fbc3a57f89fdc6d0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=E2=96=88=E2=96=88=E2=96=88=E2=96=88=E2=96=88?= Date: Mon, 5 May 2025 18:06:31 -0400 Subject: [PATCH 1/2] fix: Disconnect twitch client proxy nullinside-development-group/twitch-streaming-tools#11 --- .../Twitch/ITwitchClientProxy.cs | 9 +- .../Twitch/TwitchClientProxy.cs | 102 ++++++++++++------ 2 files changed, 73 insertions(+), 38 deletions(-) diff --git a/src/Nullinside.Api.Common/Twitch/ITwitchClientProxy.cs b/src/Nullinside.Api.Common/Twitch/ITwitchClientProxy.cs index 2d5958c..9d70354 100644 --- a/src/Nullinside.Api.Common/Twitch/ITwitchClientProxy.cs +++ b/src/Nullinside.Api.Common/Twitch/ITwitchClientProxy.cs @@ -39,9 +39,10 @@ public interface ITwitchClientProxy : IDisposable, IAsyncDisposable { /// /// Removes a callback for when the channel receives a new chat message. /// + /// The name of the channel to add the callback for. /// The callback to remove. /// An asynchronous task. - void RemoveMessageCallback(Action callback); + void RemoveMessageCallback(string channel, Action callback); /// /// Adds a callback for when users are banned from the chat. @@ -53,8 +54,9 @@ public interface ITwitchClientProxy : IDisposable, IAsyncDisposable { /// /// Removes a callback for when users are banned from the chat. /// + /// The name of the channel to add the callback for. /// The callback to remove from when a user is banned. - void RemoveBannedCallback(Action callback); + void RemoveBannedCallback(string channel, Action callback); /// /// Adds a callback for when the channel receives a raid. @@ -67,7 +69,8 @@ public interface ITwitchClientProxy : IDisposable, IAsyncDisposable { /// /// Removes a callback for when the channel receives a raid. /// + /// The name of the channel to add the callback for. /// The callback to remove. /// An asynchronous task. - void RemoveRaidCallback(Action callback); + void RemoveRaidCallback(string channel, Action callback); } \ No newline at end of file diff --git a/src/Nullinside.Api.Common/Twitch/TwitchClientProxy.cs b/src/Nullinside.Api.Common/Twitch/TwitchClientProxy.cs index 2405138..26c1e58 100644 --- a/src/Nullinside.Api.Common/Twitch/TwitchClientProxy.cs +++ b/src/Nullinside.Api.Common/Twitch/TwitchClientProxy.cs @@ -29,11 +29,6 @@ public class TwitchClientProxy : ITwitchClientProxy { /// private static TwitchClientProxy? instance; - /// - /// The callback(s) to invoke when a new instance is created. - /// - private static Action? onInstanceCreated; - /// /// The list of chats we attempted to join with the bot. /// @@ -61,17 +56,17 @@ public class TwitchClientProxy : ITwitchClientProxy { /// /// The callback(s) to invoke when a channel receives a chat message. /// - private Action? onMessageReceived; + private Dictionary?> _onMessageReceived = new(); /// /// The callback(s) to invoke when a channel is raided. /// - private Action? onRaid; + private Dictionary?> onRaid; /// /// The callback(s) to invoke when a channel receives a ban message. /// - private Action? onUserBanReceived; + private Dictionary?> onUserBanReceived; /// /// The web socket to connect to twitch chat with. @@ -98,7 +93,6 @@ public static TwitchClientProxy Instance { get { if (null == instance) { instance = new TwitchClientProxy(); - onInstanceCreated?.Invoke(instance); } return instance; @@ -162,39 +156,60 @@ public async Task SendMessage(string channel, string message, uint retryCo /// public async Task AddMessageCallback(string channel, Action callback) { await JoinChannel(channel); - onMessageReceived -= callback; - onMessageReceived += callback; + var channelSan = channel.ToLowerInvariant(); + lock (_onMessageReceived) { + _onMessageReceived[channelSan] = callback; + } } /// - public void RemoveMessageCallback(Action callback) { - onMessageReceived -= callback; + public void RemoveMessageCallback(string channel, Action callback) { + bool shouldRemove = false; + var channelSan = channel.ToLowerInvariant(); + lock (_onMessageReceived) { + _onMessageReceived.Remove(channel); + } + + if (shouldRemove) { + client?.LeaveChannel(channelSan); + + // First add the channel to the master list. + lock (joinedChannels) { + joinedChannels.Add(channelSan); + } + } } /// public async Task AddBannedCallback(string channel, Action callback) { await JoinChannel(channel); - onUserBanReceived -= callback; - onUserBanReceived += callback; + lock (onUserBanReceived) { + onUserBanReceived[channel] = callback; + } } /// - public void RemoveBannedCallback(Action callback) { - onUserBanReceived -= callback; + public void RemoveBannedCallback(string channel, Action callback) { + lock (onUserBanReceived) { + onUserBanReceived.Remove(channel); + } } /// public async Task AddRaidCallback(string channel, Action callback) { await JoinChannel(channel); - onRaid -= callback; - onRaid += callback; + lock (onRaid) { + onRaid[channel] = callback; + } } /// - public void RemoveRaidCallback(Action callback) { - onRaid -= callback; + public void RemoveRaidCallback(string channel, Action callback) { + lock (onRaid) { + onRaid.Remove(channel); + } } /// @@ -203,17 +218,6 @@ public ValueTask DisposeAsync() { return ValueTask.CompletedTask; } - /// - public void AddInstanceCallback(Action callback) { - onInstanceCreated -= callback; - onInstanceCreated += callback; - } - - /// - public void RemoveInstanceCallback(Action callback) { - onInstanceCreated -= callback; - } - /// /// Joins a twitch channel. /// @@ -303,6 +307,10 @@ private async Task Connect() { try { bool isConnected = false; lock (twitchClientLock) { + if (client?.IsConnected ?? false) { + return true; + } + // If this is a first time initialization, create a brand-new client. bool haveNoClient = null == client; if (haveNoClient) { @@ -377,7 +385,13 @@ private async Task Connect() { /// The twitch client. /// The event arguments. private void TwitchChatClient_OnRaidNotification(object? sender, OnRaidNotificationArgs e) { - Delegate[]? invokeList = onRaid?.GetInvocationList(); + Action? callback; + var channel = e.Channel.ToLowerInvariant(); + lock (onRaid) { + onRaid.TryGetValue(channel, out callback); + } + + Delegate[]? invokeList = callback?.GetInvocationList(); if (null == invokeList) { return; } @@ -393,7 +407,19 @@ private void TwitchChatClient_OnRaidNotification(object? sender, OnRaidNotificat /// The twitch client. /// The event arguments. private void TwitchChatClient_OnMessageReceived(object? sender, OnMessageReceivedArgs e) { - Delegate[]? invokeList = onMessageReceived?.GetInvocationList(); + Action? callbacks = null; + var channelSan = e.ChatMessage.Channel.ToLowerInvariant(); + lock (_onMessageReceived) { + if (_onMessageReceived.TryGetValue(channelSan, out Action? messageReceivedCallback)) { + callbacks = messageReceivedCallback; + } + } + + if (null == callbacks) { + return; + } + + Delegate[]? invokeList = callbacks?.GetInvocationList(); if (null == invokeList) { return; } @@ -409,7 +435,13 @@ private void TwitchChatClient_OnMessageReceived(object? sender, OnMessageReceive /// The twitch client. /// The event arguments. private void TwitchChatClient_OnUserBanned(object? sender, OnUserBannedArgs e) { - Delegate[]? invokeList = onUserBanReceived?.GetInvocationList(); + Action? callback; + var channel = e.UserBan.Channel.ToLowerInvariant(); + lock (onUserBanReceived) { + onUserBanReceived.TryGetValue(channel, out callback); + } + + Delegate[]? invokeList = callback?.GetInvocationList(); if (null == invokeList) { return; } From 3d8416a5d3781dfe4588c79444ebc42bec6ecb61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=E2=96=88=E2=96=88=E2=96=88=E2=96=88=E2=96=88?= Date: Mon, 5 May 2025 18:14:27 -0400 Subject: [PATCH 2/2] chore: cleaning up warnings in code oops --- src/Nullinside.Api.Common/Twitch/TwitchClientProxy.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Nullinside.Api.Common/Twitch/TwitchClientProxy.cs b/src/Nullinside.Api.Common/Twitch/TwitchClientProxy.cs index 26c1e58..426cde9 100644 --- a/src/Nullinside.Api.Common/Twitch/TwitchClientProxy.cs +++ b/src/Nullinside.Api.Common/Twitch/TwitchClientProxy.cs @@ -61,12 +61,12 @@ public class TwitchClientProxy : ITwitchClientProxy { /// /// The callback(s) to invoke when a channel is raided. /// - private Dictionary?> onRaid; + private Dictionary?> onRaid = new(); /// /// The callback(s) to invoke when a channel receives a ban message. /// - private Dictionary?> onUserBanReceived; + private Dictionary?> onUserBanReceived = new(); /// /// The web socket to connect to twitch chat with.