diff --git a/MiniTwitch.Irc.Benchmarks/Benchmarks.cs b/MiniTwitch.Irc.Benchmarks/Benchmarks.cs
new file mode 100644
index 0000000..ad2d4c9
--- /dev/null
+++ b/MiniTwitch.Irc.Benchmarks/Benchmarks.cs
@@ -0,0 +1,53 @@
+using System.Text;
+using BenchmarkDotNet.Attributes;
+using MiniTwitch.Irc.Internal.Models;
+
+namespace MiniTwitch.Irc.Benchmarks;
+
+[MemoryDiagnoser(false)]
+public class Benchmarks
+{
+ private byte[][] utf8Lines = null!;
+ private readonly IrcClient _client = new(op => op.Anonymous = true);
+
+ [Params(1, 100, 1000)]
+ public int LineCount { get; set; }
+
+ [GlobalSetup]
+ public void AddData()
+ {
+ utf8Lines = File.ReadAllText("data.txt").Split('\0').Take(LineCount).Select(Encoding.UTF8.GetBytes).ToArray();
+ Console.WriteLine(utf8Lines.Length);
+ }
+
+ [Benchmark]
+ public void Create()
+ {
+ foreach (var line in utf8Lines.AsSpan())
+ {
+ _ = new IrcMessage(line);
+ }
+ }
+
+ [Benchmark]
+ public void Create_Parse()
+ {
+ foreach (var line in utf8Lines.AsSpan())
+ {
+ var msg = new IrcMessage(line);
+ _ = msg.ParseTags();
+ _ = msg.GetChannel();
+ _ = msg.GetContent();
+ _ = msg.GetUsername();
+ }
+ }
+
+ [Benchmark]
+ public void Create_Parse_Map_Send()
+ {
+ foreach (var line in utf8Lines.AsSpan())
+ {
+ _client.Parse(line);
+ }
+ }
+}
diff --git a/MiniTwitch.Irc.Benchmarks/MiniTwitch.Irc.Benchmarks.csproj b/MiniTwitch.Irc.Benchmarks/MiniTwitch.Irc.Benchmarks.csproj
new file mode 100644
index 0000000..aa43ff6
--- /dev/null
+++ b/MiniTwitch.Irc.Benchmarks/MiniTwitch.Irc.Benchmarks.csproj
@@ -0,0 +1,22 @@
+
+
+
+ Exe
+ net7.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MiniTwitch.Irc.Benchmarks/Program.cs b/MiniTwitch.Irc.Benchmarks/Program.cs
new file mode 100644
index 0000000..7d49125
--- /dev/null
+++ b/MiniTwitch.Irc.Benchmarks/Program.cs
@@ -0,0 +1,4 @@
+using BenchmarkDotNet.Running;
+using MiniTwitch.Irc.Benchmarks;
+
+BenchmarkRunner.Run();
diff --git a/MiniTwitch.Irc.Benchmarks/data.txt b/MiniTwitch.Irc.Benchmarks/data.txt
new file mode 100644
index 0000000..01d5d6b
Binary files /dev/null and b/MiniTwitch.Irc.Benchmarks/data.txt differ
diff --git a/MiniTwitch.Irc.Test/CommandParsingTests.cs b/MiniTwitch.Irc.Test/CommandParsingTests.cs
index 79adb48..fbc2501 100644
--- a/MiniTwitch.Irc.Test/CommandParsingTests.cs
+++ b/MiniTwitch.Irc.Test/CommandParsingTests.cs
@@ -1,5 +1,6 @@
using System.Text;
using MiniTwitch.Irc.Internal.Enums;
+using MiniTwitch.Irc.Internal.Models;
using MiniTwitch.Irc.Internal.Parsing;
using Xunit;
@@ -10,78 +11,67 @@ public class CommandParsingTests
public void Parse_PRIVMSG()
{
string raw = "@badge-info=subscriber/42;badges=subscriber/42,glhf-pledge/1;color=#F97304;display-name=zneix;emotes=;first-msg=0;flags=;id=ffc033d5-862c-413c-8422-72a59aa1b0f1;mod=0;returning-chatter=0;room-id=11148817;subscriber=1;tmi-sent-ts=1678752306574;turbo=0;user-id=99631238;user-type= :zneix!zneix@zneix.tmi.twitch.tv PRIVMSG #pajlada :btw DFantasy, have you gotten an unused Dragon Hunter Crossbow just laying around by any chance? kkOna";
- ReadOnlySpan span = Encoding.UTF8.GetBytes(raw).AsSpan();
- Assert.Equal(IrcCommand.PRIVMSG, IrcParsing.ParseCommand(span).cmd);
+ Assert.Equal(IrcCommand.PRIVMSG, new IrcMessage(Encoding.UTF8.GetBytes(raw)).Command);
}
[Fact]
public void Parse_USERNOTICE()
{
string raw = "@badge-info=subscriber/15;badges=subscriber/12;color=#BAFF2F;display-name=VOJTERIS;emotes=;flags=;id=267bffe6-534c-4ddc-a11c-d32b242d8734;login=vojteris;mod=0;msg-id=resub;msg-param-cumulative-months=15;msg-param-months=0;msg-param-multimonth-duration=0;msg-param-multimonth-tenure=0;msg-param-should-share-streak=0;msg-param-sub-plan-name=look\\sat\\sthose\\sshitty\\semotes,\\srip\\s$5\\sLUL;msg-param-sub-plan=1000;msg-param-was-gifted=false;room-id=11148817;subscriber=1;system-msg=VOJTERIS\\ssubscribed\\sat\\sTier\\s1.\\sThey've\\ssubscribed\\sfor\\s15\\smonths!;tmi-sent-ts=1678743959736;user-id=75436142;user-type= :tmi.twitch.tv USERNOTICE #pajlada :egFors TeaTime";
- ReadOnlySpan span = Encoding.UTF8.GetBytes(raw).AsSpan();
- Assert.Equal(IrcCommand.USERNOTICE, IrcParsing.ParseCommand(span).cmd);
+ Assert.Equal(IrcCommand.USERNOTICE, new IrcMessage(Encoding.UTF8.GetBytes(raw)).Command);
}
[Fact]
public void Parse_CLEARCHAT()
{
string raw = "@ban-duration=30;room-id=11148817;target-user-id=783267696;tmi-sent-ts=1678712873805 :tmi.twitch.tv CLEARCHAT #pajlada :occluder";
- ReadOnlySpan span = Encoding.UTF8.GetBytes(raw).AsSpan();
- Assert.Equal(IrcCommand.CLEARCHAT, IrcParsing.ParseCommand(span).cmd);
+ Assert.Equal(IrcCommand.CLEARCHAT, new IrcMessage(Encoding.UTF8.GetBytes(raw)).Command);
}
[Fact]
public void Parse_WHISPER()
{
string raw = "@badges=;color=#2E8B57;display-name=pajbot;emotes=25:7-11;message-id=7;thread-id=82008718_783267696;turbo=0;user-id=82008718;user-type= :pajbot!pajbot@pajbot.tmi.twitch.tv WHISPER occluder :Riftey Kappa";
- ReadOnlySpan span = Encoding.UTF8.GetBytes(raw).AsSpan();
- Assert.Equal(IrcCommand.WHISPER, IrcParsing.ParseCommand(span).cmd);
+ Assert.Equal(IrcCommand.WHISPER, new IrcMessage(Encoding.UTF8.GetBytes(raw)).Command);
}
[Fact]
public void Parse_ROOMSTATE()
{
string raw = "@emote-only=0;followers-only=-1;r9k=0;room-id=783267696;slow=0;subs-only=0 :tmi.twitch.tv ROOMSTATE #occluder";
- ReadOnlySpan span = Encoding.UTF8.GetBytes(raw).AsSpan();
- Assert.Equal(IrcCommand.ROOMSTATE, IrcParsing.ParseCommand(span).cmd);
+ Assert.Equal(IrcCommand.ROOMSTATE, new IrcMessage(Encoding.UTF8.GetBytes(raw)).Command);
}
[Fact]
public void Parse_NOTICE()
{
string raw = "@msg-id=msg_channel_suspended :tmi.twitch.tv NOTICE #foretack :This channel does not exist or has been suspended.";
- ReadOnlySpan span = Encoding.UTF8.GetBytes(raw).AsSpan();
- Assert.Equal(IrcCommand.NOTICE, IrcParsing.ParseCommand(span).cmd);
+ Assert.Equal(IrcCommand.NOTICE, new IrcMessage(Encoding.UTF8.GetBytes(raw)).Command);
}
[Fact]
public void Parse_CLEARMSG()
{
string raw = "@login=occluder;room-id=;target-msg-id=55dc74c9-a6b2-4443-9b68-3446a5ddb7ed;tmi-sent-ts=1678798254260 :tmi.twitch.tv CLEARMSG #occluder :@emote-only=0;followers-only=-1;r9k=0;room-id=783267696;slow=0;subs-only=0 :tmi.twitch.tv ROOMSTATE #occluder ";
- ReadOnlySpan span = Encoding.UTF8.GetBytes(raw).AsSpan();
- Assert.Equal(IrcCommand.CLEARMSG, IrcParsing.ParseCommand(span).cmd);
+ Assert.Equal(IrcCommand.CLEARMSG, new IrcMessage(Encoding.UTF8.GetBytes(raw)).Command);
}
[Fact]
public void Parse_USERSTATE()
{
string raw = "@badge-info=;badges=broadcaster/1;color=#F2647B;display-name=occluder;emote-sets=0,4236,15961,19194,376284588,385292559,477339272,5fc5e142-d394-481f-a561-5406c2ba3ef0,e21484e8-cf11-48b1-8b67-c180fa39f926;mod=0;subscriber=0;user-type= :tmi.twitch.tv USERSTATE #occluder";
- ReadOnlySpan span = Encoding.UTF8.GetBytes(raw).AsSpan();
- Assert.Equal(IrcCommand.USERSTATE, IrcParsing.ParseCommand(span).cmd);
+ Assert.Equal(IrcCommand.USERSTATE, new IrcMessage(Encoding.UTF8.GetBytes(raw)).Command);
}
[Fact]
public void Parse_RECONNECT()
{
string raw = ":tmi.twitch.tv RECONNECT";
- ReadOnlySpan span = Encoding.UTF8.GetBytes(raw).AsSpan();
- Assert.Equal(IrcCommand.RECONNECT, IrcParsing.ParseCommand(span).cmd);
+ Assert.Equal(IrcCommand.RECONNECT, new IrcMessage(Encoding.UTF8.GetBytes(raw)).Command);
}
[Fact]
public void Parse_PING()
{
string raw = "PING :tmi.twitch.tv";
- ReadOnlySpan span = Encoding.UTF8.GetBytes(raw).AsSpan();
- Assert.Equal(IrcCommand.PING, IrcParsing.ParseCommand(span).cmd);
+ Assert.Equal(IrcCommand.PING, new IrcMessage(Encoding.UTF8.GetBytes(raw)).Command);
}
[Fact]
public void Parse_PONG()
{
string raw = "PONG :tmi.twitch.tv";
- ReadOnlySpan span = Encoding.UTF8.GetBytes(raw).AsSpan();
- Assert.Equal(IrcCommand.PONG, IrcParsing.ParseCommand(span).cmd);
+ Assert.Equal(IrcCommand.PONG, new IrcMessage(Encoding.UTF8.GetBytes(raw)).Command);
}
[Fact]
public void Parse_Multiple()
diff --git a/MiniTwitch.Irc.Test/IrcParsingTests.cs b/MiniTwitch.Irc.Test/IrcParsingTests.cs
index bc4b3be..1b4662d 100644
--- a/MiniTwitch.Irc.Test/IrcParsingTests.cs
+++ b/MiniTwitch.Irc.Test/IrcParsingTests.cs
@@ -1,4 +1,5 @@
using System.Text;
+using MiniTwitch.Irc.Internal.Models;
using MiniTwitch.Irc.Internal.Parsing;
using Xunit;
@@ -9,18 +10,7 @@ public class IrcParsingTests
public void Find_Channel()
{
string raw = ":foo!foo@foo.tmi.twitch.tv JOIN #bar";
- ReadOnlySpan span = Encoding.UTF8.GetBytes(raw).AsSpan();
- string channel = IrcParsing.FindChannel(span);
-
- Assert.Equal("bar", channel);
- }
-
- [Fact]
- public void Find_Channel_AnySeparator()
- {
- string raw = ":foo!foo@foo.tmi.twitch.tv JOIN #bar\r\n";
- ReadOnlySpan span = Encoding.UTF8.GetBytes(raw).AsSpan();
- string channel = IrcParsing.FindChannel(span, true);
+ string channel = new IrcMessage(Encoding.UTF8.GetBytes(raw)).GetChannel();
Assert.Equal("bar", channel);
}
@@ -29,8 +19,7 @@ public void Find_Channel_AnySeparator()
public void Find_Content()
{
string raw = "@badge-info=subscriber/11;badges=subscriber/6;color=#F2647B;display-name=occluder;emotes=;first-msg=0;flags=;id=e674e393-1230-4a89-bebc-fae1f925e52c;mod=0;returning-chatter=0;room-id=11148817;subscriber=1;tmi-sent-ts=1680255594264;turbo=0;user-id=783267696;user-type= :occluder!occluder@occluder.tmi.twitch.tv PRIVMSG #pajlada :Are you on some dank browser jammehcow";
- ReadOnlySpan span = Encoding.UTF8.GetBytes(raw).AsSpan();
- string content = IrcParsing.FindContent(span).Content;
+ string content = new IrcMessage(Encoding.UTF8.GetBytes(raw)).GetContent().Content;
Assert.Equal("Are you on some dank browser jammehcow", content);
}
@@ -39,8 +28,7 @@ public void Find_Content()
public void Find_Content_Empty()
{
string raw = "@badge-info=subscriber/5;badges=subscriber/3;color=#5F9EA0;display-name=Syn993;emotes=;flags=;id=401d17b8-363a-4f63-85c8-cd5996fbd4e0;login=syn993;mod=0;msg-id=resub;msg-param-cumulative-months=5;msg-param-months=0;msg-param-multimonth-duration=0;msg-param-multimonth-tenure=0;msg-param-should-share-streak=1;msg-param-streak-months=4;msg-param-sub-plan-name=Channel\\sSubscription\\s(mandeow);msg-param-sub-plan=1000;msg-param-was-gifted=false;room-id=128856353;subscriber=1;system-msg=Syn993\\ssubscribed\\sat\\sTier\\s1.\\sThey've\\ssubscribed\\sfor\\s5\\smonths,\\scurrently\\son\\sa\\s4\\smonth\\sstreak!;tmi-sent-ts=1678873100296;user-id=79085174;user-type= :tmi.twitch.tv USERNOTICE #mande";
- ReadOnlySpan span = Encoding.UTF8.GetBytes(raw).AsSpan();
- string content = IrcParsing.FindContent(span, maybeEmpty: true).Content;
+ string content = new IrcMessage(Encoding.UTF8.GetBytes(raw)).GetContent().Content;
Assert.Equal(string.Empty, content);
}
@@ -49,19 +37,16 @@ public void Find_Content_Empty()
public void Find_Content_Action()
{
string raw = "@badge-info=subscriber/1;badges=subscriber/0;color=#9ACD32;display-name=FeelsCzechMan;emotes=425671:13-20;first-msg=0;flags=;id=0012619e-8e14-4d51-93c9-e9d6fd5a178b;mod=0;returning-chatter=0;room-id=11148817;subscriber=1;tmi-sent-ts=1679730451594;turbo=0;user-id=875745889;user-type= :feelsczechman!feelsczechman@feelsczechman.tmi.twitch.tv PRIVMSG #pajlada :\u0001ACTION FeelsDankMan PowerUpR DANK WAVE▂▃▄▅▆▇██▇▆▅▄▃▂▂▃▄▅▆▇██▇▆▅▄▃▂▂▃▄▅▆▇██▇▆▅▄▃▂▂▃▄▅▆▇██▇▆▅▄▃▂▂▃▄▅▆▇██▇▆▅▄▃▂\u0001";
- ReadOnlySpan span = Encoding.UTF8.GetBytes(raw).AsSpan();
- (string content, bool action) = IrcParsing.FindContent(span, maybeAction: true);
-
- Assert.True(action);
+ (string content, bool action) = new IrcMessage(Encoding.UTF8.GetBytes(raw)).GetContent(maybeAction: true);
Assert.Equal("FeelsDankMan PowerUpR DANK WAVE▂▃▄▅▆▇██▇▆▅▄▃▂▂▃▄▅▆▇██▇▆▅▄▃▂▂▃▄▅▆▇██▇▆▅▄▃▂▂▃▄▅▆▇██▇▆▅▄▃▂▂▃▄▅▆▇██▇▆▅▄▃▂", content);
+ Assert.True(action);
}
[Fact]
public void Find_Content_InvalidAction()
{
string raw = "@badge-info=subscriber/1;badges=subscriber/0;color=#9ACD32;display-name=FeelsCzechMan;emotes=425671:13-20;first-msg=0;flags=;id=0012619e-8e14-4d51-93c9-e9d6fd5a178b;mod=0;returning-chatter=0;room-id=11148817;subscriber=1;tmi-sent-ts=1679730451594;turbo=0;user-id=875745889;user-type= :feelsczechman!feelsczechman@feelsczechman.tmi.twitch.tv PRIVMSG #pajlada :\u0001ACTION \u0001";
- ReadOnlySpan span = Encoding.UTF8.GetBytes(raw).AsSpan();
- (string content, bool action) = IrcParsing.FindContent(span, maybeAction: true);
+ (string content, bool action) = new IrcMessage(Encoding.UTF8.GetBytes(raw)).GetContent();
Assert.False(action);
Assert.Equal("\u0001ACTION \u0001", content);
@@ -71,8 +56,7 @@ public void Find_Content_InvalidAction()
public void Find_Username()
{
string raw = "@badge-info=subscriber/1;badges=subscriber/0;color=#9ACD32;display-name=FeelsCzechMan;emotes=425671:13-20;first-msg=0;flags=;id=0012619e-8e14-4d51-93c9-e9d6fd5a178b;mod=0;returning-chatter=0;room-id=11148817;subscriber=1;tmi-sent-ts=1679730451594;turbo=0;user-id=875745889;user-type= :feelsczechman!feelsczechman@feelsczechman.tmi.twitch.tv PRIVMSG #pajlada :\u0001ACTION FeelsDankMan PowerUpR DANK WAVE▂▃▄▅▆▇██▇▆▅▄▃▂▂▃▄▅▆▇██▇▆▅▄▃▂▂▃▄▅▆▇██▇▆▅▄▃▂▂▃▄▅▆▇██▇▆▅▄▃▂▂▃▄▅▆▇██▇▆▅▄▃▂\u0001";
- ReadOnlySpan span = Encoding.UTF8.GetBytes(raw).AsSpan();
- string username = IrcParsing.FindUsername(span);
+ string username = new IrcMessage(Encoding.UTF8.GetBytes(raw)).GetUsername();
Assert.Equal("feelsczechman", username);
}
@@ -81,8 +65,7 @@ public void Find_Username()
public void Find_Username_NoTags()
{
string raw = ":pajapajapaja!pajapajapaja@pajapajapaja.tmi.twitch.tv JOIN #bar";
- ReadOnlySpan span = Encoding.UTF8.GetBytes(raw).AsSpan();
- string username = IrcParsing.FindUsername(span, true);
+ string username = new IrcMessage(Encoding.UTF8.GetBytes(raw)).GetUsername();
Assert.Equal("pajapajapaja", username);
}
diff --git a/MiniTwitch.Irc/AssemblyInfo.cs b/MiniTwitch.Irc/AssemblyInfo.cs
index 297c946..476f62d 100644
--- a/MiniTwitch.Irc/AssemblyInfo.cs
+++ b/MiniTwitch.Irc/AssemblyInfo.cs
@@ -1,3 +1,4 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("MiniTwitch.Irc.Test")]
+[assembly: InternalsVisibleTo("MiniTwitch.Irc.Benchmarks")]
diff --git a/MiniTwitch.Irc/Internal/Enums/Tags.cs b/MiniTwitch.Irc/Internal/Enums/Tags.cs
new file mode 100644
index 0000000..936d370
--- /dev/null
+++ b/MiniTwitch.Irc/Internal/Enums/Tags.cs
@@ -0,0 +1,66 @@
+namespace MiniTwitch.Irc.Internal.Enums;
+
+internal enum Tags
+{
+ Id = 205,
+ R9K = 278,
+ Mod = 320,
+ Vip = 335,
+ Bits = 434,
+ Slow = 453,
+ Flags = 525,
+ Login = 537,
+ Color = 543,
+ Turbo = 556,
+ MsgId = 577,
+ Badges = 614,
+ Emotes = 653,
+ RoomId = 695,
+ UserId = 697,
+ ThreadId = 882,
+ FirstMsg = 924,
+ SubsOnly = 940,
+ UserType = 942,
+ BadgeInfo = 972,
+ MessageId = 991,
+ EmoteSets = 1030,
+ EmoteOnly = 1033,
+ SystemMsg = 1049,
+ Subscriber = 1076,
+ TmiSentTs = 1093,
+ ClientNonce = 1215,
+ BanDuration = 1220,
+ DisplayName = 1220,
+ TargetMsgId = 1269,
+ TargetUserId = 1389,
+ FollowersOnly = 1484,
+ MsgParamColor = 1489,
+ MsgParamMonths = 1611,
+ MsgParamSubPlan = 1748,
+ ReturningChatter = 1782,
+ ReplyParentMsgId = 1873,
+ ReplyParentUserId = 1993,
+ MsgParamSenderName = 2049,
+ MsgParamGiftMonths = 2082,
+ ReplyParentMsgBody = 2098,
+ MsgParamViewerCount = 2125,
+ PinnedChatPaidLevel = 2139,
+ MsgParamRecipientId = 2159,
+ MsgParamSenderLogin = 2169,
+ MsgParamSenderCount = 2185,
+ MsgParamSubPlanName = 2210,
+ PinnedChatPaidAmount = 2263,
+ MsgParamStreakMonths = 2306,
+ ReplyParentUserLogin = 2325,
+ MsgParamMassGiftCount = 2451,
+ PinnedChatPaidCurrency = 2478,
+ PinnedChatPaidExponent = 2484,
+ ReplyParentDisplayName = 2516,
+ ReplyThreadParentMsgId = 2550,
+ MsgParamCumulativeMonths = 2743,
+ MsgParamRecipientUserName = 2863,
+ MsgParamShouldShareStreak = 2872,
+ ReplyThreadParentUserLogin = 3002,
+ MsgparamRecipientDisplayName = 3174,
+ PinnedChatPaidIsSystemMessage = 3331,
+}
diff --git a/MiniTwitch.Irc/Internal/Models/IrcMessage.cs b/MiniTwitch.Irc/Internal/Models/IrcMessage.cs
new file mode 100644
index 0000000..03043bf
--- /dev/null
+++ b/MiniTwitch.Irc/Internal/Models/IrcMessage.cs
@@ -0,0 +1,192 @@
+using System.Runtime.CompilerServices;
+using MiniTwitch.Common.Extensions;
+using MiniTwitch.Irc.Internal.Enums;
+using MiniTwitch.Irc.Internal.Parsing;
+
+namespace MiniTwitch.Irc.Internal.Models;
+
+internal readonly ref struct IrcMessage
+{
+ public readonly bool HasTags { get; init; } = default;
+ public readonly Range TagsRange { get; init; } = default;
+ public readonly bool HasUsername { get; init; } = default;
+ public readonly Range UsernameRange { get; init; } = default;
+ public readonly IrcCommand Command { get; init; } = default;
+ public readonly bool IsGlobalChannel { get; init; } = default;
+ public readonly Range ChannelRange { get; init; } = default;
+ public readonly bool HasMessageContent { get; init; } = default;
+ public readonly Range MessageContentRange { get; init; } = default;
+ public readonly bool IsMultipleMessages { get; init; } = default;
+ public readonly int NextMessageStartIndex { get; init; } = default;
+ public readonly ReadOnlyMemory Memory { get; init; } = default;
+
+ public IrcMessage(ReadOnlyMemory memory)
+ {
+ const byte colon = (byte)':';
+ const byte at = (byte)'@';
+ const byte space = (byte)' ';
+ const byte excl = (byte)'!';
+ const byte asterisk = (byte)'*';
+ const byte cr = (byte)'\r';
+
+ ReadOnlySpan span = memory.Span;
+ this.Memory = memory;
+ switch (span[0])
+ {
+ case at:
+ this.HasTags = true;
+ // :foo!foo@foo.tmi.twitch.tv PRIVMSG #bar :asdf
+ // ----->|
+ this.TagsRange = 1..span.IndexOf(space);
+ int usernameStart = this.TagsRange.End.Value + 2;
+
+ AfterUsernameStart:
+
+ // :foo!foo@foo.tmi.twitch.tv PRIVMSG #bar :asdf
+ // -->|
+ int usernameEnd = span[usernameStart..].IndexOf(excl) + usernameStart;
+ if (usernameEnd - usernameStart is not -1 and not > 25)
+ {
+ this.HasUsername = true;
+ this.UsernameRange = usernameStart..usernameEnd;
+ }
+
+ int commandStartAddVal = this.HasUsername ? usernameEnd : usernameStart;
+ int commandStart = span[commandStartAddVal..].IndexOf(space) + commandStartAddVal + 1;
+ int commandEnd = span[commandStart..].IndexOf(space) + commandStart;
+ if (commandEnd - commandStart == -1)
+ {
+ this.Command = (IrcCommand)span[commandStart..span.Length].Sum();
+ return;
+ }
+
+ this.Command = (IrcCommand)span[commandStart..commandEnd].Sum();
+ int contentStart;
+ if (span[commandEnd + 1] == asterisk)
+ {
+ this.IsGlobalChannel = true;
+ contentStart = commandEnd + 4;
+ }
+ else
+ {
+ int channelStart = commandEnd + 2;
+ int channelEnd = span[channelStart..].IndexOfAny(space, cr) + channelStart;
+ if (channelEnd - channelStart == -1)
+ {
+ this.ChannelRange = channelStart..span.Length;
+ return;
+ }
+
+ this.ChannelRange = channelStart..channelEnd;
+ if (span[channelEnd] == cr)
+ {
+ this.IsMultipleMessages = true;
+ this.NextMessageStartIndex = channelEnd + 2;
+ return;
+ }
+
+ contentStart = channelEnd + 2;
+ }
+
+ // Didn't end at channel so there must be content
+ this.HasMessageContent = true;
+ int contentEnd = span[contentStart..].IndexOf(cr) + contentStart;
+ if (contentEnd - contentStart == -1)
+ {
+ this.MessageContentRange = contentStart..span.Length;
+ return;
+ }
+
+ this.MessageContentRange = contentStart..contentEnd;
+ if (span.Length > contentEnd + 1)
+ {
+ this.IsMultipleMessages = true;
+ this.NextMessageStartIndex = contentEnd + 2;
+ }
+ break;
+
+ case colon:
+ usernameStart = 1;
+ goto AfterUsernameStart;
+
+ default:
+ commandStart = 0;
+ commandEnd = span.IndexOf(space);
+ this.Command = (IrcCommand)span[commandStart..commandEnd].Sum();
+ int crIndex = span[commandEnd..].IndexOf(cr) + commandEnd;
+ if (crIndex - commandEnd != -1)
+ {
+ this.IsMultipleMessages = true;
+ this.NextMessageStartIndex = crIndex + 2;
+ }
+
+ break;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly string GetChannel() => TagHelper.GetString(this.Memory.Span[this.ChannelRange], true);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly string GetUsername() => TagHelper.GetString(this.Memory.Span[this.UsernameRange]);
+
+ public readonly (string Content, bool Action) GetContent(bool maybeAction = false)
+ {
+ if (!this.HasMessageContent)
+ return (string.Empty, false);
+
+ string content = TagHelper.GetString(this.Memory.Span[this.MessageContentRange]);
+ if (maybeAction && content.Length > 9 && content[0] == '\u0001' && content[^1] == '\u0001')
+ return (content[8..^1], true);
+
+ return (content, false);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly IrcTags ParseTags()
+ {
+ const byte semiColon = (byte)';';
+ const byte equals = (byte)'=';
+
+ ReadOnlySpan span = this.Memory.Span;
+ int tagCount = 0;
+
+ // Determine the amount of tags by counting '='
+ int eqIndex;
+ ReadOnlySpan tagIndexCountSpan = span;
+ while ((eqIndex = tagIndexCountSpan.IndexOf(equals)) != -1)
+ {
+ tagCount++;
+ tagIndexCountSpan = tagIndexCountSpan[(eqIndex + 1)..];
+ }
+
+ IrcTags tags = new(tagCount);
+ Index tagsEndIndex = this.TagsRange.End;
+ Index tagStart = this.TagsRange.Start;
+ Index tagEquals;
+ Index tagEnd;
+
+ for (int i = 0; i < tagCount; i++)
+ {
+ if (tagStart.Value >= tagsEndIndex.Value)
+ break;
+
+ // Index of '=' from the start of the tag
+ tagEquals = span[tagStart..tagsEndIndex].IndexOf(equals) + tagStart.Value;
+ if (tagEquals.Value == tagStart.Value - 1)
+ break;
+
+ // Index of ';' from the equal sign of the tag
+ tagEnd = span[tagEquals..tagsEndIndex].IndexOf(semiColon) + tagEquals.Value;
+ // Account for last tag, which doesn't end with a semicolon
+ if (tagEnd.Value == tagEquals.Value - 1)
+ tagEnd = tagsEndIndex;
+
+ // Key memory slice: from index 0 until the next equal sign
+ // Value memory slice: from index 0 after the equal sign until next semicolon
+ tags.Add(i, this.Memory[tagStart..tagEquals], this.Memory[(tagEquals.Value + 1)..tagEnd]);
+ tagStart = tagEnd.Value + 1;
+ }
+
+ return tags;
+ }
+}
diff --git a/MiniTwitch.Irc/Internal/Parsing/IrcParsing.cs b/MiniTwitch.Irc/Internal/Parsing/IrcParsing.cs
deleted file mode 100644
index 658ffdf..0000000
--- a/MiniTwitch.Irc/Internal/Parsing/IrcParsing.cs
+++ /dev/null
@@ -1,219 +0,0 @@
-using System.Text;
-using MiniTwitch.Common.Extensions;
-using MiniTwitch.Irc.Internal.Enums;
-using MiniTwitch.Irc.Internal.Models;
-
-namespace MiniTwitch.Irc.Internal.Parsing;
-
-internal static class IrcParsing
-{
- internal static string FindChannel(this ReadOnlySpan span, bool anySeparator = false)
- {
- const byte numberSymbol = (byte)'#';
- const byte space = (byte)' ';
- const byte lf = (byte)'\n';
- const byte cr = (byte)'\r';
-
- int scopeStart = span.IndexOf(space) + 1;
- int symbolIndex = span[scopeStart..].IndexOf(numberSymbol) + scopeStart;
- int nextSpace;
- if (anySeparator)
- {
- ReadOnlySpan ends = stackalloc byte[] { space, lf, cr };
- nextSpace = span[symbolIndex..].IndexOfAny(ends) + symbolIndex;
- }
- else
- {
- nextSpace = span[symbolIndex..].IndexOf(space) + symbolIndex;
- }
-
- if (nextSpace == symbolIndex - 1)
- nextSpace = span.Length;
-
- ReadOnlySpan newSpan;
- try
- {
- newSpan = span[(symbolIndex + 1)..nextSpace];
- }
- catch (Exception)
- {
- throw new ArgumentException($"Failed to find channel from {symbolIndex + 1} to {nextSpace} in\n{Encoding.UTF8.GetString(span)}");
- }
- return TagHelper.GetString(newSpan, true);
- }
-
- internal static (string Content, bool Action) FindContent(this ReadOnlySpan span, bool maybeEmpty = false, bool maybeAction = false)
- {
- const byte colon = (byte)':';
- const byte space = (byte)' ';
-
- // :foo!foo@foo.tmi.twitch.tv PRIVMSG #bar :asdf
- // ↑
- int firstSeparatorIndex = span.IndexOf(space) + 2;
- // :foo!foo@foo.tmi.twitch.tv PRIVMSG #bar :asdf
- // ↑
- int secondSeparatorIndex = span[firstSeparatorIndex..].IndexOf(colon) + firstSeparatorIndex + 1;
- if (maybeEmpty && secondSeparatorIndex - firstSeparatorIndex - 1 == -1)
- return (string.Empty, false);
-
- ReadOnlySpan newSpan = span[secondSeparatorIndex..];
- string content = TagHelper.GetString(newSpan);
- if (maybeAction && content.Length > 9 && content[0] == '\u0001' && content[^1] == '\u0001')
- return (content[8..^1], true);
-
- return (content, false);
- }
-
- internal static string FindUsername(this ReadOnlySpan span, bool noTags = false)
- {
- const byte space = (byte)' ';
- const byte exclamationMark = (byte)'!';
-
- int separator;
- int exclamationIndex;
- if (noTags)
- {
- // :foo!foo@foo.tmi.twitch.tv JOIN #bar
- // |__|
- separator = 1;
- exclamationIndex = span.IndexOf(exclamationMark);
- }
- else
- {
- // :foo!foo@foo.tmi.twitch.tv PRIVMSG #bar :asdf
- // ↑
- separator = span.IndexOf(space) + 2;
- // :foo!foo@foo.tmi.twitch.tv PRIVMSG #bar :asdf
- // ↑
- exclamationIndex = span[separator..].IndexOf(exclamationMark) + separator;
- }
-
- ReadOnlySpan newSpan;
- try
- {
- newSpan = span[separator..exclamationIndex];
- }
- catch (Exception)
- {
- throw new ArgumentException($"Failed to find channel from {separator + 1} to {exclamationIndex} in\n{Encoding.UTF8.GetString(span)}");
- }
- return TagHelper.GetString(newSpan);
- }
-
- internal static IrcTags ParseTags(ReadOnlyMemory memory)
- {
- ReadOnlySpan span = memory.Span;
-
- const byte at = (byte)'@';
- const byte space = (byte)' ';
- const byte colon = (byte)':';
- const byte semiColon = (byte)';';
- const byte equals = (byte)'=';
-
- int tagsStartIndex = span[0] is at or colon ? 1 : 0;
- int tagsEndIndex = span.IndexOf(space);
- int tagCount = 0;
-
- // Determine the amount of tags by counting '='
- int eqIndex;
- ReadOnlySpan tagIndexCountSpan = span;
- while ((eqIndex = tagIndexCountSpan.IndexOf(equals)) != -1)
- {
- tagCount++;
- tagIndexCountSpan = tagIndexCountSpan[(eqIndex + 1)..];
- }
-
- IrcTags tags = new(tagCount);
- int tagStart = tagsStartIndex;
- int tagEquals;
- int tagEnd;
-
- for (int i = 0; i < tagCount; i++)
- {
- if (tagStart >= tagsEndIndex)
- break;
-
- // Index of '=' from the start of the tag
- tagEquals = span[tagStart..tagsEndIndex].IndexOf(equals) + tagStart;
- if (tagEquals == tagStart - 1)
- break;
-
- // Index of ';' from the equal sign of the tag
- tagEnd = span[tagEquals..tagsEndIndex].IndexOf(semiColon) + tagEquals;
- // Account for last tag, which doesn't end with a semicolon
- if (tagEnd == tagEquals - 1)
- tagEnd = tagsEndIndex;
-
- // Key memory slice: from index 0 until the next equal sign
- // Value memory slice: from index 0 after the equal sign until next semicolon
- tags.Add(i, memory[tagStart..tagEquals], memory[(tagEquals + 1)..tagEnd]);
- tagStart = tagEnd + 1;
- }
-
- return tags;
- }
-
- ///
- /// Parses IRC commands from messages
- ///
- /// The span to parse
- /// The IRC command and the index start of the next message (0 if none)
- internal static (IrcCommand cmd, int lfIndex) ParseCommand(ReadOnlySpan span)
- {
- const byte space = (byte)' ';
- const byte colon = (byte)':';
- const byte at = (byte)'@';
- const byte lf = (byte)'\n';
-
- int scopeStart;
- int firstSpace;
- int startIndex;
- int length;
- ReadOnlySpan command;
-
- if (span[0] == lf)
- {
- return (IrcCommand.Unknown, 0);
- }
- else if (span[0] is not colon and not at)
- {
- firstSpace = span.IndexOf(space);
- command = span[..firstSpace];
- }
- else
- {
- // Start looking for the command from the first space
- scopeStart = span.IndexOf(space);
- // :
- if (span[scopeStart + 1] == colon)
- {
- scopeStart += 2;
- // :
- // ↑
- firstSpace = span[scopeStart..].IndexOf(space) + scopeStart;
- startIndex = firstSpace + 1;
- // :
- // |_________|
- length = span[startIndex..].IndexOf(space);
-
- if (firstSpace == scopeStart - 1 || length == -1)
- return (IrcCommand.Unknown, 0);
-
- command = span[startIndex..(startIndex + length)];
- }
- // :
- else
- {
- // Since there are no tags, 'scopeStart' will be 'firstSpace' here
- ++scopeStart;
- int secondSpace = span[scopeStart..].IndexOf(space) + scopeStart;
- if (secondSpace == scopeStart - 1)
- secondSpace = span.Length;
-
- command = span[scopeStart..secondSpace];
- }
- }
-
- return ((IrcCommand)command.Sum(), span.IndexOf(lf) + 1);
- }
-}
diff --git a/MiniTwitch.Irc/IrcClient.cs b/MiniTwitch.Irc/IrcClient.cs
index 71a844c..e16b5f9 100644
--- a/MiniTwitch.Irc/IrcClient.cs
+++ b/MiniTwitch.Irc/IrcClient.cs
@@ -5,7 +5,7 @@
using MiniTwitch.Irc.Interfaces;
using MiniTwitch.Irc.Internal;
using MiniTwitch.Irc.Internal.Enums;
-using MiniTwitch.Irc.Internal.Parsing;
+using MiniTwitch.Irc.Internal.Models;
using MiniTwitch.Irc.Models;
namespace MiniTwitch.Irc;
@@ -146,7 +146,7 @@ public sealed class IrcClient : IAsyncDisposable
#endregion
#region Fields
- private static readonly WaitableEvents[] _channelJoinEvents = new[]
+ private static readonly WaitableEvents[] _channelJoinEvents = new[]
{
WaitableEvents.JoinedChannel, WaitableEvents.ChannelSuspended
};
@@ -316,7 +316,7 @@ public async ValueTask SendMessage(string channel, string message, bool action =
if (!_manager.CanSend(channel, _moderated.Contains(channel)))
{
- TimeSpan delay = TimeSpan.FromSeconds(90 / this.Options.MessageRateLimit);
+ var delay = TimeSpan.FromSeconds(90 / this.Options.MessageRateLimit);
Log(LogLevel.Debug, "Cannot send message to #{channel}: Rate limit of {count} hit. Retrying in {delay}s",
channel, this.Options.ModMessageRateLimit, delay.TotalSeconds);
Log(LogLevel.Warning, "#{channel}: Your message was not sent yet due to the configured messaging ratelimit (normal: {normal}/30s, mod: {mod}/30s)",
@@ -355,7 +355,7 @@ public async ValueTask ReplyTo(Privmsg parentMessage, string message, bool actio
string channel = parentMessage.Channel.Name;
if (!_manager.CanSend(channel, _moderated.Contains(channel)))
{
- TimeSpan delay = TimeSpan.FromSeconds(90 / this.Options.MessageRateLimit);
+ var delay = TimeSpan.FromSeconds(90 / this.Options.MessageRateLimit);
Log(LogLevel.Debug, "Cannot send message to #{channel}: Rate limit of {count} hit. Retrying in {delay}s",
channel, this.Options.ModMessageRateLimit, delay.TotalSeconds);
Log(LogLevel.Warning, "#{channel}: Your message was not sent yet due to the configured messaging ratelimit (normal: {normal}/30s, mod: {mod}/30s)",
@@ -400,7 +400,7 @@ public async ValueTask ReplyTo(string messageId, string channel, string reply, b
if (!_manager.CanSend(channel, _moderated.Contains(channel)))
{
- TimeSpan delay = TimeSpan.FromSeconds(90 / this.Options.MessageRateLimit);
+ var delay = TimeSpan.FromSeconds(90 / this.Options.MessageRateLimit);
Log(LogLevel.Debug, "Cannot send message to #{channel}: Rate limit of {count} hit. Retrying in {delay}s",
channel, this.Options.ModMessageRateLimit, delay.TotalSeconds);
Log(LogLevel.Warning, "#{channel}: Your message was not sent yet due to the configured messaging ratelimit (normal: {normal}/30s, mod: {mod}/30s)",
@@ -527,22 +527,18 @@ public Task PartChannel(string channel, CancellationToken cancellationToken = de
#region Parsing
internal void Parse(ReadOnlyMemory data)
{
- (IrcCommand command, int lfIndex) = IrcParsing.ParseCommand(data.Span);
- int accumulatedIndex = lfIndex;
- ReceiveData(command, data[..(lfIndex == 0 ? ^0 : lfIndex - 2)]);
- // Go over data if it has multiple messages
- while (lfIndex != 0 && data.Length - accumulatedIndex > 0)
+ IrcMessage message = new(data);
+ HandleMessage(ref message);
+ if (message.IsMultipleMessages)
{
- (command, lfIndex) = IrcParsing.ParseCommand(data.Span[accumulatedIndex..]);
- ReceiveData(command, data[accumulatedIndex..(lfIndex == 0 ? ^0 : lfIndex + accumulatedIndex - 2)]);
- accumulatedIndex += lfIndex;
+ Parse(data[message.NextMessageStartIndex..]);
}
}
- private void ReceiveData(IrcCommand command, ReadOnlyMemory data)
+ private void HandleMessage(ref IrcMessage message)
{
// Return if user decides to ignore the command
- if (this.Options.IgnoreCommands != IgnoreCommand.None && command switch
+ if (this.Options.IgnoreCommands != IgnoreCommand.None && message.Command switch
{
IrcCommand.PRIVMSG => this.Options.IgnoreCommands.HasFlag(IgnoreCommand.PRIVMSG),
IrcCommand.USERNOTICE => this.Options.IgnoreCommands.HasFlag(IgnoreCommand.USERNOTICE),
@@ -560,10 +556,10 @@ private void ReceiveData(IrcCommand command, ReadOnlyMemory data)
return;
}
- switch (command)
+ switch (message.Command)
{
case IrcCommand.PRIVMSG:
- Privmsg ircMessage = new(data, this);
+ Privmsg ircMessage = new(ref message, this);
OnMessage?.Invoke(ircMessage).StepOver(this.ExceptionHandler);
break;
@@ -591,7 +587,7 @@ private void ReceiveData(IrcCommand command, ReadOnlyMemory data)
break;
case IrcCommand.USERNOTICE:
- Usernotice usernotice = new(data);
+ Usernotice usernotice = new(ref message);
switch (usernotice.MsgId)
{
case UsernoticeType.Sub
@@ -628,7 +624,7 @@ private void ReceiveData(IrcCommand command, ReadOnlyMemory data)
break;
case IrcCommand.CLEARCHAT:
- Clearchat clearchat = new(data);
+ Clearchat clearchat = new(ref message);
if (clearchat.IsClearChat)
OnChatClear?.Invoke(clearchat).StepOver(this.ExceptionHandler);
else if (clearchat.IsBan)
@@ -639,12 +635,12 @@ private void ReceiveData(IrcCommand command, ReadOnlyMemory data)
break;
case IrcCommand.CLEARMSG:
- Clearmsg clearmsg = new(data);
+ Clearmsg clearmsg = new(ref message);
OnMessageDelete?.Invoke(clearmsg).StepOver(this.ExceptionHandler);
break;
case IrcCommand.ROOMSTATE:
- IrcChannel ircChannel = new(data);
+ IrcChannel ircChannel = new(ref message);
if (ircChannel.Roomstate == RoomstateType.All)
{
_coordinator.ReleaseIfLocked(WaitableEvents.JoinedChannel);
@@ -685,7 +681,7 @@ private void ReceiveData(IrcCommand command, ReadOnlyMemory data)
break;
case IrcCommand.PART:
- IrcChannel channel = new(data);
+ IrcChannel channel = new(ref message);
if (this.JoinedChannels.Remove(channel))
{
Log(LogLevel.Information, "Parted #{channel}", channel.Name);
@@ -697,7 +693,7 @@ private void ReceiveData(IrcCommand command, ReadOnlyMemory data)
break;
case IrcCommand.NOTICE:
- Notice notice = new(data);
+ Notice notice = new(ref message);
if (notice.Type == NoticeType.Msg_channel_suspended)
{
Log(LogLevel.Error, "Tried joining suspended channel: #{channel}", notice.Channel.Name);
@@ -712,7 +708,7 @@ private void ReceiveData(IrcCommand command, ReadOnlyMemory data)
break;
case IrcCommand.USERSTATE or IrcCommand.GLOBALUSERSTATE:
- Userstate state = new(data);
+ Userstate state = new(ref message);
if (state.Self.IsMod && !_moderated.Contains(state.Channel.Name))
_ = _moderated.Add(state.Channel.Name);
@@ -720,7 +716,7 @@ private void ReceiveData(IrcCommand command, ReadOnlyMemory data)
break;
case IrcCommand.WHISPER:
- Whisper whisper = new(data);
+ Whisper whisper = new(ref message);
OnWhisper?.Invoke(whisper).StepOver(this.ExceptionHandler);
break;
}
@@ -728,7 +724,7 @@ private void ReceiveData(IrcCommand command, ReadOnlyMemory data)
#endregion
#region Utils
- private ILogger GetLogger() => this.Options.Logger ?? DefaultLogger;
+ private ILogger GetLogger() => this.Options.Logger ?? this.DefaultLogger;
private void LogEventException(Exception ex) => LogException(ex, "🚨 Exception caught in an event:");
diff --git a/MiniTwitch.Irc/IrcMembershipClient.cs b/MiniTwitch.Irc/IrcMembershipClient.cs
index 80b3704..a46a28a 100644
--- a/MiniTwitch.Irc/IrcMembershipClient.cs
+++ b/MiniTwitch.Irc/IrcMembershipClient.cs
@@ -4,7 +4,7 @@
using MiniTwitch.Irc.Interfaces;
using MiniTwitch.Irc.Internal;
using MiniTwitch.Irc.Internal.Enums;
-using MiniTwitch.Irc.Internal.Parsing;
+using MiniTwitch.Irc.Internal.Models;
using MiniTwitch.Irc.Models;
namespace MiniTwitch.Irc;
@@ -234,20 +234,17 @@ public Task PartChannel(string channel, CancellationToken cancellationToken = de
#region Parsing
internal void Parse(ReadOnlyMemory data)
{
- (IrcCommand command, int lfIndex) = IrcParsing.ParseCommand(data.Span);
- int accumulatedIndex = lfIndex;
- ReceiveData(command, data);
- while (lfIndex != 0 && data.Length - accumulatedIndex > 0)
+ IrcMessage message = new(data);
+ HandleMessage(message);
+ if (message.IsMultipleMessages)
{
- (command, lfIndex) = IrcParsing.ParseCommand(data.Span[accumulatedIndex..]);
- ReceiveData(command, data[accumulatedIndex..]);
- accumulatedIndex += lfIndex;
+ Parse(data[message.NextMessageStartIndex..]);
}
}
- private void ReceiveData(IrcCommand command, ReadOnlyMemory data)
+ private void HandleMessage(IrcMessage message)
{
- switch (command)
+ switch (message.Command)
{
case IrcCommand.Connected:
if (_connectionWaiter.CurrentCount == 0)
@@ -278,11 +275,11 @@ private void ReceiveData(IrcCommand command, ReadOnlyMemory data)
{
User = new MessageAuthor()
{
- Name = data.Span.FindUsername(noTags: true)
+ Name = message.GetUsername()
},
Channel = new IrcChannel()
{
- Name = data.Span.FindChannel(anySeparator: true)
+ Name = message.GetChannel()
}
};
OnUserJoin?.Invoke(joinArgs).StepOver(this.ExceptionHandler);
@@ -293,11 +290,11 @@ private void ReceiveData(IrcCommand command, ReadOnlyMemory data)
{
User = new MessageAuthor()
{
- Name = data.Span.FindUsername(noTags: true)
+ Name = message.GetUsername()
},
Channel = new IrcChannel()
{
- Name = data.Span.FindChannel(anySeparator: true)
+ Name = message.GetChannel()
}
};
OnUserPart?.Invoke(partArgs).StepOver(this.ExceptionHandler);
diff --git a/MiniTwitch.Irc/Models/ClearChat.cs b/MiniTwitch.Irc/Models/ClearChat.cs
index f7a525d..e0a6d29 100644
--- a/MiniTwitch.Irc/Models/ClearChat.cs
+++ b/MiniTwitch.Irc/Models/ClearChat.cs
@@ -1,6 +1,7 @@
using System.Text;
using MiniTwitch.Common.Extensions;
using MiniTwitch.Irc.Interfaces;
+using MiniTwitch.Irc.Internal.Enums;
using MiniTwitch.Irc.Internal.Models;
using MiniTwitch.Irc.Internal.Parsing;
@@ -30,7 +31,7 @@ namespace MiniTwitch.Irc.Models;
internal bool IsClearChat { get; init; }
internal bool IsBan { get; init; }
- internal Clearchat(ReadOnlyMemory memory)
+ internal Clearchat(ref IrcMessage message)
{
int duration = 0;
long targetId = 0;
@@ -38,7 +39,7 @@ internal Clearchat(ReadOnlyMemory memory)
long tmiSentTs = 0;
- using IrcTags tags = IrcParsing.ParseTags(memory);
+ using IrcTags tags = message.ParseTags();
foreach (IrcTag tag in tags)
{
ReadOnlySpan tagKey = tag.Key.Span;
@@ -47,22 +48,22 @@ internal Clearchat(ReadOnlyMemory memory)
switch (tagKey.Sum())
{
//room-id
- case 695:
+ case (int)Tags.RoomId:
channelId = TagHelper.GetLong(tagValue);
break;
//tmi-sent-ts
- case 1093:
+ case (int)Tags.TmiSentTs:
tmiSentTs = TagHelper.GetLong(tagValue);
break;
//ban-duration
- case 1220:
+ case (int)Tags.BanDuration:
duration = TagHelper.GetInt(tagValue);
break;
//target-user-id
- case 1389:
+ case (int)Tags.TargetUserId:
targetId = TagHelper.GetLong(tagValue);
break;
}
@@ -71,12 +72,12 @@ internal Clearchat(ReadOnlyMemory memory)
this.Duration = duration == 0 ? TimeSpan.Zero : TimeSpan.FromSeconds(duration);
this.Target = new MessageAuthor()
{
- Name = memory.Span.FindContent().Content,
+ Name = message.HasMessageContent ? message.GetContent().Content : string.Empty,
Id = targetId
};
this.Channel = new IrcChannel()
{
- Name = memory.Span.FindChannel(),
+ Name = message.GetChannel(),
Id = channelId
};
this.TmiSentTs = tmiSentTs;
@@ -92,6 +93,7 @@ internal Clearchat(ReadOnlyMemory memory)
public static Clearchat Construct(string rawData)
{
ReadOnlyMemory memory = new(Encoding.UTF8.GetBytes(rawData));
- return new(memory);
+ var message = new IrcMessage(memory);
+ return new(ref message);
}
}
diff --git a/MiniTwitch.Irc/Models/Clearmsg.cs b/MiniTwitch.Irc/Models/Clearmsg.cs
index 7dedc4d..689fa98 100644
--- a/MiniTwitch.Irc/Models/Clearmsg.cs
+++ b/MiniTwitch.Irc/Models/Clearmsg.cs
@@ -1,6 +1,7 @@
using System.Text;
using MiniTwitch.Common.Extensions;
using MiniTwitch.Irc.Interfaces;
+using MiniTwitch.Irc.Internal.Enums;
using MiniTwitch.Irc.Internal.Models;
using MiniTwitch.Irc.Internal.Parsing;
@@ -31,14 +32,14 @@ namespace MiniTwitch.Irc.Models;
///
public DateTimeOffset SentTimestamp => DateTimeOffset.FromUnixTimeMilliseconds(this.TmiSentTs);
- internal Clearmsg(ReadOnlyMemory memory)
+ internal Clearmsg(ref IrcMessage message)
{
string targetUsername = string.Empty;
- string channelName = memory.Span.FindChannel();
+ string channelName = message.GetChannel();
string messageId = string.Empty;
long tmiSentTs = 0;
- using IrcTags tags = IrcParsing.ParseTags(memory);
+ using IrcTags tags = message.ParseTags();
foreach (IrcTag tag in tags)
{
ReadOnlySpan tagKey = tag.Key.Span;
@@ -47,17 +48,17 @@ internal Clearmsg(ReadOnlyMemory memory)
switch (tagKey.Sum())
{
//login
- case 537:
+ case (int)Tags.Login:
targetUsername = TagHelper.GetString(tagValue);
break;
//tmi-sent-ts
- case 1093:
+ case (int)Tags.TmiSentTs:
tmiSentTs = TagHelper.GetLong(tagValue);
break;
//target-msg-id
- case 1269:
+ case (int)Tags.TargetMsgId:
messageId = TagHelper.GetString(tagValue);
break;
@@ -73,7 +74,7 @@ internal Clearmsg(ReadOnlyMemory memory)
Name = channelName
};
this.MessageId = messageId;
- this.MessageContent = memory.Span.FindContent(maybeAction: true).Content;
+ this.MessageContent = message.GetContent(maybeAction: true).Content;
this.TmiSentTs = tmiSentTs;
}
@@ -85,7 +86,8 @@ internal Clearmsg(ReadOnlyMemory memory)
public static Clearmsg Construct(string rawData)
{
ReadOnlyMemory memory = new(Encoding.UTF8.GetBytes(rawData));
- return new(memory);
+ var message = new IrcMessage(memory);
+ return new(ref message);
}
///
diff --git a/MiniTwitch.Irc/Models/IrcChannel.cs b/MiniTwitch.Irc/Models/IrcChannel.cs
index 7dbbfcb..34780dc 100644
--- a/MiniTwitch.Irc/Models/IrcChannel.cs
+++ b/MiniTwitch.Irc/Models/IrcChannel.cs
@@ -37,12 +37,12 @@ namespace MiniTwitch.Irc.Models;
private static readonly TimeSpan _followersOnlyOffTimeSpan = TimeSpan.FromMinutes(-1);
- internal IrcChannel(ReadOnlyMemory memory)
+ internal IrcChannel(ref IrcMessage message)
{
int followerModeDuration = -1;
int slowModeDuration = 0;
// do this otherwise it will include '\r\n' in the name
- string name = memory.Span.FindChannel(anySeparator: true);
+ string name = message.GetChannel();
long id = 0;
bool emoteOnlyModified = false;
@@ -51,7 +51,7 @@ internal IrcChannel(ReadOnlyMemory memory)
bool followerModeModified = false;
bool slowModeModified = false;
- using IrcTags tags = IrcParsing.ParseTags(memory);
+ using IrcTags tags = message.ParseTags();
foreach (IrcTag tag in tags)
{
ReadOnlySpan tagKey = tag.Key.Span;
@@ -59,36 +59,36 @@ internal IrcChannel(ReadOnlyMemory memory)
switch (tagKey.Sum())
{
//r9k
- case 278:
+ case (int)Tags.R9K:
this.UniqueModeEnabled = TagHelper.GetBool(tagValue);
uniqueModeModified = true;
break;
//slow
- case 453:
+ case (int)Tags.Slow:
slowModeDuration = TagHelper.GetInt(tagValue);
slowModeModified = true;
break;
//room-id
- case 695:
+ case (int)Tags.RoomId:
id = TagHelper.GetLong(tagValue);
break;
//subs-only
- case 940:
+ case (int)Tags.SubsOnly:
this.SubOnlyEnabled = TagHelper.GetBool(tagValue);
subModeModified = true;
break;
//emote-only
- case 1033:
+ case (int)Tags.EmoteOnly:
this.EmoteOnlyEnabled = TagHelper.GetBool(tagValue);
emoteOnlyModified = true;
break;
//followers-only
- case 1484:
+ case (int)Tags.FollowersOnly:
followerModeDuration = TagHelper.GetInt(tagValue);
followerModeModified = true;
break;
@@ -140,7 +140,8 @@ internal IrcChannel(ReadOnlyMemory memory)
public static IrcChannel Construct(string rawData)
{
ReadOnlyMemory memory = new(Encoding.UTF8.GetBytes(rawData));
- return new(memory);
+ var message = new IrcMessage(memory);
+ return new(ref message);
}
#pragma warning disable CS8765 // Nullability of type of parameter doesn't match overridden member (possibly because of nullability attributes).
diff --git a/MiniTwitch.Irc/Models/MessageReply.cs b/MiniTwitch.Irc/Models/MessageReply.cs
index da40645..0f6ccba 100644
--- a/MiniTwitch.Irc/Models/MessageReply.cs
+++ b/MiniTwitch.Irc/Models/MessageReply.cs
@@ -38,7 +38,7 @@ public readonly struct MessageReply
///
/// Whether there are reply contents in this message
///
- public bool HasContent => ParentUserId != 0;
+ public bool HasContent => this.ParentUserId != 0;
///
public static implicit operator string(MessageReply messageReply) => messageReply.HasContent ? messageReply.ParentMessage : string.Empty;
diff --git a/MiniTwitch.Irc/Models/Notice.cs b/MiniTwitch.Irc/Models/Notice.cs
index 2500ec1..9cea2cd 100644
--- a/MiniTwitch.Irc/Models/Notice.cs
+++ b/MiniTwitch.Irc/Models/Notice.cs
@@ -1,7 +1,9 @@
-using System.Text;
+using System;
+using System.Text;
using MiniTwitch.Common.Extensions;
using MiniTwitch.Irc.Enums;
using MiniTwitch.Irc.Interfaces;
+using MiniTwitch.Irc.Internal.Enums;
using MiniTwitch.Irc.Internal.Models;
using MiniTwitch.Irc.Internal.Parsing;
@@ -25,10 +27,10 @@ namespace MiniTwitch.Irc.Models;
///
public NoticeType Type { get; init; } = NoticeType.Unknown;
- internal Notice(ReadOnlyMemory memory)
+ internal Notice(ref IrcMessage message)
{
- this.SystemMessage = memory.Span.FindContent().Content;
- using IrcTags ircTags = IrcParsing.ParseTags(memory);
+ this.SystemMessage = message.GetContent().Content;
+ using IrcTags ircTags = message.ParseTags();
foreach (IrcTag tag in ircTags)
{
ReadOnlySpan tagKey = tag.Key.Span;
@@ -38,24 +40,16 @@ internal Notice(ReadOnlyMemory memory)
switch (tagKey.Sum())
{
//msg-id
- case 577:
- // I do not like this. But because some of its values would have the same sum, I'm forced to do this
+ case (int)Tags.MsgId:
this.Type = TagHelper.GetEnum(tagValue);
break;
}
}
- try
+ this.Channel = new IrcChannel()
{
- this.Channel = new IrcChannel()
- {
- Name = memory.Span.FindChannel()
- };
- }
- catch
- {
- this.Type = NoticeType.Bad_auth;
- }
+ Name = message.IsGlobalChannel ? "*" : message.GetChannel()
+ };
}
///
@@ -66,7 +60,8 @@ internal Notice(ReadOnlyMemory memory)
public static Notice Construct(string rawData)
{
ReadOnlyMemory memory = new(Encoding.UTF8.GetBytes(rawData));
- return new(memory);
+ var message = new IrcMessage(memory);
+ return new(ref message);
}
#pragma warning disable CS8765 // Nullability of type of parameter doesn't match overridden member (possibly because of nullability attributes).
diff --git a/MiniTwitch.Irc/Models/Privmsg.cs b/MiniTwitch.Irc/Models/Privmsg.cs
index cb7925e..3b63efc 100644
--- a/MiniTwitch.Irc/Models/Privmsg.cs
+++ b/MiniTwitch.Irc/Models/Privmsg.cs
@@ -4,6 +4,7 @@
using MiniTwitch.Common.Extensions;
using MiniTwitch.Irc.Enums;
using MiniTwitch.Irc.Interfaces;
+using MiniTwitch.Irc.Internal.Enums;
using MiniTwitch.Irc.Internal.Models;
using MiniTwitch.Irc.Internal.Parsing;
@@ -83,7 +84,7 @@ namespace MiniTwitch.Irc.Models;
internal IrcClient? Source { get; init; }
- internal Privmsg(ReadOnlyMemory memory, IrcClient? source = null)
+ internal Privmsg(ref IrcMessage message, IrcClient? source = null)
{
this.Source = source;
@@ -92,7 +93,7 @@ internal Privmsg(ReadOnlyMemory memory, IrcClient? source = null)
string badgeInfo = string.Empty;
Color color = default;
string displayName = string.Empty;
- string username = memory.Span.FindUsername();
+ string username = message.GetUsername();
long uid = 0;
bool mod = false;
bool sub = false;
@@ -117,11 +118,10 @@ internal Privmsg(ReadOnlyMemory memory, IrcClient? source = null)
HypeChatLevel level = HypeChatLevel.None;
// IBasicChannel
- string channelName = memory.Span.FindChannel();
+ string channelName = message.GetChannel();
long channelId = 0;
- (this.Content, this.IsAction) = memory.Span.FindContent(maybeAction: true);
-
+ (this.Content, this.IsAction) = message.GetContent(maybeAction: true);
string emotes = string.Empty;
string flags = string.Empty;
string id = string.Empty;
@@ -131,7 +131,7 @@ internal Privmsg(ReadOnlyMemory memory, IrcClient? source = null)
bool firstMsg = false;
bool returningChatter = false;
- using IrcTags tags = IrcParsing.ParseTags(memory);
+ using IrcTags tags = message.ParseTags();
foreach (IrcTag tag in tags)
{
ReadOnlySpan tagKey = tag.Key.Span;
@@ -140,157 +140,157 @@ internal Privmsg(ReadOnlyMemory memory, IrcClient? source = null)
switch (tagKey.Sum())
{
//id
- case 205:
+ case (int)Tags.Id:
id = TagHelper.GetString(tagValue);
break;
//mod
- case 320:
+ case (int)Tags.Mod:
mod = TagHelper.GetBool(tagValue);
break;
//vip
- case 335:
+ case (int)Tags.Vip:
vip = true;
break;
//bits
- case 434:
+ case (int)Tags.Bits:
bits = TagHelper.GetInt(tagValue);
break;
//flags
- case 525:
+ case (int)Tags.Flags:
flags = TagHelper.GetString(tagValue);
break;
//color
- case 543:
+ case (int)Tags.Color:
color = TagHelper.GetColor(tagValue);
break;
//turbo
- case 556:
+ case (int)Tags.Turbo:
turbo = TagHelper.GetBool(tagValue);
break;
//badges
- case 614:
+ case (int)Tags.Badges:
badges = TagHelper.GetString(tagValue, true);
break;
//emotes
- case 653:
+ case (int)Tags.Emotes:
emotes = TagHelper.GetString(tagValue);
break;
//room-id
- case 695:
+ case (int)Tags.RoomId:
channelId = TagHelper.GetLong(tagValue);
break;
//user-id
- case 697:
+ case (int)Tags.UserId:
uid = TagHelper.GetLong(tagValue);
break;
//first-msg
- case 924:
+ case (int)Tags.FirstMsg:
firstMsg = TagHelper.GetBool(tagValue);
break;
//user-type
- case 942 when tagValue.Length > 0:
+ case (int)Tags.UserType when tagValue.Length > 0:
userType = (UserType)tagValue.Sum();
break;
//badge-info
- case 972:
+ case (int)Tags.BadgeInfo:
badgeInfo = TagHelper.GetString(tagValue, true, true);
break;
//subscriber
- case 1076:
+ case (int)Tags.Subscriber:
sub = TagHelper.GetBool(tagValue);
break;
//tmi-sent-ts
- case 1093:
+ case (int)Tags.TmiSentTs:
tmiSentTs = TagHelper.GetLong(tagValue);
break;
//client-nonce
- case 1215:
+ case (int)Tags.ClientNonce:
nonce = TagHelper.GetString(tagValue);
break;
//display-name
- case 1220:
+ case (int)Tags.DisplayName:
displayName = TagHelper.GetString(tagValue);
break;
//returning-chatter
- case 1782:
+ case (int)Tags.ReturningChatter:
returningChatter = TagHelper.GetBool(tagValue);
break;
//reply-parent-msg-id
- case 1873:
+ case (int)Tags.ReplyParentMsgId:
replyMessageId = TagHelper.GetString(tagValue);
break;
//reply-parent-user-id
- case 1993:
+ case (int)Tags.ReplyParentUserId:
replyUserId = TagHelper.GetLong(tagValue);
break;
//reply-parent-msg-body
- case 2098:
+ case (int)Tags.ReplyParentMsgBody:
replyMessageBody = TagHelper.GetString(tagValue, unescape: true);
break;
//pinned-chat-paid-level
- case 2139:
+ case (int)Tags.PinnedChatPaidLevel:
level = TagHelper.GetEnum(tagValue);
break;
//pinned-chat-paid-amount
- case 2263:
+ case (int)Tags.PinnedChatPaidAmount:
paidAmount = TagHelper.GetInt(tagValue);
break;
//reply-parent-user-login
- case 2325:
+ case (int)Tags.ReplyParentUserLogin:
replyUsername = TagHelper.GetString(tagValue);
break;
//pinned-chat-paid-currency
- case 2478:
+ case (int)Tags.PinnedChatPaidCurrency:
currency = TagHelper.GetString(tagValue, intern: true);
break;
//pinned-chat-paid-exponent
- case 2484:
+ case (int)Tags.PinnedChatPaidExponent:
exponent = TagHelper.GetInt(tagValue);
break;
//reply-parent-display-name
- case 2516:
+ case (int)Tags.ReplyParentDisplayName:
replyDisplayName = TagHelper.GetString(tagValue);
break;
//reply-thread-parent-msg-id
- case 2550:
+ case (int)Tags.ReplyThreadParentMsgId:
threadParentMessageid = TagHelper.GetString(tagValue);
break;
//reply-thread-parent-user-login
- case 3002:
+ case (int)Tags.ReplyThreadParentUserLogin:
threadParentUsername = TagHelper.GetString(tagValue);
break;
//pinned-chat-paid-is-system-message
- case 3331:
+ case (int)Tags.PinnedChatPaidIsSystemMessage:
isSystemMessage = TagHelper.GetBool(tagValue);
break;
}
@@ -350,7 +350,7 @@ internal Privmsg(ReadOnlyMemory memory, IrcClient? source = null)
/// Prepend .me
/// Prefer replying to the target message in the same thread instead of creating a new one
/// A cancellation token to stop further execution of asynchronous actions
- public ValueTask ReplyWith(string reply, bool action = false, bool replyInThread = false, CancellationToken cancellationToken = default) =>
+ public ValueTask ReplyWith(string reply, bool action = false, bool replyInThread = false, CancellationToken cancellationToken = default) =>
this.Source?.ReplyTo(this, reply, action, replyInThread, cancellationToken) ?? ValueTask.CompletedTask;
///
@@ -361,7 +361,8 @@ public ValueTask ReplyWith(string reply, bool action = false, bool replyInThread
public static Privmsg Construct(string rawData)
{
ReadOnlyMemory memory = new(Encoding.UTF8.GetBytes(rawData));
- return new(memory);
+ var message = new IrcMessage(memory);
+ return new(ref message);
}
///
diff --git a/MiniTwitch.Irc/Models/Usernotice.cs b/MiniTwitch.Irc/Models/Usernotice.cs
index 07f44b4..b59384f 100644
--- a/MiniTwitch.Irc/Models/Usernotice.cs
+++ b/MiniTwitch.Irc/Models/Usernotice.cs
@@ -68,7 +68,7 @@ namespace MiniTwitch.Irc.Models;
internal UsernoticeType MsgId { get; init; } = UsernoticeType.None;
- internal Usernotice(ReadOnlyMemory memory)
+ internal Usernotice(ref IrcMessage message)
{
// Author
bool isMod = false;
@@ -88,7 +88,7 @@ internal Usernotice(ReadOnlyMemory memory)
long recipientId = 0;
// Channel
- string channelName = memory.Span.FindChannel();
+ string channelName = message.GetChannel();
long channelId = 0;
SubPlan subPlan = SubPlan.None;
@@ -98,7 +98,7 @@ internal Usernotice(ReadOnlyMemory memory)
string id = string.Empty;
string subPlanName = string.Empty;
string systemMessage = string.Empty;
- string message = string.Empty;
+ string content = string.Empty;
string gifterUsername = string.Empty;
string gifterDisplayName = string.Empty;
int cumulativeMonths = 0;
@@ -110,7 +110,7 @@ internal Usernotice(ReadOnlyMemory memory)
int viewerCount = 0;
bool shouldShareStreak = false;
- using IrcTags tags = IrcParsing.ParseTags(memory);
+ using IrcTags tags = message.ParseTags();
foreach (IrcTag tag in tags)
{
ReadOnlySpan tagKey = tag.Key.Span;
@@ -119,167 +119,167 @@ internal Usernotice(ReadOnlyMemory memory)
switch (tagKey.Sum())
{
//id
- case 205:
+ case (int)Tags.Id:
id = TagHelper.GetString(tagValue);
break;
//mod
- case 320:
+ case (int)Tags.Mod:
isMod = TagHelper.GetBool(tagValue);
break;
//flags
- case 525:
+ case (int)Tags.Flags:
flags = TagHelper.GetString(tagValue);
break;
//login
- case 537:
+ case (int)Tags.Login:
username = TagHelper.GetString(tagValue);
break;
//color
- case 543:
+ case (int)Tags.Color:
colorCode = TagHelper.GetColor(tagValue);
break;
//turbo
- case 556:
+ case (int)Tags.Turbo:
isTurbo = TagHelper.GetBool(tagValue);
break;
//msg-id
- case 577:
+ case (int)Tags.MsgId:
this.MsgId = (UsernoticeType)tagValue.Sum();
break;
//badges
- case 614:
+ case (int)Tags.Badges:
badges = TagHelper.GetString(tagValue, true);
break;
//emotes
- case 653:
+ case (int)Tags.Emotes:
emotes = TagHelper.GetString(tagValue);
break;
//room-id
- case 695:
+ case (int)Tags.RoomId:
channelId = TagHelper.GetLong(tagValue);
break;
//user-id
- case 697:
+ case (int)Tags.UserId:
userId = TagHelper.GetLong(tagValue);
break;
//user-type
- case 942 when tagValue.Length > 0:
+ case (int)Tags.UserType when tagValue.Length > 0:
userType = (UserType)tagValue.Sum();
break;
//badge-info
- case 972:
+ case (int)Tags.BadgeInfo:
badgeInfo = TagHelper.GetString(tagValue, true, true);
break;
//system-msg
- case 1049:
+ case (int)Tags.SystemMsg:
systemMessage = TagHelper.GetString(tagValue, unescape: true);
break;
//subscriber
- case 1076:
+ case (int)Tags.Subscriber:
isSubscriber = TagHelper.GetBool(tagValue);
break;
//tmi-sent-ts
- case 1093:
+ case (int)Tags.TmiSentTs:
this.TmiSentTs = TagHelper.GetLong(tagValue);
break;
//display-name
- case 1220:
+ case (int)Tags.DisplayName:
displayName = TagHelper.GetString(tagValue);
break;
//msg-param-color
- case 1489:
+ case (int)Tags.MsgParamColor:
color = (AnnouncementColor)tagValue.Sum();
break;
//msg-param-months
- case 1611:
+ case (int)Tags.MsgParamMonths:
months = TagHelper.GetInt(tagValue);
break;
//msg-param-sub-plan
- case 1748:
+ case (int)Tags.MsgParamSubPlan:
subPlan = (SubPlan)tagValue.Sum();
break;
//msg-param-sender-name
- case 2049:
+ case (int)Tags.MsgParamSenderName:
gifterDisplayName = TagHelper.GetString(tagValue);
break;
//msg-param-gift-months
- case 2082:
+ case (int)Tags.MsgParamGiftMonths:
giftedMonths = TagHelper.GetInt(tagValue);
break;
//msg-param-viewerCount
- case 2125:
+ case (int)Tags.MsgParamViewerCount:
viewerCount = TagHelper.GetInt(tagValue);
break;
//msg-param-recipient-id
- case 2159:
+ case (int)Tags.MsgParamRecipientId:
recipientId = TagHelper.GetLong(tagValue);
break;
//msg-param-sender-login
- case 2169:
+ case (int)Tags.MsgParamSenderLogin:
gifterUsername = TagHelper.GetString(tagValue);
break;
//msg-param-sender-count
- case 2185:
+ case (int)Tags.MsgParamSenderCount:
totalGiftCount = TagHelper.GetInt(tagValue);
break;
//msg-param-sub-plan-name
- case 2210:
+ case (int)Tags.MsgParamSubPlanName:
subPlanName = TagHelper.GetString(tagValue, true, true);
break;
//msg-param-streak-months
- case 2306:
+ case (int)Tags.MsgParamStreakMonths:
monthStreak = TagHelper.GetInt(tagValue);
break;
//msg-param-mass-gift-count
- case 2451:
+ case (int)Tags.MsgParamMassGiftCount:
giftCount = TagHelper.GetInt(tagValue);
break;
//msg-param-cumulative-months
- case 2743:
+ case (int)Tags.MsgParamCumulativeMonths:
cumulativeMonths = TagHelper.GetInt(tagValue);
break;
//msg-param-recipient-user-name
- case 2863:
+ case (int)Tags.MsgParamRecipientUserName:
recipientUsername = TagHelper.GetString(tagValue);
break;
//msg-param-should-share-streak
- case 2872:
+ case (int)Tags.MsgParamShouldShareStreak:
shouldShareStreak = TagHelper.GetBool(tagValue);
break;
//msg-param-recipient-display-name
- case 3174:
+ case (int)Tags.MsgparamRecipientDisplayName:
recipientDisplayName = TagHelper.GetString(tagValue);
break;
}
@@ -287,7 +287,7 @@ internal Usernotice(ReadOnlyMemory memory)
if (this.MsgId is UsernoticeType.Resub or UsernoticeType.Announcement)
{
- message = memory.Span.FindContent(maybeEmpty: true).Content;
+ content = message.HasMessageContent ? message.GetContent().Content : string.Empty;
}
this.Author = new MessageAuthor()
@@ -322,7 +322,7 @@ internal Usernotice(ReadOnlyMemory memory)
this.Id = id;
this.SubPlanName = subPlanName;
this.SystemMessage = systemMessage;
- this.Message = message;
+ this.Message = content;
this.GifterUsername = gifterUsername;
this.GifterDisplayName = gifterDisplayName;
this.CumulativeMonths = cumulativeMonths;
@@ -343,7 +343,8 @@ internal Usernotice(ReadOnlyMemory memory)
public static Usernotice Construct(string rawData)
{
ReadOnlyMemory memory = new(Encoding.UTF8.GetBytes(rawData));
- return new(memory);
+ var message = new IrcMessage(memory);
+ return new(ref message);
}
#pragma warning disable CS8765 // Nullability of type of parameter doesn't match overridden member (possibly because of nullability attributes).
diff --git a/MiniTwitch.Irc/Models/Userstate.cs b/MiniTwitch.Irc/Models/Userstate.cs
index 2b1580a..370244c 100644
--- a/MiniTwitch.Irc/Models/Userstate.cs
+++ b/MiniTwitch.Irc/Models/Userstate.cs
@@ -3,6 +3,7 @@
using MiniTwitch.Common.Extensions;
using MiniTwitch.Irc.Enums;
using MiniTwitch.Irc.Interfaces;
+using MiniTwitch.Irc.Internal.Enums;
using MiniTwitch.Irc.Internal.Models;
using MiniTwitch.Irc.Internal.Parsing;
@@ -37,7 +38,7 @@ public readonly struct Userstate
internal IrcClient? Source { get; init; }
- internal Userstate(ReadOnlyMemory memory, IrcClient? source = null)
+ internal Userstate(ref IrcMessage message, IrcClient? source = null)
{
this.Source = source;
@@ -50,11 +51,11 @@ internal Userstate(ReadOnlyMemory memory, IrcClient? source = null)
bool subscriber = false;
bool turbo = false;
UserType type = UserType.None;
- string channel = memory.Span.FindChannel(true);
+ string channel = message.GetChannel();
string emoteSets = string.Empty;
string nonce = string.Empty;
- using IrcTags tags = IrcParsing.ParseTags(memory);
+ using IrcTags tags = message.ParseTags();
foreach (IrcTag tag in tags)
{
if (tag.Key.Length == 0)
@@ -68,57 +69,57 @@ internal Userstate(ReadOnlyMemory memory, IrcClient? source = null)
{
//mod
- case 320:
+ case (int)Tags.Mod:
mod = TagHelper.GetBool(tagValue);
break;
//vip
- case 335:
+ case (int)Tags.Vip:
vip = TagHelper.GetBool(tagValue);
break;
//color
- case 543:
+ case (int)Tags.Color:
color = TagHelper.GetColor(tagValue);
break;
//turbo
- case 556:
+ case (int)Tags.Turbo:
turbo = TagHelper.GetBool(tagValue);
break;
//badges
- case 614:
+ case (int)Tags.Badges:
badges = TagHelper.GetString(tagValue, true);
break;
//user-type
- case 942 when tagValue.Length > 0:
+ case (int)Tags.UserType when tagValue.Length > 0:
type = (UserType)tagValue.Sum();
break;
//badge-info
- case 972:
+ case (int)Tags.BadgeInfo:
badgeInfo = TagHelper.GetString(tagValue, true, true);
break;
//emote-sets
- case 1030:
+ case (int)Tags.EmoteSets:
emoteSets = TagHelper.GetString(tagValue, true);
break;
//subscriber
- case 1076:
+ case (int)Tags.Subscriber:
subscriber = TagHelper.GetBool(tagValue);
break;
//client-nonce
- case 1215:
+ case (int)Tags.ClientNonce:
nonce = TagHelper.GetString(tagValue);
break;
//display-name
- case 1220:
+ case (int)Tags.DisplayName:
displayName = TagHelper.GetString(tagValue);
break;
}
@@ -153,6 +154,7 @@ internal Userstate(ReadOnlyMemory memory, IrcClient? source = null)
public static Userstate Construct(string rawData)
{
ReadOnlyMemory memory = new(Encoding.UTF8.GetBytes(rawData));
- return new(memory);
+ var message = new IrcMessage(memory);
+ return new(ref message);
}
}
\ No newline at end of file
diff --git a/MiniTwitch.Irc/Models/Whisper.cs b/MiniTwitch.Irc/Models/Whisper.cs
index 8afcfb5..d94cdaa 100644
--- a/MiniTwitch.Irc/Models/Whisper.cs
+++ b/MiniTwitch.Irc/Models/Whisper.cs
@@ -3,6 +3,7 @@
using MiniTwitch.Common.Extensions;
using MiniTwitch.Irc.Enums;
using MiniTwitch.Irc.Interfaces;
+using MiniTwitch.Irc.Internal.Enums;
using MiniTwitch.Irc.Internal.Models;
using MiniTwitch.Irc.Internal.Parsing;
@@ -34,12 +35,12 @@ public readonly struct Whisper
///
public bool IsAction { get; init; }
- internal Whisper(ReadOnlyMemory memory)
+ internal Whisper(ref IrcMessage message)
{
string badges = string.Empty;
Color color = default;
string displayName = string.Empty;
- string username = memory.Span.FindUsername();
+ string username = message.GetUsername();
long uid = 0;
UserType type = UserType.None;
bool turbo = false;
@@ -47,9 +48,8 @@ internal Whisper(ReadOnlyMemory memory)
string emotes = string.Empty;
int id = 0;
string threadId = string.Empty;
- (string content, bool action) = memory.Span.FindContent(maybeAction: true);
-
- using IrcTags tags = IrcParsing.ParseTags(memory);
+ (string content, bool action) = message.GetContent(maybeAction: true);
+ using IrcTags tags = message.ParseTags();
foreach (IrcTag tag in tags)
{
ReadOnlySpan tagKey = tag.Key.Span;
@@ -57,47 +57,47 @@ internal Whisper(ReadOnlyMemory memory)
switch (tagKey.Sum())
{
//color
- case 543:
+ case (int)Tags.Color:
color = TagHelper.GetColor(tagValue);
break;
//turbo
- case 556:
+ case (int)Tags.Turbo:
turbo = TagHelper.GetBool(tagValue);
break;
//badges
- case 614:
+ case (int)Tags.Badges:
badges = TagHelper.GetString(tagValue, true);
break;
//emotes
- case 653:
+ case (int)Tags.Emotes:
emotes = TagHelper.GetString(tagValue);
break;
//user-id
- case 697:
+ case (int)Tags.UserId:
uid = TagHelper.GetLong(tagValue);
break;
//thread-id
- case 882:
+ case (int)Tags.ThreadId:
threadId = TagHelper.GetString(tagValue, true);
break;
//user-type
- case 942 when tagValue.Length > 0:
+ case (int)Tags.UserType when tagValue.Length > 0:
type = (UserType)tagValue.Sum();
break;
//message-id
- case 991:
+ case (int)Tags.MessageId:
id = TagHelper.GetInt(tagValue);
break;
//display-name
- case 1220:
+ case (int)Tags.DisplayName:
displayName = TagHelper.GetString(tagValue);
break;
}
@@ -128,7 +128,8 @@ internal Whisper(ReadOnlyMemory memory)
public static Whisper Construct(string rawData)
{
ReadOnlyMemory memory = new(Encoding.UTF8.GetBytes(rawData));
- return new(memory);
+ var message = new IrcMessage(memory);
+ return new(ref message);
}
///
diff --git a/MiniTwitch.sln b/MiniTwitch.sln
index ac681bc..b9f0ba7 100644
--- a/MiniTwitch.sln
+++ b/MiniTwitch.sln
@@ -20,9 +20,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C1BCE870-D15
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{1AE9EDC5-22F2-4EEB-9B8A-46B264597907}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MiniTwitch.PubSub", "MiniTwitch.PubSub\MiniTwitch.PubSub.csproj", "{91F7B7DD-2061-470C-BCF0-649CD5FEFDA5}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MiniTwitch.PubSub", "MiniTwitch.PubSub\MiniTwitch.PubSub.csproj", "{91F7B7DD-2061-470C-BCF0-649CD5FEFDA5}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MiniTwitch.PubSub.Test", "MiniTwitch.PubSub.Test\MiniTwitch.PubSub.Test.csproj", "{F7E72622-6545-46D4-92A4-493849E0DFCD}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MiniTwitch.PubSub.Test", "MiniTwitch.PubSub.Test\MiniTwitch.PubSub.Test.csproj", "{F7E72622-6545-46D4-92A4-493849E0DFCD}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{3AB3F64E-4D4B-4E24-862F-54E734F5A401}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MiniTwitch.Irc.Benchmarks", "MiniTwitch.Irc.Benchmarks\MiniTwitch.Irc.Benchmarks.csproj", "{E6F90853-811D-4E89-A911-14325A52ECE8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -50,6 +54,10 @@ Global
{F7E72622-6545-46D4-92A4-493849E0DFCD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F7E72622-6545-46D4-92A4-493849E0DFCD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F7E72622-6545-46D4-92A4-493849E0DFCD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E6F90853-811D-4E89-A911-14325A52ECE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E6F90853-811D-4E89-A911-14325A52ECE8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E6F90853-811D-4E89-A911-14325A52ECE8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E6F90853-811D-4E89-A911-14325A52ECE8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -60,6 +68,7 @@ Global
{70E0798C-2704-4905-AE1E-710136EC5C58} = {1AE9EDC5-22F2-4EEB-9B8A-46B264597907}
{91F7B7DD-2061-470C-BCF0-649CD5FEFDA5} = {C1BCE870-D158-438A-91C6-387B2F7677B4}
{F7E72622-6545-46D4-92A4-493849E0DFCD} = {1AE9EDC5-22F2-4EEB-9B8A-46B264597907}
+ {E6F90853-811D-4E89-A911-14325A52ECE8} = {3AB3F64E-4D4B-4E24-862F-54E734F5A401}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2698C4F9-D2CF-447D-945B-6382FF15EA63}