UNPKG

@twurple/common

Version:

Common functions used by the `@twurple` library family.

117 lines (116 loc) 3.79 kB
import { utf8Length, utf8Substring } from '@d-fischer/shared-utils'; import { ChatEmote } from "./ChatEmote.mjs"; /** * Transforms the parts of the given text that are marked as emotes. * * @param text The message text. * @param emoteOffsets The emote offsets. An emote name maps to a list of text ranges. */ export function parseEmotePositions(text, emoteOffsets) { return [...emoteOffsets.entries()] .flatMap(([emote, placements]) => placements.map((placement) => { const [startStr, endStr] = placement.split('-'); const start = +startStr; const end = +endStr; const name = utf8Substring(text, start, end + 1); return { type: 'emote', position: start, length: end - start + 1, id: emote, name, displayInfo: new ChatEmote({ code: name, id: emote }) }; })) .sort((a, b) => a.position - b.position); } /** * Finds the positions of all cheermotes in the given message. * * @param text The message text. * @param names The names of the cheermotes to find. */ export function findCheermotePositions(text, names) { const result = []; const re = new RegExp('(?<=^|\\s)([a-z]+(?:\\d+[a-z]+)*)(\\d+)(?=\\s|$)', 'gi'); let match = null; while ((match = re.exec(text))) { const name = match[1].toLowerCase(); if (names.includes(name)) { const amount = Number(match[2]); result.push({ name, amount, position: utf8Length(text.slice(0, match.index)), length: match[0].length }); } } return result; } /** * Add text parts to the given list of message parts for all the text that's unaccounted for. * * @param text The message text. * @param otherPositions The parsed non-text parts of the message. */ export function fillTextPositions(text, otherPositions) { const messageLength = utf8Length(text); if (!otherPositions.length) { return [ { type: 'text', position: 0, length: messageLength, text: text } ]; } const result = []; let currentPosition = 0; for (const token of otherPositions) { if (token.position > currentPosition) { result.push({ type: 'text', position: currentPosition, length: token.position - currentPosition, text: utf8Substring(text, currentPosition, token.position) }); } result.push(token); currentPosition = token.position + token.length; } if (currentPosition < messageLength) { result.push({ type: 'text', position: currentPosition, length: messageLength - currentPosition, text: utf8Substring(text, currentPosition) }); } return result; } /** * Parse a chat message with emotes and optionally cheermotes. * * @param text The message text. * @param emoteOffsets The emote offsets. An emote name maps to a list of text ranges. * @param cheermoteNames The names of the cheermotes to find. Will not do cheermote parsing if not given. */ export function parseChatMessage(text, emoteOffsets, cheermoteNames) { if (!text) { return []; } const foundParts = parseEmotePositions(text, emoteOffsets); if (cheermoteNames) { foundParts.push(...findCheermotePositions(text, cheermoteNames).map((cm) => ({ type: 'cheer', ...cm }))); foundParts.sort((a, b) => a.position - b.position); } return fillTextPositions(text, foundParts); }