using System; using System.Collections.Generic; using System.Linq; using System.Numerics; using System.Reflection; using System.Text.RegularExpressions; namespace axXez.TS3.Protocol { /// A single key-value entry from a TeamSpeak text-protocol message, with typed accessors and deserialization helpers. public class TsDataEntry { private static readonly Regex _kvRegex = new(@"[ ]?(?[^ =\n|]*)=(?[^ \n|]*)", RegexOptions.Compiled); private readonly Dictionary _data; /// Returns the value of the first matching key, or an empty string if none is found. public string this[params string[] keys] => GetValue(keys); internal TsDataEntry(Dictionary data) => _data = data; internal static TsDataEntry Parse(string part) { var dict = new Dictionary(); foreach (Match match in _kvRegex.Matches(part)) { string key = match.Groups["name"].Value; if (!dict.TryAdd(key, TsEncoding.Unescape(match.Groups["value"].Value))) GlobalLogger.Log("TsEncoding", $"Duplicate key '{key}' in entry, ignoring.", LogType.Warning); } return new TsDataEntry(dict); } /// Returns true and sets to the first matching key found in this entry. public bool ContainsKey(out string key, params string[] keys) { key = keys.FirstOrDefault(_data.ContainsKey); return key != null; } /// Returns true and sets to the raw string value of the first matching key, or null if not found. public bool TryGetValue(out string value, params string[] keys) { string key = keys.FirstOrDefault(_data.ContainsKey); value = key != null ? _data[key] : null; return key != null; } /// Returns the raw string value of the first matching key, or an empty string if not found. public string GetValue(params string[] keys) { string key = keys.FirstOrDefault(_data.ContainsKey); return key != null ? _data[key] : ""; } /// Returns the numeric value of the first matching key. Returns -1 for signed types or 0 for unsigned types when the key is not found or the value is unparseable. public T GetValue(params string[] keys) where T : INumber { string value = GetValue(keys); if (!string.IsNullOrEmpty(value) && T.TryParse(value, null, out T result)) return result; return T.TryParse("-1", null, out T fallback) ? fallback : T.Zero; } internal object GetEntity(Type targetType) => Activator.CreateInstance(targetType, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, [this], null); /// Deserializes this entry into a of type . public T GetEntity() where T : TsEntity => (T)Activator.CreateInstance(typeof(T), BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, [this], null); /// Returns the Unix-timestamp field of the first matching key as a local . public DateTime GetDateTime(params string[] keys) => new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) .AddSeconds(GetValue(keys)) .ToLocalTime(); /// Parses a TeamSpeak boolean string ("0" or "1") into a . public static bool TryParseBool(string input, out bool value) { if (!TryParseNumber(input, out var b)) { value = false; return false; } value = b == 1; return true; } /// Parses a numeric string into a value of type . public static bool TryParseNumber(string input, out T value) where T : INumber { if (!T.TryParse(input, null, out var n)) { value = default; return false; } value = n; return true; } /// Parses a millisecond-count string into a boxed as object. public static bool TryParseTimespan(string input, out object value) { if (!TryParseNumber(input, out var ms)) { value = default; return false; } value = TimeSpan.FromMilliseconds(ms); return true; } /// Parses a Unix-timestamp string into a local boxed as object. public static bool TryParseDatetime(string input, out object value) { if (!TryParseNumber(input, out var seconds)) { value = default; return false; } value = DateTimeOffset.FromUnixTimeSeconds(seconds).LocalDateTime; return true; } internal Dictionary ToDictionary() => _data; internal void Merge(TsDataEntry incoming) { foreach (var kvp in incoming._data) _data[kvp.Key] = kvp.Value; } internal bool Inject(string key, string value) { if (!_data.TryGetValue(key, out var current) || !value.Equals(current)) { _data[key] = value; return true; } return false; } } }