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));
}
}