From f833b1ab1ebef37618dba3fbb1e0a661ff183af5 Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 16 Sep 2023 15:16:38 +0200 Subject: [PATCH] Updated missing Topics, updated interface (#120) * Updated Communication Library, Updated interface with missing methods, added missing topics * Added events and objects for new channel-bits-badge-unlocks and low-trust-users, cleanup * Removed OnBitsReceived event * Updated Communication library to version 2.0.0 --- .../Events/OnChannelBitsBadgeUnlockArgs.cs | 18 +++ .../Events/OnLowTrustUsersArgs.cs | 18 +++ TwitchLib.PubSub/Interfaces/ITwitchPubSub.cs | 56 ++++++- TwitchLib.PubSub/Models/Responses/Message.cs | 12 +- .../Messages/BitsBadgeNotificationMessage.cs | 29 ++++ .../Responses/Messages/ChannelBitsEvents.cs | 77 --------- .../Responses/Messages/LowTrustUsers.cs | 12 ++ TwitchLib.PubSub/TwitchLib.PubSub.csproj | 2 +- TwitchLib.PubSub/TwitchPubSub.cs | 147 ++++++++++-------- 9 files changed, 219 insertions(+), 152 deletions(-) create mode 100644 TwitchLib.PubSub/Events/OnChannelBitsBadgeUnlockArgs.cs create mode 100644 TwitchLib.PubSub/Events/OnLowTrustUsersArgs.cs create mode 100644 TwitchLib.PubSub/Models/Responses/Messages/BitsBadgeNotificationMessage.cs delete mode 100644 TwitchLib.PubSub/Models/Responses/Messages/ChannelBitsEvents.cs create mode 100644 TwitchLib.PubSub/Models/Responses/Messages/LowTrustUsers.cs diff --git a/TwitchLib.PubSub/Events/OnChannelBitsBadgeUnlockArgs.cs b/TwitchLib.PubSub/Events/OnChannelBitsBadgeUnlockArgs.cs new file mode 100644 index 0000000..daa43d0 --- /dev/null +++ b/TwitchLib.PubSub/Events/OnChannelBitsBadgeUnlockArgs.cs @@ -0,0 +1,18 @@ +using System; +using TwitchLib.PubSub.Models.Responses.Messages; + +namespace TwitchLib.PubSub.Events +{ + public class OnChannelBitsBadgeUnlockArgs : EventArgs + { + /// + /// The subscription + /// + public BitsBadgeNotificationMessage BitsBadgeUnlocks; + + /// + /// The channel ID the event came from + /// + public string ChannelId; + } +} \ No newline at end of file diff --git a/TwitchLib.PubSub/Events/OnLowTrustUsersArgs.cs b/TwitchLib.PubSub/Events/OnLowTrustUsersArgs.cs new file mode 100644 index 0000000..0b3e046 --- /dev/null +++ b/TwitchLib.PubSub/Events/OnLowTrustUsersArgs.cs @@ -0,0 +1,18 @@ +using System; +using TwitchLib.PubSub.Models.Responses.Messages; + +namespace TwitchLib.PubSub.Events +{ + public class OnLowTrustUsersArgs : EventArgs + { + /// + /// The subscription + /// + public LowTrustUsers LowTrustUsers; + + /// + /// The channel ID the event came from + /// + public string ChannelId; + } +} \ No newline at end of file diff --git a/TwitchLib.PubSub/Interfaces/ITwitchPubSub.cs b/TwitchLib.PubSub/Interfaces/ITwitchPubSub.cs index 0390295..2be0c14 100644 --- a/TwitchLib.PubSub/Interfaces/ITwitchPubSub.cs +++ b/TwitchLib.PubSub/Interfaces/ITwitchPubSub.cs @@ -9,14 +9,28 @@ namespace TwitchLib.PubSub.Interfaces /// public interface ITwitchPubSub { + /// + /// Fires when a moderation event hits a user + /// + event EventHandler OnAutomodCaughtUserMessage; + + /// + /// Fires when Automod updates a held message. + /// + event EventHandler OnAutomodCaughtMessage; + /// /// Occurs when [on ban]. /// event EventHandler OnBan; /// - /// Occurs when [on bits received]. + /// Fires when PubSub receives a bits message. + /// + event EventHandler OnBitsReceivedV2; + /// + /// Fires when PubSub receives notice when the channel unlocks bit badge. /// - event EventHandler OnBitsReceived; + event EventHandler OnChannelBitsBadgeUnlock; /// /// Occurs when [on channel extension broadcast]. /// @@ -54,6 +68,10 @@ public interface ITwitchPubSub /// event EventHandler OnListenResponse; /// + /// Fires when PubSub receives notice when the channel detects low trust user. + /// + event EventHandler OnLowTrustUsers; + /// /// Occurs when [on pub sub service closed]. /// event EventHandler OnPubSubServiceClosed; @@ -74,6 +92,10 @@ public interface ITwitchPubSub /// event EventHandler OnR9kBetaOff; /// + /// Fires when PubSub receives notice when a channel cancels the raid + /// + event EventHandler OnRaidCancel; + /// /// Occurs when [on stream down]. /// event EventHandler OnStreamDown; @@ -186,12 +208,6 @@ public interface ITwitchPubSub /// Task DisconnectAsync(); - /// - /// Listens to bits events. - /// - /// The channel twitch identifier. - [Obsolete("This topic is deprecated by Twitch. Please use ListenToBitsEventsV2()", false)] - void ListenToBitsEvents(string channelTwitchId); /// /// Listens to bits events. /// @@ -255,6 +271,30 @@ public interface ITwitchPubSub /// /// The channel twitch identifier. void ListenToPredictions(string channelTwitchId); + /// + /// A user’s message held by AutoMod has been approved or denied. + /// + /// Current user identifier. + /// The channel twitch identifier. + void ListenToUserModerationNotifications(string myTwitchId, string channelTwitchId); + /// + /// Sends a request to listen to Automod queued messages in a specific channel + /// + /// A moderator's twitch account's ID + /// Channel ID who has previous parameter's moderator + void ListenToAutomodQueue(string userTwitchId, string channelTwitchId); + /// + /// Message sent when a user earns a new Bits badge in a particular channel, and chooses to share the notification with chat. + /// + /// The channel twitch identifier. + void ListenToChannelBitsBadgeUnlocks(string channelTwitchId); + /// + /// The broadcaster or a moderator updates the low trust status of a user, or a new message has been sent in chat by a potential ban evader or a bans shared user. + /// + /// The channel twitch identifier. + /// Suspicious user identifier. + void ListenToLowTrustUsers(string channelTwitchId, string suspiciousUser); + /// /// Sends the topics. /// diff --git a/TwitchLib.PubSub/Models/Responses/Message.cs b/TwitchLib.PubSub/Models/Responses/Message.cs index c4b8bbe..3dcbf40 100644 --- a/TwitchLib.PubSub/Models/Responses/Message.cs +++ b/TwitchLib.PubSub/Models/Responses/Message.cs @@ -1,7 +1,6 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using TwitchLib.PubSub.Models.Responses.Messages; -using TwitchLib.PubSub.Models.Responses.Messages.AutomodCaughtMessage; using TwitchLib.PubSub.Models.Responses.Messages.UserModerationNotifications; namespace TwitchLib.PubSub.Models.Responses @@ -41,9 +40,6 @@ public Message(string jsonStr) case "chat_moderator_actions": MessageData = new ChatModeratorActions(encodedJsonMessage); break; - case "channel-bits-events-v1": - MessageData = new ChannelBitsEvents(encodedJsonMessage); - break; case "channel-bits-events-v2": encodedJsonMessage = encodedJsonMessage.Replace("\\", ""); var dataEncoded = JObject.Parse(encodedJsonMessage)["data"].ToString(); @@ -79,6 +75,14 @@ public Message(string jsonStr) case "predictions-channel-v1": MessageData = new PredictionEvents(encodedJsonMessage); break; + case "channel-bits-badge-unlocks": + encodedJsonMessage = encodedJsonMessage.Replace("\\", ""); + var channelBitsBadgeData = JObject.Parse(encodedJsonMessage)["data"].ToString(); + MessageData = JsonConvert.DeserializeObject(channelBitsBadgeData); + break; + case "low-trust-users": + MessageData = new LowTrustUsers(encodedJsonMessage); + break; } } } diff --git a/TwitchLib.PubSub/Models/Responses/Messages/BitsBadgeNotificationMessage.cs b/TwitchLib.PubSub/Models/Responses/Messages/BitsBadgeNotificationMessage.cs new file mode 100644 index 0000000..fb859cd --- /dev/null +++ b/TwitchLib.PubSub/Models/Responses/Messages/BitsBadgeNotificationMessage.cs @@ -0,0 +1,29 @@ +using System; +using Newtonsoft.Json; + +namespace TwitchLib.PubSub.Models.Responses.Messages +{ + public class BitsBadgeNotificationMessage : MessageData + { + [JsonProperty(PropertyName = "user_id")] + public string UserId { get; protected set; } + + [JsonProperty(PropertyName = "user_name")] + public string UserName { get; protected set; } + + [JsonProperty(PropertyName = "channel_id")] + public string ChannelId { get; protected set; } + + [JsonProperty(PropertyName = "channel_name")] + public string ChannelName { get; protected set; } + + [JsonProperty(PropertyName = "badge_tier")] + public int BadgeTier { get; protected set; } + + [JsonProperty(PropertyName = "chat_message")] + public string ChatMessage { get; protected set; } + + [JsonProperty(PropertyName = "time")] + public DateTime Time { get; protected set; } + } +} \ No newline at end of file diff --git a/TwitchLib.PubSub/Models/Responses/Messages/ChannelBitsEvents.cs b/TwitchLib.PubSub/Models/Responses/Messages/ChannelBitsEvents.cs deleted file mode 100644 index 17d7111..0000000 --- a/TwitchLib.PubSub/Models/Responses/Messages/ChannelBitsEvents.cs +++ /dev/null @@ -1,77 +0,0 @@ -using Newtonsoft.Json.Linq; - -namespace TwitchLib.PubSub.Models.Responses.Messages -{ - /// - /// Model representing the data in a channel bits event. - /// Implements the - /// - /// - /// - public class ChannelBitsEvents : MessageData - { - /// - /// Username of the sender. - /// - /// The username. - public string Username { get; } - /// - /// The channel the bits were sent to. - /// - /// The name of the channel. - public string ChannelName { get; } - /// - /// User ID of the sender. - /// - /// The user identifier. - public string UserId { get; } - /// - /// Channel/User ID of where the bits were sent to. - /// - /// The channel identifier. - public string ChannelId { get; } - /// - /// Time stamp of the event. - /// - /// The time. - public string Time { get; } - /// - /// Chat message that accompanied the bits. - /// - /// The chat message. - public string ChatMessage { get; } - /// - /// The amount of bits sent. - /// - /// The bits used. - public int BitsUsed { get; } - /// - /// The total amount of bits the user has sent. - /// - /// The total bits used. - public int TotalBitsUsed { get; } - /// - /// Context related to event. - /// - /// The context. - public string Context { get; } - - /// - /// ChannelBitsEvent model constructor. - /// - /// The json string. - public ChannelBitsEvents(string jsonStr) - { - var json = JObject.Parse(jsonStr); - Username = json.SelectToken("data").SelectToken("user_name")?.ToString(); - ChannelName = json.SelectToken("data").SelectToken("channel_name")?.ToString(); - UserId = json.SelectToken("data").SelectToken("user_id")?.ToString(); - ChannelId = json.SelectToken("data").SelectToken("channel_id")?.ToString(); - Time = json.SelectToken("data").SelectToken("time")?.ToString(); - ChatMessage = json.SelectToken("data").SelectToken("chat_message")?.ToString(); - BitsUsed = int.Parse(json.SelectToken("data").SelectToken("bits_used").ToString()); - TotalBitsUsed = int.Parse(json.SelectToken("data").SelectToken("total_bits_used").ToString()); - Context = json.SelectToken("data").SelectToken("context")?.ToString(); - } - } -} diff --git a/TwitchLib.PubSub/Models/Responses/Messages/LowTrustUsers.cs b/TwitchLib.PubSub/Models/Responses/Messages/LowTrustUsers.cs new file mode 100644 index 0000000..c1b6260 --- /dev/null +++ b/TwitchLib.PubSub/Models/Responses/Messages/LowTrustUsers.cs @@ -0,0 +1,12 @@ +namespace TwitchLib.PubSub.Models.Responses.Messages +{ + public class LowTrustUsers : MessageData + { + public string RawData { get; private set; } + + public LowTrustUsers(string jsonString) + { + RawData = jsonString; + } + } +} \ No newline at end of file diff --git a/TwitchLib.PubSub/TwitchLib.PubSub.csproj b/TwitchLib.PubSub/TwitchLib.PubSub.csproj index 67f3972..cb821cf 100644 --- a/TwitchLib.PubSub/TwitchLib.PubSub.csproj +++ b/TwitchLib.PubSub/TwitchLib.PubSub.csproj @@ -25,6 +25,6 @@ - + diff --git a/TwitchLib.PubSub/TwitchPubSub.cs b/TwitchLib.PubSub/TwitchPubSub.cs index a019d80..8136f83 100644 --- a/TwitchLib.PubSub/TwitchPubSub.cs +++ b/TwitchLib.PubSub/TwitchPubSub.cs @@ -29,6 +29,13 @@ namespace TwitchLib.PubSub /// public class TwitchPubSub : ITwitchPubSub { + private const string PingPayload = "{ \"type\": \"PING\" }"; + + /// + /// The random + /// + private static readonly Random Random = new Random(); + /// /// The socket /// @@ -152,11 +159,6 @@ public class TwitchPubSub : ITwitchPubSub public event EventHandler OnR9kBetaOff; /// /// - /// Fires when PubSub receives notice of a bit donation. - /// - public event EventHandler OnBitsReceived; - /// - /// /// Fires when PubSub receives a bits message. /// public event EventHandler OnBitsReceivedV2; @@ -187,6 +189,16 @@ public class TwitchPubSub : ITwitchPubSub public event EventHandler OnChannelSubscription; /// /// + /// Fires when PubSub receives notice when the channel unlocks bit badge. + /// + public event EventHandler OnChannelBitsBadgeUnlock; + /// + /// + /// Fires when PubSub receives notice when the channel detects low trust user. + /// + public event EventHandler OnLowTrustUsers; + /// + /// /// Fires when PubSub receives a message sent to the specified extension on the specified channel. /// public event EventHandler OnChannelExtensionBroadcast; @@ -292,10 +304,10 @@ public TwitchPubSub(ILogger logger = null) var options = new ClientOptions(clientType: ClientType.PubSub); _socket = new WebSocketClient(options); - _socket.OnConnected += Socket_OnConnected; - _socket.OnError += OnError; - _socket.OnMessage += OnMessage; - _socket.OnDisconnected += Socket_OnDisconnected; + _socket.OnConnected += Socket_OnConnectedAsync; + _socket.OnError += OnErrorAsync; + _socket.OnMessage += OnMessageAsync; + _socket.OnDisconnected += Socket_OnDisconnectedAsync; _pongTimer.Interval = 15000; //15 seconds, we should get a pong back within 10 seconds. _pongTimer.Elapsed += PongTimerTickAsync; @@ -306,10 +318,12 @@ public TwitchPubSub(ILogger logger = null) /// /// The sender. /// The instance containing the event data. - private void OnError(object sender, OnErrorEventArgs e) + private Task OnErrorAsync(object sender, OnErrorEventArgs e) { _logger?.LogError($"OnError in PubSub Websocket connection occured! Exception: {e.Exception}"); OnPubSubServiceError?.Invoke(this, new OnPubSubServiceErrorArgs { Exception = e.Exception }); + + return Task.CompletedTask; } /// @@ -317,11 +331,11 @@ private void OnError(object sender, OnErrorEventArgs e) /// /// The sender. /// The instance containing the event data. - private void OnMessage(object sender, OnMessageEventArgs e) + private Task OnMessageAsync(object sender, OnMessageEventArgs e) { _logger?.LogDebug($"Received Websocket OnMessage: {e.Message}"); OnLog?.Invoke(this, new OnLogArgs { Data = e.Message }); - ParseMessage(e.Message); + return ParseMessageAsync(e.Message); } /// @@ -329,12 +343,14 @@ private void OnMessage(object sender, OnMessageEventArgs e) /// /// The source of the event. /// The instance containing the event data. - private void Socket_OnDisconnected(object sender, EventArgs e) + private Task Socket_OnDisconnectedAsync(object sender, EventArgs e) { _logger?.LogWarning("PubSub Websocket connection closed"); _pingTimer.Stop(); _pongTimer.Stop(); OnPubSubServiceClosed?.Invoke(this, null); + + return Task.CompletedTask; } /// @@ -342,15 +358,17 @@ private void Socket_OnDisconnected(object sender, EventArgs e) /// /// The source of the event. /// The instance containing the event data. - private void Socket_OnConnected(object sender, EventArgs e) + private Task Socket_OnConnectedAsync(object sender, EventArgs e) { _logger?.LogInformation("PubSub Websocket connection established"); _pingTimer.Interval = 180000; _pingTimer.Elapsed += PingTimerTickAsync; _pingTimer.Start(); OnPubSubServiceConnected?.Invoke(this, null); - } + return Task.CompletedTask; + } + /// /// Pings the timer tick. /// @@ -362,11 +380,7 @@ private async void PingTimerTickAsync(object sender, ElapsedEventArgs e) _pongReceived = false; //Send ping. - var data = new JObject( - new JProperty("type", "PING") - ); - - await _socket.SendAsync(data.ToString()); + await _socket.SendAsync(PingPayload); //Start pong timer. _pongTimer.Start(); @@ -398,7 +412,7 @@ private async void PongTimerTickAsync(object sender, ElapsedEventArgs e) /// Parses the message. /// /// The message. - private void ParseMessage(string message) + private async Task ParseMessageAsync(string message) { var type = JObject.Parse(message).SelectToken("type")?.ToString(); @@ -471,6 +485,14 @@ private void ParseMessage(string message) var subscription = msg.MessageData as ChannelSubscription; OnChannelSubscription?.Invoke(this, new OnChannelSubscriptionArgs { Subscription = subscription, ChannelId = channelId }); return; + case "channel-bits-badge-unlocks": + var channelBitsBadgeUnlocks = msg.MessageData as BitsBadgeNotificationMessage; + OnChannelBitsBadgeUnlock?.Invoke(this, new OnChannelBitsBadgeUnlockArgs { BitsBadgeUnlocks = channelBitsBadgeUnlocks, ChannelId = channelId }); + break; + case "low-trust-users": + var lowTrustUsers = msg.MessageData as LowTrustUsers; + OnLowTrustUsers?.Invoke(this, new OnLowTrustUsersArgs { LowTrustUsers = lowTrustUsers, ChannelId = channelId }); + break; case "whispers": var whisper = (Whisper)msg.MessageData; OnWhisper?.Invoke(this, new OnWhisperArgs { Whisper = whisper, ChannelId = channelId }); @@ -534,24 +556,6 @@ private void ParseMessage(string message) return; } break; - case "channel-bits-events-v1": - if (msg.MessageData is ChannelBitsEvents cbe) - { - OnBitsReceived?.Invoke(this, new OnBitsReceivedArgs - { - BitsUsed = cbe.BitsUsed, - ChannelId = cbe.ChannelId, - ChannelName = cbe.ChannelName, - ChatMessage = cbe.ChatMessage, - Context = cbe.Context, - Time = cbe.Time, - TotalBitsUsed = cbe.TotalBitsUsed, - UserId = cbe.UserId, - Username = cbe.Username - }); - return; - } - break; case "channel-bits-events-v2": if (msg.MessageData is ChannelBitsEventsV2 cbev2) { @@ -683,18 +687,13 @@ private void ParseMessage(string message) _pongReceived = true; return; case "reconnect": - // This does not fit here. This method parses message, it shouldn't do any action. - // TODO: Fire event to trigger socket close - _socket.CloseAsync().GetAwaiter().GetResult(); + await _socket.CloseAsync(); break; } + UnaccountedFor(message); } - - /// - /// The random - /// - private static readonly Random Random = new Random(); + /// /// Generates the nonce. /// @@ -798,6 +797,7 @@ private void UnaccountedFor(string message) /// The channel identifier. public void ListenToFollows(string channelId) { + // This topic is not documented var topic = $"following.{channelId}"; _topicToChannelId[topic] = channelId; ListenToTopic(topic); @@ -816,6 +816,12 @@ public void ListenToChatModeratorActions(string userId, string channelId) ListenToTopic(topic); } + /// + /// + /// A user’s message held by AutoMod has been approved or denied. + /// + /// Current user identifier. + /// The channel twitch identifier. public void ListenToUserModerationNotifications(string myTwitchId, string channelTwitchId) { var topic = $"user-moderation-notifications.{myTwitchId}.{channelTwitchId}"; @@ -844,24 +850,12 @@ public void ListenToAutomodQueue(string userTwitchId, string channelTwitchId) /// The extension identifier. public void ListenToChannelExtensionBroadcast(string channelId, string extensionId) { + // This topic is not documented var topic = $"channel-ext-v1.{channelId}-{extensionId}-broadcast"; _topicToChannelId[topic] = channelId; ListenToTopic(topic); } - /// - /// - /// Sends request to listenOn bits events in specific channel - /// - /// Channel Id of channel to listen to bits on (can be fetched from TwitchApi) - [Obsolete("This topic is deprecated by Twitch. Please use ListenToBitsEventsV2()", false)] - public void ListenToBitsEvents(string channelTwitchId) - { - var topic = $"channel-bits-events-v1.{channelTwitchId}"; - _topicToChannelId[topic] = channelTwitchId; - ListenToTopic(topic); - } - /// /// /// Sends request to listen to bits events in specific channel @@ -881,6 +875,7 @@ public void ListenToBitsEventsV2(string channelTwitchId) /// Id of channel to listen to playback events in. public void ListenToVideoPlayback(string channelTwitchId) { + // This topic is not documented var topic = $"video-playback-by-id.{channelTwitchId}"; _topicToChannelId[topic] = channelTwitchId; ListenToTopic(topic); @@ -930,6 +925,7 @@ public void ListenToChannelPoints(string channelTwitchId) /// Channel to listen to leaderboards on. public void ListenToLeaderboards(string channelTwitchId) { + // These topics are not documented var topicBits = $"leaderboard-events-v1.bits-usage-by-channel-v1-{channelTwitchId}-WEEK"; var topicSubs = $"leaderboard-events-v1.sub-gift-sent-{channelTwitchId}-WEEK"; _topicToChannelId[topicBits] = channelTwitchId; @@ -944,6 +940,7 @@ public void ListenToLeaderboards(string channelTwitchId) /// Channel to listen to raids get prepared on. public void ListenToRaid(string channelTwitchId) { + // This topic is not documented var topicRaid = $"raid.{channelTwitchId}"; _topicToChannelId[topicRaid] = channelTwitchId; ListenToTopic(topicRaid); @@ -965,13 +962,39 @@ public void ListenToSubscriptions(string channelId) /// /// Sends request to listen to channel predictions. /// - /// + /// The channel twitch identifier. public void ListenToPredictions(string channelTwitchId) { + // This topic is not documented var topic = $"predictions-channel-v1.{channelTwitchId}"; _topicToChannelId[topic] = channelTwitchId; ListenToTopic(topic); } + + /// + /// + /// Message sent when a user earns a new Bits badge in a particular channel, and chooses to share the notification with chat. + /// + /// The channel twitch identifier. + public void ListenToChannelBitsBadgeUnlocks(string channelTwitchId) + { + var topic = $"channel-bits-badge-unlocks.{channelTwitchId}"; + _topicToChannelId[topic] = channelTwitchId; + ListenToTopic(topic); + } + + /// + /// + /// The broadcaster or a moderator updates the low trust status of a user, or a new message has been sent in chat by a potential ban evader or a bans shared user. + /// + /// The channel twitch identifier. + /// Suspicious user identifier. + public void ListenToLowTrustUsers(string channelTwitchId, string suspiciousUser) + { + var topic = $"low-trust-users.{channelTwitchId}.{suspiciousUser}"; + _topicToChannelId[topic] = channelTwitchId; + ListenToTopic(topic); + } #endregion /// @@ -1008,7 +1031,7 @@ public async Task DisconnectAsync() /// The test json string. public void TestMessageParser(string testJsonString) { - ParseMessage(testJsonString); + ParseMessageAsync(testJsonString).GetAwaiter().GetResult(); } } }