@dada78641/bwrepinfo
Version:
Parses StarCraft: Remastered replay files and returns human-readable information
133 lines (119 loc) • 4.75 kB
JavaScript
// bwrepinfo <https://github.com/msikma/hydrabot>
// © MIT license
import {escapeInsideBlock, formatMessageTime, wrapSpoiler, wrapCodeBlock} from '../util/format.js'
// All available ANSI colors, as mapped to Brood War player colors.
// Note that not all Brood War colors are represented here.
// Players without an ANSI equivalent are given an arbitrary color.
const ANSI_COLORS = {
'Gray': 30,
'Black': 30,
'Red': 31,
'Green': 32,
'Pale green': 32,
'Yellow': 33,
'Pale yellow': 33,
'Blue': 34,
'Navy': 34,
'Pink': 35,
'Purple': 35,
'Magenta': 35,
'Teal': 36,
'Turquoise': 36,
'White': 37
}
// The color used if we've run out of colors (note: max players is normally 8).
const FALLBACK_COLOR = 'White'
/**
* Limits the chat to a maximum number of lines.
*
* This is necessary to prevent the chat log from being too long for an Embed.
* If the number of messages is within the limit, the original array is returned.
*/
function limitChatMessages(arr, limit, omit) {
if (arr.length <= limit * 2) {
return arr
}
return [
...arr.slice(0, limit),
omit(arr.length - (limit * 2)),
...arr.slice(-limit)
]
}
/**
* Colorizes a string using an ANSI escape sequence.
*/
function formatColorString(str, id) {
return `\u001b[0;${id}m${str}\u001b[0m`
}
/**
* Grays out a string by giving it a gray color ANSI escape sequence.
*
* Can be used for notifications, such as the "messages omitted" line.
*/
function formatNoteString(str) {
return formatColorString(str, ANSI_COLORS['Gray'])
}
/**
* Formats a series of replay chat messages into a Markdown code block.
*
* This utilizes ANSI escape sequences to colorize the text to be similar to the players'
* original in-game colors. So if a player was red, their messages will have a red username.
*
* Not all colors have ANSI equivalents, and random colors are used to represent those players.
*
* If the number of chat messages exceeds a threshold, messages in the center will be omitted.
* The 'maxLines' property determines the threshold; if it's 4, then at most 4 lines at the top
* and 4 lines at the bottom will be permitted, plus a "(x messages omitted)" line in between.
*/
export function formatChatToCodeBlock(messages, players, useSpoilerMessages = true, maxLines = 4) {
if (messages.length === 0) {
return ''
}
const assignedPlayers = {}
const assignedColors = {}
const allColors = Object.keys(ANSI_COLORS)
// Assign colors to all players. First we start with the "preferred" colors,
// which are the ones for which there is a direct ANSI equivalent.
for (const player of players) {
const colorName = player.color.name
const preferredColor = ANSI_COLORS[colorName]
if (!preferredColor) continue
assignedPlayers[player.ID] = colorName
assignedColors[colorName] = player.ID
}
// Now assign the rest of the colors to all players without a preferred color.
// This just gives the next available color one at a time.
// If there are more than 8 players (which should never happen), all remaining
// players will get the fallback color.
for (const player of players) {
if (assignedPlayers[player.ID]) continue
const assigned = Object.keys(assignedColors)
const unusedColors = allColors.filter(key => !assigned.includes(key))
if (!unusedColors.length) {
assignedPlayers[player.ID] = FALLBACK_COLOR
continue
}
const colorName = unusedColors[0]
assignedPlayers[player.ID] = colorName
assignedColors[colorName] = player.ID
}
// Iterate through all messages and format them as a colored string.
const messagesSubset = limitChatMessages(messages, maxLines, n => ({note: `(${n} message${n === 1 ? '' : 's'} omitted)`}))
const messagesColored = []
for (const message of messagesSubset) {
// If the chat is long, there will be a "(x messages omitted)" line here.
// Color it gray if so.
if (message.note) {
messagesColored.push(`${formatNoteString(message.note)}`)
continue
}
// Get the player's assigned color and format their message with it.
const color = assignedPlayers[message.sender.ID]
const colorId = ANSI_COLORS[color ?? FALLBACK_COLOR]
messagesColored.push(`${formatNoteString(`[${formatMessageTime(message.timeMs)}]`)} ${formatColorString(`${escapeInsideBlock(message.sender.name)}:`, colorId)} ${escapeInsideBlock(message.message)}`)
}
// Wrap the chat messages in a code block tagged as ANSI.
const chatLogMonospace = wrapCodeBlock(messagesColored.join('\n'), 'ansi')
// Chat messages can spoil the result of a game, so we optionally wrap it in spoiler tags as well.
return useSpoilerMessages ? wrapSpoiler(chatLogMonospace) : chatLogMonospace
}