UNPKG

serverquery

Version:

Low level TeamSpeak™ 3 ServerQuery protocol implementation

162 lines (125 loc) 3.62 kB
const LIST_DELIMETER = '|'; const LIST_DELIMETER_REGEX = /\|/g; const PARAMS_DELIMETER = ' '; const PARAMS_DELIMETER_REGEX = / +/g; const ASSIGNMENT = '='; const ASSIGNMENT_REGEX = /^(\w+)(?:=(.*))?$/; const INTEGER_REGEX = /^[0-9]+$/; const FLOAT_REGEX = /^[0-9]+.[0-9]+$/; /** * Escape a string. * @param {String} str * Adapted from: https://github.com/gwTumm/node-teamspeak/blob/master/index.js */ export function escape(str) { return String(str) .replace(/\\/g, '\\\\') // Backslash .replace(/\//g, '\\/') // Slash .replace(/\|/g, '\\p') // Pipe .replace(/\n/g, '\\n') // Newline .replace(/\r/g, '\\r') // Carriage Return .replace(/\t/g, '\\t') // Tab .replace(/\v/g, '\\v') // Vertical Tab .replace(/\f/g, '\\f') // Formfeed .replace(/ /g, '\\s'); // Whitespace } /** * Unescape a string. * @param {String} str * Adapted from: https://github.com/gwTumm/node-teamspeak/blob/master/index.js */ export function unescape(str) { return String(str) .replace(/\\s/g, ' ') // Whitespace .replace(/\\p/g, '|') // Pipe .replace(/\\n/g, '\n') // Newline .replace(/\\f/g, '\f') // Formfeed .replace(/\\r/g, '\r') // Carriage Return .replace(/\\t/g, '\t') // Tab .replace(/\\v/g, '\v') // Vertical Tab .replace(/\\\//g, '\/') // Slash .replace(/\\\\/g, '\\'); // Backslash } /** * Serialize a key: camelCase -> snake_case * @param {String} key */ export function serializeKey(key) { return key .replace(/[a-z][A-Z]/g, match => match[0] + '_' + match[1].toLowerCase()); } /** * Deserialize a key: snake_case -> camelCase * @param {String} key */ export function deserializeKey(key) { return key .replace(/_[a-z]/g, match => match[1].toUpperCase()); } /** * Serialize a value. * @param {String|[String]} value */ export function serializeValue(value) { if (value instanceof Array) { return value.map(escape).join('|'); } return escape(value); } /** * Deserialize a value. * @param {String} value */ export function deserializeValue(value) { // parse as number, if it is number like if (INTEGER_REGEX.test(value)) { return Number.parseInt(value, 10); } if (FLOAT_REGEX.test(value)) { return Number.parseFloat(value); } return unescape(value); } /** * Serialize a command. * @param {String} cmd * @param {[String]} flags * @param {Object} params */ export function serializeCommand(cmd, flags, params) { if (flags.length) { cmd += ' -' + flags.join(' -'); } params = Object.keys(params) .map(k => `${serializeKey(k)}=${serializeValue(params[k])}`) .join(' '); if (params) { cmd += ' ' + params; } return cmd + '\r\n'; } /** * Parse a response. * @param {String} response * @param {Boolean} skipListCheck */ export function parseResponse(response, skipListCheck = false) { // got a list as response if (!skipListCheck && LIST_DELIMETER_REGEX.test(response)) { return response .split(LIST_DELIMETER_REGEX) .map(item => parseResponse(item, true)); } let fields = Object.create(null); response .split(PARAMS_DELIMETER_REGEX) .forEach(pair => { let [, key, value] = pair.match(ASSIGNMENT_REGEX); if (value) { fields[deserializeKey(key)] = deserializeValue(value); } else { fields[deserializeKey(key)] = null; } }); return fields; }