From cd12b4c319e84dc12c049c21d3593039a65c2d3e Mon Sep 17 00:00:00 2001 From: Uzair Shamim Date: Fri, 13 Oct 2023 00:57:31 -0400 Subject: [PATCH] Add NOTICE message support with tests --- .../TwitchConnectorTests/ClearChatTests.cs | 4 +- .../TwitchConnectorTests/ClearMsgTests.cs | 4 +- .../TwitchConnectorTests/NoticeTests.cs | 99 +++++++++++++++++++ .../TwitchConnectorTests/PrivMsgTests.cs | 4 +- .../Implementation/TwitchConnector.cs | 29 ++++-- .../Implementation/UserMessage.cs | 7 -- TwitchEverywhere/TwitchEverywhere.csproj | 8 +- TwitchEverywhere/Types/Notice.cs | 6 ++ 8 files changed, 140 insertions(+), 21 deletions(-) create mode 100644 TwitchEverywhere.UnitTests/TwitchConnectorTests/NoticeTests.cs delete mode 100644 TwitchEverywhere/Implementation/UserMessage.cs create mode 100644 TwitchEverywhere/Types/Notice.cs diff --git a/TwitchEverywhere.UnitTests/TwitchConnectorTests/ClearChatTests.cs b/TwitchEverywhere.UnitTests/TwitchConnectorTests/ClearChatTests.cs index 48e0447..87d9942 100644 --- a/TwitchEverywhere.UnitTests/TwitchConnectorTests/ClearChatTests.cs +++ b/TwitchEverywhere.UnitTests/TwitchConnectorTests/ClearChatTests.cs @@ -37,9 +37,9 @@ Message message switch( message.MessageType ) { case MessageType.ClearChat: { - ClearChat privMsg = (ClearChat)message; + ClearChat msg = (ClearChat)message; ClearChat? expectedPrivMessage = (ClearChat)expectedMessage; - ClearChatMessageCallback( privMsg, expectedPrivMessage ); + ClearChatMessageCallback( msg, expectedPrivMessage ); break; } case MessageType.PrivMsg: diff --git a/TwitchEverywhere.UnitTests/TwitchConnectorTests/ClearMsgTests.cs b/TwitchEverywhere.UnitTests/TwitchConnectorTests/ClearMsgTests.cs index 07bc4e1..cf6057f 100644 --- a/TwitchEverywhere.UnitTests/TwitchConnectorTests/ClearMsgTests.cs +++ b/TwitchEverywhere.UnitTests/TwitchConnectorTests/ClearMsgTests.cs @@ -36,9 +36,9 @@ Message message switch( message.MessageType ) { case MessageType.ClearMsg: { - ClearMsg privMsg = (ClearMsg)message; + ClearMsg msg = (ClearMsg)message; ClearMsg? expectedPrivMessage = (ClearMsg)expectedMessage; - ClearMsgMessageCallback( privMsg, expectedPrivMessage ); + ClearMsgMessageCallback( msg, expectedPrivMessage ); break; } case MessageType.PrivMsg: diff --git a/TwitchEverywhere.UnitTests/TwitchConnectorTests/NoticeTests.cs b/TwitchEverywhere.UnitTests/TwitchConnectorTests/NoticeTests.cs new file mode 100644 index 0000000..28bc048 --- /dev/null +++ b/TwitchEverywhere.UnitTests/TwitchConnectorTests/NoticeTests.cs @@ -0,0 +1,99 @@ +using System.Collections.Immutable; +using Moq; +using NUnit.Framework.Internal; +using TwitchEverywhere.Implementation; +using TwitchEverywhere.Types; + +namespace TwitchEverywhere.UnitTests.TwitchConnectorTests; + +[TestFixture] +public class NoticeTests { + private readonly TwitchConnectionOptions m_options = new( + "channel", + "access_token", + "refresh_token", + "client_id", + "client_secret", + "client_name" + ); + + private readonly DateTime m_startTime = DateTimeOffset.FromUnixTimeMilliseconds(1507246572675).UtcDateTime; + + private ITwitchConnector m_twitchConnector; + + [Test] + [TestCaseSource(nameof(NoticeMessages))] + public async Task Notice( IImmutableList messages, Message? expectedMessage ) { + Mock authorizer = new( behavior: MockBehavior.Strict ); + Mock dateTimeService = new( MockBehavior.Strict ); + dateTimeService.Setup( dts => dts.GetStartTime() ).Returns( m_startTime ); + + IWebSocketConnection webSocket = new TestWebSocketConnection( messages ); + + void MessageCallback( + Message message + ) { + Assert.That( message, Is.Not.Null ); + + switch( message.MessageType ) { + case MessageType.Notice: { + Notice msg = (Notice)message; + Notice? expectedPrivMessage = (Notice)expectedMessage; + NoticeMessageCallback( msg, expectedPrivMessage ); + break; + } + case MessageType.PrivMsg: + case MessageType.ClearMsg: + case MessageType.ClearChat: + case MessageType.GlobalUserState: + case MessageType.RoomState: + case MessageType.UserNotice: + case MessageType.UserState: + case MessageType.Whisper: + default: + throw new ArgumentOutOfRangeException(); + } + } + + authorizer.Setup( expression: a => a.GetToken() ).ReturnsAsync( value: "token" ); + m_twitchConnector = new TwitchConnector( + authorizer: authorizer.Object, + webSocketConnection: webSocket, + dateTimeService: dateTimeService.Object + ); + + bool result = await m_twitchConnector.TryConnect( m_options, MessageCallback ); + Assert.That( result, Is.True ); + } + + private void NoticeMessageCallback( + Notice notice, + Notice? expectedNoticeMessage + ) { + Assert.That( notice.MsgId, Is.EqualTo( expectedNoticeMessage?.MsgId ), "MsgId was not equal to expected value"); + Assert.That( notice.TargetUserId, Is.EqualTo( expectedNoticeMessage?.TargetUserId ), "TargetUserId was not equal to expected value"); + Assert.That( notice.MessageType, Is.EqualTo( expectedNoticeMessage?.MessageType ), "MessageType was not equal to expected value"); + } + + private static IEnumerable NoticeMessages() { + yield return new TestCaseData( + new List { + $"@msg-id=delete_message_success :tmi.twitch.tv NOTICE #channel :The message from foo is now deleted." + }.ToImmutableList(), + new Notice( + MsgId: "delete_message_success", + TargetUserId: "" + ) + ).SetName("Message Delete Success, No User ID"); + + yield return new TestCaseData( + new List { + $"@msg-id=whisper_restricted;target-user-id=12345678 :tmi.twitch.tv NOTICE #channel :Your settings prevent you from sending this whisper." + }.ToImmutableList(), + new Notice( + MsgId: "whisper_restricted", + TargetUserId: "12345678" + ) + ).SetName("Whisper Restricted, With User ID"); + } +} \ No newline at end of file diff --git a/TwitchEverywhere.UnitTests/TwitchConnectorTests/PrivMsgTests.cs b/TwitchEverywhere.UnitTests/TwitchConnectorTests/PrivMsgTests.cs index 8984777..0a06479 100644 --- a/TwitchEverywhere.UnitTests/TwitchConnectorTests/PrivMsgTests.cs +++ b/TwitchEverywhere.UnitTests/TwitchConnectorTests/PrivMsgTests.cs @@ -37,9 +37,9 @@ Message message switch( message.MessageType ) { case MessageType.PrivMsg: { - PrivMsg privMsg = (PrivMsg)message; + PrivMsg msg = (PrivMsg)message; PrivMsg? expectedPrivMessage = (PrivMsg)expectedMessage; - PrivMessageCallback( privMsg, expectedPrivMessage ); + PrivMessageCallback( msg, expectedPrivMessage ); break; } case MessageType.ClearChat: diff --git a/TwitchEverywhere/Implementation/TwitchConnector.cs b/TwitchEverywhere/Implementation/TwitchConnector.cs index 8529a27..cd93c8f 100644 --- a/TwitchEverywhere/Implementation/TwitchConnector.cs +++ b/TwitchEverywhere/Implementation/TwitchConnector.cs @@ -105,14 +105,27 @@ await ws.CloseAsync( Message chat = GetClearChatMessage( response, options.Channel ); m_messageCallback( chat ); } else if( response.Contains( $" CLEARMSG #{options.Channel}" ) ) { - Message chat = GetClearMsgMessage( response, options.Channel ); + Message chat = GetClearMsgMessage( response ); + m_messageCallback( chat ); + } else if( response.Contains( $" NOTICE #{options.Channel}" ) ) { + Message chat = GetNoticeMsgMessage( response ); m_messageCallback( chat ); } } } + private Message GetNoticeMsgMessage( + string response + ) { + string targetMessageId = GetValueFromResponse( response, MsgIdPattern ); + string targetUserId = GetValueFromResponse( response, TargetUserIdPattern ); + + return new Notice( + MsgId: targetMessageId, + TargetUserId: targetUserId + ); + } private Message GetClearMsgMessage( - string response, - string channel + string response ) { string login = LoginPattern .Match( response ) @@ -169,7 +182,10 @@ string channel ); } - private Message GetUserMessage( string response, string channel ) { + private Message GetUserMessage( + string response, + string channel + ) { string[] segments = response.Split( $"PRIVMSG #{channel} :" ); string displayName = GetValueFromResponse( response, DisplayNamePattern ); @@ -268,7 +284,7 @@ string emotesText if( string.IsNullOrEmpty( emotesText ) ) { return null; } -// 25 : 0-4 , 12-16 / 1902 : 6-10 + List emotes = new(); string[] separatedRawEmotes = emotesText.Split( "/" ); @@ -399,5 +415,6 @@ await socketConnection.SendAsync( private readonly static Regex UserTypePattern = new("user-type=([^; ]+)"); private readonly static Regex VipPattern = new("vip=([^;]*)"); private readonly static Regex BanDurationPattern = new("ban-duration=([^;]*)"); - private readonly static Regex TargetUserIdPattern = new("target-user-id=([^;]*)"); + private readonly static Regex TargetUserIdPattern = new("target-user-id=([^ ;]*)"); + private readonly static Regex MsgIdPattern = new("msg-id=([^ ;]*)"); } diff --git a/TwitchEverywhere/Implementation/UserMessage.cs b/TwitchEverywhere/Implementation/UserMessage.cs deleted file mode 100644 index fe78e92..0000000 --- a/TwitchEverywhere/Implementation/UserMessage.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace TwitchEverywhere.Implementation; - -public record UserMessage( - string DisplayName, - string Message, - DateTime Timestamp -); \ No newline at end of file diff --git a/TwitchEverywhere/TwitchEverywhere.csproj b/TwitchEverywhere/TwitchEverywhere.csproj index f1c0468..ecf9b82 100644 --- a/TwitchEverywhere/TwitchEverywhere.csproj +++ b/TwitchEverywhere/TwitchEverywhere.csproj @@ -10,9 +10,13 @@ enable enable 11 - 0.1.0 - ../README.md + 0.1.1 + README.md net7.0;net6.0 + + + + diff --git a/TwitchEverywhere/Types/Notice.cs b/TwitchEverywhere/Types/Notice.cs new file mode 100644 index 0000000..9aaddde --- /dev/null +++ b/TwitchEverywhere/Types/Notice.cs @@ -0,0 +1,6 @@ +namespace TwitchEverywhere.Types; + +public record Notice( + string MsgId, + string TargetUserId +) : Message ( MessageType.Notice ); \ No newline at end of file