function parseBbCode(str) {
	if (typeof str != 'string') {
		// Don't try to process non-string values, e.g. null
		return str;

	}
	// Steam will put a backslash in front of a bracket for a BBCode tag that shouldn't be parsed as BBCode, but our
	// parser doesn't ignore those. Let's just replace "\\[" with some string that's very improbable to exist in a Steam
	// chat message, then replace it again later.

	let replacement = Crypto.randomBytes(32).toString('hex');
	str = str.replace(/\\\[/g, replacement);

	let parsed = BBCode.parse(str, {
		onlyAllowTags: [
			'emoticon',
			'code',
			'pre',
			'img',
			'url',
			'spoiler',
			'quote',
			'random',
			'flip',
			'tradeofferlink',
			'tradeoffer',
			'sticker',
			'gameinvite',
			'og',
			'roomeffect'
		]
	});

	return collapseStrings(parsed.map(processTagNode));

	function processTagNode(node) {
		if (node.tag == 'url') {
			// we only need to post-process attributes in url tags
			for (let i in node.attrs) {
				if (node.attrs[i] == i) {
					// The URL argument gets parsed with the name as its value
					node.attrs.url = node.attrs[i];
					delete node.attrs[i];
				}
			}
		}

		if (node.content) {
			node.content = collapseStrings(node.content.map(processTagNode));
		}

		return node;
	}

	function collapseStrings(arr) {
		// Turn sequences of strings into single strings
		let strStart = null;
		let newContent = [];
		for (let i = 0; i < arr.length; i++) {
			if (typeof arr[i] === 'string') {
				arr[i] = arr[i].replace(new RegExp(replacement, 'g'), '['); // only put in the bracket without the backslash because this is now "parsed"
				if (strStart === null) {
					// This is a string item and we haven't found the start of a string yet
					strStart = i;
				}
			}

			if (typeof arr[i] !== 'string') {
				// This is not a string item
				if (strStart !== null) {
					// We found the end of a string
					newContent.push(arr.slice(strStart, i).join(''));
				}

				newContent.push(arr[i]); // push this item (probably a TagNode)
				strStart = null;
			}
		}

		if (strStart !== null) {
			newContent.push(arr.slice(strStart, arr.length).join(''));
		}

		return newContent;
	}
}
@param {string} str
@returns {(string|BBCodeNode)[]}