"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var chat_formatter_exports = {}; __export(chat_formatter_exports, { formatText: () => formatText, linkRegex: () => linkRegex, stripFormatting: () => stripFormatting }); module.exports = __toCommonJS(chat_formatter_exports); /** * Chat parser * Pokemon Showdown - http://pokemonshowdown.com/ * * Parses formate. * * @license MIT */ const linkRegex = /(?:(?:https?:\/\/[a-z0-9-]+(?:\.[a-z0-9-]+)*|www\.[a-z0-9-]+(?:\.[a-z0-9-]+)+|\b[a-z0-9-]+(?:\.[a-z0-9-]+)*\.(?:(?:com?|org|net|edu|info|us|jp)\b|[a-z]{2,3}(?=:[0-9]|\/)))(?::[0-9]+)?(?:\/(?:(?:[^\s()&<>[\]]|&|"|\((?:[^\s()<>&[\]]|&)*\)|\[(?:[^\s()<>&[\]]|&)*])*(?:[^\s()[\]{}".,!?;:&<>*`^~\\]|\((?:[^\s()<>&[\]]|&)*\)))?)?|[a-z0-9.]+@[a-z0-9-]+(?:\.[a-z0-9-]+)*\.[a-z]{2,})(?![^ ]*>)/ig; class TextFormatter { constructor(str, isTrusted = false, replaceLinebreaks = false, showSyntax = false) { str = `${str}`.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); str = str.replace(linkRegex, (uri) => { if (showSyntax) return `${uri}`; let fulluri; if (/^[a-z0-9.]+@/ig.test(uri)) { fulluri = "mailto:" + uri; } else { fulluri = uri.replace(/^([a-z]*[^a-z:])/g, "http://$1"); if (uri.substr(0, 24) === "https://docs.google.com/" || uri.substr(0, 16) === "docs.google.com/") { if (uri.startsWith("https")) uri = uri.slice(8); if (uri.substr(-12) === "?usp=sharing" || uri.substr(-12) === "&usp=sharing") uri = uri.slice(0, -12); if (uri.substr(-6) === "#gid=0") uri = uri.slice(0, -6); let slashIndex = uri.lastIndexOf("/"); if (uri.length - slashIndex > 18) slashIndex = uri.length; if (slashIndex - 4 > 19 + 3) { uri = `${uri.slice(0, 19)}${uri.slice(19, slashIndex - 4)}${uri.slice(slashIndex - 4)}`; } } } return `${uri}`; }); this.str = str; this.buffers = []; this.stack = []; this.isTrusted = isTrusted; this.replaceLinebreaks = this.isTrusted || replaceLinebreaks; this.showSyntax = showSyntax; this.offset = 0; } // debugAt(i=0, j=i+1) { console.log(`${this.slice(0, i)}[${this.slice(i, j)}]${this.slice(j, this.str.length)}`); } slice(start, end) { return this.str.slice(start, end); } at(start) { return this.str.charAt(start); } /** * We've encountered a possible start for a span. It's pushed onto our span * stack. * * The span stack saves the start position so it can be replaced with HTML * if we find an end for the span, but we don't actually replace it until * `closeSpan` is called, so nothing happens (it stays plaintext) if no end * is found. */ pushSpan(spanType, start, end) { this.pushSlice(start); this.stack.push([spanType, this.buffers.length]); this.buffers.push(this.slice(start, end)); this.offset = end; } pushSlice(end) { if (end !== this.offset) { this.buffers.push(this.slice(this.offset, end)); this.offset = end; } } closeParenSpan(start) { let stackPosition = -1; for (let i = this.stack.length - 1; i >= 0; i--) { const span = this.stack[i]; if (span[0] === "(") { stackPosition = i; break; } if (span[0] !== "spoiler") break; } if (stackPosition === -1) return false; this.pushSlice(start); while (this.stack.length > stackPosition) this.popSpan(start); this.offset = start; return true; } /** * We've encountered a possible end for a span. If it's in the span stack, * we transform it into HTML. */ closeSpan(spanType, start, end) { let stackPosition = -1; for (let i = this.stack.length - 1; i >= 0; i--) { const span2 = this.stack[i]; if (span2[0] === spanType) { stackPosition = i; break; } } if (stackPosition === -1) return false; this.pushSlice(start); while (this.stack.length > stackPosition + 1) this.popSpan(start); const span = this.stack.pop(); const startIndex = span[1]; let tagName = ""; let attrs = ""; switch (spanType) { case "_": tagName = "i"; break; case "*": tagName = "b"; break; case "~": tagName = "s"; break; case "^": tagName = "sup"; break; case "\\": tagName = "sub"; break; case "|": tagName = "span"; attrs = this.showSyntax ? ' class="spoiler-shown"' : ' class="spoiler"'; break; } const syntax = this.showSyntax ? `${spanType}${spanType}` : ""; if (tagName) { this.buffers[startIndex] = `${syntax}<${tagName}${attrs}>`; this.buffers.push(`${syntax}`); this.offset = end; } return true; } /** * Ends a span without an ending symbol. For most spans, this means * they don't take effect, but certain spans like spoiler tags don't * require ending symbols. */ popSpan(end) { const span = this.stack.pop(); if (!span) return false; this.pushSlice(end); switch (span[0]) { case "spoiler": this.buffers.push(``); this.buffers[span[1]] = this.showSyntax ? `` : ``; break; case ">": this.buffers.push(``); this.buffers[span[1]] = ``; break; default: break; } return true; } popAllSpans(end) { while (this.stack.length) this.popSpan(end); this.pushSlice(end); } toUriComponent(html) { const component = html.replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, "&"); return encodeURIComponent(component); } /** * Handles special cases. */ runLookahead(spanType, start) { switch (spanType) { case "`": { let delimLength = 0; let i = start; while (this.at(i) === "`") { delimLength++; i++; } let curDelimLength = 0; while (i < this.str.length) { const char = this.at(i); if (char === "\n") break; if (char === "`") { curDelimLength++; } else { if (curDelimLength === delimLength) break; curDelimLength = 0; } i++; } if (curDelimLength !== delimLength) return false; const end = i; this.pushSlice(start); let innerStart = start + delimLength; let innerEnd = i - delimLength; if (innerStart + 1 >= innerEnd) { } else if (this.at(innerStart) === " " && this.at(innerEnd - 1) === " ") { innerStart++; innerEnd--; } else if (this.at(innerStart) === " " && this.at(innerStart + 1) === "`") { innerStart++; } else if (this.at(innerEnd - 1) === " " && this.at(innerEnd - 2) === "`") { innerEnd--; } if (this.showSyntax) this.buffers.push(`${this.slice(start, innerStart)}`); this.buffers.push(``); this.buffers.push(this.slice(innerStart, innerEnd)); this.buffers.push(``); if (this.showSyntax) this.buffers.push(`${this.slice(innerEnd, end)}`); this.offset = end; } return true; case "[": { if (this.slice(start, start + 2) !== "[[") return false; let i = start + 2; let colonPos = -1; let anglePos = -1; while (i < this.str.length) { const char = this.at(i); if (char === "]" || char === "\n") break; if (char === ":" && colonPos < 0) colonPos = i; if (char === "&" && this.slice(i, i + 4) === "<") anglePos = i; i++; } if (this.slice(i, i + 2) !== "]]") return false; this.pushSlice(start); this.offset = i + 2; let termEnd = i; let uri = ""; if (anglePos >= 0 && this.slice(i - 4, i) === ">") { uri = this.slice(anglePos + 4, i - 4); termEnd = anglePos; if (this.at(termEnd - 1) === " ") termEnd--; uri = encodeURI(uri.replace(/^([a-z]*[^a-z:])/g, "http://$1")); } let term = this.slice(start + 2, termEnd).replace(/<\/?[au](?: [^>]+)?>/g, ""); if (this.showSyntax) { term += `${this.slice(termEnd, i)}`; } else if (uri && !this.isTrusted) { const shortUri = uri.replace(/^https?:\/\//, "").replace(/^www\./, "").replace(/\/$/, ""); term += ` <${shortUri}>`; uri += '" rel="noopener'; } if (colonPos > 0) { const key = this.slice(start + 2, colonPos).toLowerCase(); switch (key) { case "w": case "wiki": if (this.showSyntax) break; term = term.slice(term.charAt(key.length + 1) === " " ? key.length + 2 : key.length + 1); uri = `//en.wikipedia.org/w/index.php?title=Special:Search&search=${this.toUriComponent(term)}`; term = `wiki: ${term}`; break; case "pokemon": case "item": case "type": case "category": if (this.showSyntax) { this.buffers.push(`${this.slice(start, this.offset)}`); return true; } term = term.slice(term.charAt(key.length + 1) === " " ? key.length + 2 : key.length + 1); let display = ""; if (this.isTrusted) { display = ``; } else { display = `[${term}]`; } let dir = key; if (key === "item") dir += "s"; if (key === "category") dir = "categories"; uri = `//dex.pokemonshowdown.com/${dir}/${toID(term)}`; term = display; } } if (!uri) { uri = `//www.google.com/search?ie=UTF-8&btnI&q=${this.toUriComponent(term)}`; } if (this.showSyntax) { this.buffers.push(`[[${term}]]`); } else { this.buffers.push(`${term}`); } } return true; case "<": { if (this.slice(start, start + 8) !== "<<") return false; let i = start + 8; while (/[a-z0-9-]/.test(this.at(i))) i++; if (this.slice(i, i + 8) !== ">>") return false; this.pushSlice(start); const roomid = this.slice(start + 8, i); if (this.showSyntax) { this.buffers.push(`<<${roomid}>>`); } else { this.buffers.push(`«${roomid}»`); } this.offset = i + 8; } return true; case "a": case "u": { let i = start + 2; while (this.at(i) !== "<" || this.at(i + 1) !== "/" || this.at(i + 3) !== ">") i++; i += 4; this.pushSlice(i); } return true; } return false; } get() { let beginningOfLine = this.offset; for (let i = beginningOfLine; i < this.str.length; i++) { const char = this.at(i); switch (char) { case "_": case "*": case "~": case "^": case "\\": case "|": if (this.at(i + 1) === char && this.at(i + 2) !== char) { if (!(this.at(i - 1) !== " " && this.closeSpan(char, i, i + 2))) { if (this.at(i + 2) !== " ") this.pushSpan(char, i, i + 2); } if (i < this.offset) { i = this.offset - 1; break; } } while (this.at(i + 1) === char) i++; break; case "(": this.stack.push(["(", -1]); break; case ")": this.closeParenSpan(i); if (i < this.offset) { i = this.offset - 1; break; } break; case "`": if (this.at(i + 1) === "`") this.runLookahead("`", i); if (i < this.offset) { i = this.offset - 1; break; } while (this.at(i + 1) === "`") i++; break; case "[": this.runLookahead("[", i); if (i < this.offset) { i = this.offset - 1; break; } while (this.at(i + 1) === "[") i++; break; case ":": if (i < 7) break; if (this.slice(i - 7, i + 1).toLowerCase() === "spoiler:" || this.slice(i - 8, i + 1).toLowerCase() === "spoilers:") { if (this.at(i + 1) === " ") i++; this.pushSpan("spoiler", i + 1, i + 1); } break; case "&": if (i === beginningOfLine && this.slice(i, i + 4) === ">") { if (!"._/=:;".includes(this.at(i + 4)) && !["w<", "w>"].includes(this.slice(i + 4, i + 9))) { this.pushSpan(">", i, i); } } else { this.runLookahead("<", i); } if (i < this.offset) { i = this.offset - 1; break; } while (this.slice(i + 1, i + 5) === "lt;&") i += 4; break; case "<": this.runLookahead("a", i); if (i < this.offset) { i = this.offset - 1; break; } break; case "\r": case "\n": this.popAllSpans(i); if (this.replaceLinebreaks) { this.buffers.push(`
`); this.offset++; } beginningOfLine = i + 1; break; } } this.popAllSpans(this.str.length); return this.buffers.join(""); } } function formatText(str, isTrusted = false, replaceLinebreaks = false, showSyntax = false) { return new TextFormatter(str, isTrusted, replaceLinebreaks, showSyntax).get(); } function stripFormatting(str) { str = str.replace( /\*\*([^\s*]+)\*\*|__([^\s_]+)__|~~([^\s~]+)~~|``([^\s`]+)``|\^\^([^\s^]+)\^\^|\\([^\s\\]+)\\/g, (match, $1, $2, $3, $4, $5, $6) => $1 || $2 || $3 || $4 || $5 || $6 ); return str.replace(/\[\[(?:([^<]*)\s*<[^>]+>|([^\]]+))\]\]/g, (match, $1, $2) => $1 || $2 || ""); } //# sourceMappingURL=chat-formatter.js.map