mirror of
https://codeberg.org/Ikatono/TierMaker.git
synced 2025-10-28 20:45:35 -05:00
forgot to commit for a while
This commit is contained in:
209
TwitchMessageTags.cs
Normal file
209
TwitchMessageTags.cs
Normal file
@@ -0,0 +1,209 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class TwitchMessageTags : IDictionary<string, string>
|
||||
{
|
||||
public Dictionary<string, string> Tags = new();
|
||||
public TwitchMessageTags()
|
||||
{
|
||||
|
||||
}
|
||||
public TwitchMessageTags(TwitchMessageTags other)
|
||||
{
|
||||
Tags = new(other);
|
||||
}
|
||||
private enum ParseState
|
||||
{
|
||||
FindingKey,
|
||||
FindingValue,
|
||||
ValueEscaped,
|
||||
}
|
||||
//TODO this should be unit tested
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="s"></param>
|
||||
/// <returns></returns>
|
||||
public static TwitchMessageTags Parse(string s)
|
||||
{
|
||||
s = s.TrimStart('@');
|
||||
TwitchMessageTags tags = new();
|
||||
string key = "";
|
||||
string value = "";
|
||||
var state = ParseState.FindingKey;
|
||||
foreach (char c in s)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case ParseState.FindingKey:
|
||||
if (c == '=')
|
||||
state = ParseState.FindingValue;
|
||||
else if (c == ';')
|
||||
{
|
||||
state = ParseState.FindingKey;
|
||||
tags.Add(key, "");
|
||||
key = "";
|
||||
}
|
||||
else if (c == ' ')
|
||||
{
|
||||
tags.Add(key, "");
|
||||
goto EndParse;
|
||||
}
|
||||
else
|
||||
key += c;
|
||||
break;
|
||||
case ParseState.FindingValue:
|
||||
if (c == '\\')
|
||||
{
|
||||
state = ParseState.ValueEscaped;
|
||||
}
|
||||
else if (c == ';')
|
||||
{
|
||||
tags.Add(key, value);
|
||||
key = value = "";
|
||||
state = ParseState.FindingKey;
|
||||
}
|
||||
else if (c == ' ')
|
||||
{
|
||||
tags.Add(key, value);
|
||||
goto EndParse;
|
||||
}
|
||||
else if ("\r\n\0".Contains(c))
|
||||
throw new ArgumentException("Invalid character in tag string", nameof(s));
|
||||
else
|
||||
{
|
||||
value += c;
|
||||
}
|
||||
break;
|
||||
case ParseState.ValueEscaped:
|
||||
if (c == ':')
|
||||
{
|
||||
value += ';';
|
||||
state = ParseState.FindingValue;
|
||||
}
|
||||
else if (c == 's')
|
||||
{
|
||||
value += ' ';
|
||||
state = ParseState.FindingValue;
|
||||
}
|
||||
else if (c == '\\')
|
||||
{
|
||||
value += '\\';
|
||||
state = ParseState.FindingValue;
|
||||
}
|
||||
else if (c == 'r')
|
||||
{
|
||||
value += '\r';
|
||||
state = ParseState.FindingValue;
|
||||
}
|
||||
else if (c == 'n')
|
||||
{
|
||||
value += '\n';
|
||||
state = ParseState.FindingValue;
|
||||
}
|
||||
else if (c == ';')
|
||||
{
|
||||
tags.Add(key, value);
|
||||
key = value = "";
|
||||
state = ParseState.FindingKey;
|
||||
}
|
||||
//spaces should already be stripped, but handle this as end of tags just in case
|
||||
else if (c == ' ')
|
||||
{
|
||||
tags.Add(key, value);
|
||||
key = value = "";
|
||||
goto EndParse;
|
||||
}
|
||||
else if ("\r\n\0".Contains(c))
|
||||
throw new ArgumentException("Invalid character in tag string", nameof(s));
|
||||
else
|
||||
{
|
||||
value += c;
|
||||
state = ParseState.FindingValue;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new InvalidEnumArgumentException("Invalid state enum");
|
||||
|
||||
}
|
||||
}
|
||||
//this is reached after processing the last character without hitting a space
|
||||
tags.Add(key, value);
|
||||
EndParse:
|
||||
return tags;
|
||||
}
|
||||
#region IDictionary<string, string?>
|
||||
public string this[string key] { get => ((IDictionary<string, string>)Tags)[key]; set => ((IDictionary<string, string>)Tags)[key] = value; }
|
||||
|
||||
public ICollection<string> Keys => ((IDictionary<string, string>)Tags).Keys;
|
||||
|
||||
public ICollection<string> Values => ((IDictionary<string, string>)Tags).Values;
|
||||
|
||||
public int Count => ((ICollection<KeyValuePair<string, string>>)Tags).Count;
|
||||
|
||||
public bool IsReadOnly => ((ICollection<KeyValuePair<string, string>>)Tags).IsReadOnly;
|
||||
|
||||
public void Add(string key, string value)
|
||||
{
|
||||
((IDictionary<string, string>)Tags).Add(key, value);
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<string, string> item)
|
||||
{
|
||||
((ICollection<KeyValuePair<string, string>>)Tags).Add(item);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
((ICollection<KeyValuePair<string, string>>)Tags).Clear();
|
||||
}
|
||||
|
||||
public bool Contains(KeyValuePair<string, string> item)
|
||||
{
|
||||
return ((ICollection<KeyValuePair<string, string>>)Tags).Contains(item);
|
||||
}
|
||||
|
||||
public bool ContainsKey(string key)
|
||||
{
|
||||
return ((IDictionary<string, string>)Tags).ContainsKey(key);
|
||||
}
|
||||
|
||||
public void CopyTo(KeyValuePair<string, string>[] array, int arrayIndex)
|
||||
{
|
||||
((ICollection<KeyValuePair<string, string>>)Tags).CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable<KeyValuePair<string, string>>)Tags).GetEnumerator();
|
||||
}
|
||||
|
||||
public bool Remove(string key)
|
||||
{
|
||||
return ((IDictionary<string, string>)Tags).Remove(key);
|
||||
}
|
||||
|
||||
public bool Remove(KeyValuePair<string, string> item)
|
||||
{
|
||||
return ((ICollection<KeyValuePair<string, string>>)Tags).Remove(item);
|
||||
}
|
||||
|
||||
public bool TryGetValue(string key, [MaybeNullWhen(false)] out string value)
|
||||
{
|
||||
return ((IDictionary<string, string>)Tags).TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable)Tags).GetEnumerator();
|
||||
}
|
||||
#endregion //IDictionary<string, string?>
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user