using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; namespace axXez.TS3.Protocol { /// /// Encodes, decodes, builds, and parses the TeamSpeak text protocol format /// shared by both the ServerQuery (TCP) and UDP voice transports. /// public static class TsEncoding { private static readonly Dictionary _escapeMap = new() { ['\\'] = "\\\\", ['/'] = "\\/", [' '] = "\\s", ['|'] = "\\p", ['\f'] = "\\f", ['\n'] = "\\n", ['\r'] = "\\r", ['\t'] = "\\t", ['\v'] = "\\v", }; private static readonly Dictionary _unescapeMap = new() { ['\\'] = '\\', ['/'] = '/', ['s'] = ' ', ['p'] = '|', ['f'] = '\f', ['n'] = '\n', ['r'] = '\r', ['t'] = '\t', ['v'] = '\v', }; private static readonly Regex _bbcodeEscape = new(@"(\[\/?(?!B|I|U|COLOR|URL|LEFT|CENTER|RIGHT|SIZE|IMG|LIST)=?[\w\d]*?\]|!)\[\/?(?=B|I|U|COLOR|URL|LEFT|CENTER|RIGHT|SIZE|IMG|LIST)", RegexOptions.IgnoreCase | RegexOptions.Compiled); /// Escapes a string to TeamSpeak text protocol format. public static string Escape(string str) { var sb = new StringBuilder(str.Length * 2); foreach (char ch in str) { if (_escapeMap.TryGetValue(ch, out var esc)) sb.Append(esc); else sb.Append(ch); } return sb.ToString(); } /// Unescapes a TeamSpeak text protocol formatted string. public static string Unescape(string str) { var sb = new StringBuilder(str.Length); for (int i = 0; i < str.Length; i++) { if (str[i] == '\\') { if (++i >= str.Length) throw new FormatException("Invalid escape sequence."); if (!_unescapeMap.TryGetValue(str[i], out char mapped)) throw new FormatException($"Unknown escape: \\{str[i]}"); sb.Append(mapped); } else { sb.Append(str[i]); } } return sb.ToString(); } /// Builds a TeamSpeak parameter string without a command name. public static string Build(Dictionary parameters) => parameters is null ? "" : Build(parameters.ToDictionary( kv => kv.Key, kv => kv.Value is bool b ? (b ? "1" : "0") : kv.Value?.ToString())); /// Builds a TeamSpeak parameter string without a command name. public static string Build(Dictionary parameters) { if (parameters is null) return ""; var sb = new StringBuilder(); foreach (var kv in parameters) { if (kv.Value == null) continue; if (sb.Length > 0) sb.Append(' '); sb.Append(kv.Key); sb.Append('='); sb.Append(Escape(kv.Value)); } return sb.ToString(); } /// Builds a TeamSpeak command string from a name and parameters. public static string Build(string name, Dictionary parameters) => $"{name} {Build(parameters)}"; /// Builds a TeamSpeak command string from a name and string parameters. public static string Build(string name, Dictionary parameters) => $"{name} {Build(parameters)}"; /// Parses a TeamSpeak command string into a . public static TsMessage Parse(string raw) => TsMessage.Parse(raw); /// Fixes TS6 BBCode rendering bugs caused by unknown tags or '!' immediately before a valid BBCode tag. public static string FixTs6BBCode(string str) => _bbcodeEscape.Replace(str, @"\${1}[/"); /// public static string ToTsFormat(this string str) => Escape(str); /// public static string FromTsFormat(this string str) => Unescape(str); /// Applies BBCode fixes then escapes to TeamSpeak ServerQuery format. Use this for outgoing text messages. public static string ToTsMessageFormat(this string str) => Escape(FixTs6BBCode(str)); } }