cool color and format utilities

This commit is contained in:
Evert Prants 2022-09-24 12:16:07 +03:00
parent 2b027d57ad
commit d11a983319
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
4 changed files with 228 additions and 2 deletions

View File

@ -1,4 +1,8 @@
import { IRCBot } from '../bot'; import { IRCBot } from '../bot';
import {
applyTextColor,
wrapFormattedText,
} from '../utility/message-formatting';
import { NickServValidator } from '../utility/nickserv-validator'; import { NickServValidator } from '../utility/nickserv-validator';
const bot = new IRCBot({ const bot = new IRCBot({
@ -29,7 +33,7 @@ bot.on('supported-modes', (supported) => {
// bot.on('line', console.log); // bot.on('line', console.log);
bot.on('message', ({ message, to, nickname }) => { bot.on('message', ({ message, to, nickname }) => {
console.log(`[${to}] ${nickname}: ${message}`); console.log(`[${to}] ${nickname}: ${wrapFormattedText(message)}`);
if (message.startsWith('!test')) { if (message.startsWith('!test')) {
nickserv nickserv
@ -46,6 +50,11 @@ bot.on('message', ({ message, to, nickname }) => {
return; return;
} }
if (message.startsWith('!colors')) {
bot.send(to, `${applyTextColor('blue', 'cool blue text!')}`);
return;
}
if (message.startsWith('!whois')) { if (message.startsWith('!whois')) {
bot.whois(nickname).then(console.log); bot.whois(nickname).then(console.log);
return; return;

View File

@ -5,6 +5,7 @@ export * from './types/impl.interface';
export * from './utility/collector'; export * from './utility/collector';
export * from './utility/estimate-prefix'; export * from './utility/estimate-prefix';
export * from './utility/formatstr'; export * from './utility/formatstr';
export * from './utility/message-formatting';
export * from './utility/mode-from-prefix'; export * from './utility/mode-from-prefix';
export * from './utility/nickserv-validator'; export * from './utility/nickserv-validator';
export * from './utility/parser'; export * from './utility/parser';

View File

@ -14,7 +14,7 @@ export type IRCCommunicatorEvents = {
/** /**
* Supported channel user modes from the server (e.g. `ohv: @%+`) * Supported channel user modes from the server (e.g. `ohv: @%+`)
*/ */
'supported-modes': (modes: Record<string, unknown>) => void; 'supported-modes': (modes: Record<string, string>) => void;
/** /**
* Everything this server supports. See IRC documentation for command `005` or `RPL_ISUPPORT` for more info. * Everything this server supports. See IRC documentation for command `005` or `RPL_ISUPPORT` for more info.
*/ */

View File

@ -0,0 +1,216 @@
export const IRC_FMT_COLOR_MAP = {
/**
* IRC white `rgb(255,255,255)`
*/
white: '\u00030',
/**
* IRC black `rgb(0,0,0)`
*/
black: '\u00031',
/**
* IRC blue `rgb(0,0,127)`
*/
blue: '\u00032',
/**
* IRC green `rgb(0,147,0)`
*/
green: '\u00033',
/**
* IRC red `rgb(255,0,0)`
*/
red: '\u00034',
/**
* IRC brown `rgb(127,0,0)`
*/
brown: '\u00035',
/**
* IRC purple `rgb(156,0,156)`
*/
purple: '\u00036',
/**
* IRC orange `rgb(252,127,0)`
*/
orange: '\u00037',
/**
* IRC yellow `rgb(255,255,0)`
*/
yellow: '\u00038',
/**
* IRC light green `rgb(0,252,0)`
*/
lightgreen: '\u00039',
/**
* IRC cyan `rgb(0,147,147)`
*/
cyan: '\u000310',
/**
* IRC light cyan `rgb(0,255,255)`
*/
lightcyan: '\u000311',
/**
* IRC light blue `rgb(0,0,252)`
*/
lightblue: '\u000312',
/**
* IRC pink `rgb(255,0,255)`
*/
pink: '\u000313',
/**
* IRC grey `rgb(127,127,127)`
*/
grey: '\u000314',
/**
* IRC light grey `rgb(210,210,210)`
*/
lightgrey: '\u000315',
};
/**
* ^O character, color reset character
*/
export const IRC_FMT_RESET = '\u000F';
export const IRC_FMT_FORMAT_MAP = {
/**
* Normal text
*/
normal: '\u0000',
/**
* <ins>underline text</ins>, `^_`
*/
underline: '\u001F',
/**
* **bold text**, `^B`
*/
bold: '\u0002',
/**
* *italic text*, `^I`
*/
italics: '\u001D',
/**
* reverse color text, `^V`
*/
reverse: '\u0016',
};
/**
* Remove color and format characters from a string.
* @param str Text
* @returns Stripped text
*/
export const stripFormatting = (str: string): string =>
str.replace(/(\x03\d{0,2}(,\d{0,2})?)/g, '').replace(/[\x00-\x1F]/g, '');
/**
* Colorize a string for sending.
* @param color IRC color key
* @param str Text to colorize
* @returns Text with control characters
*/
export const applyTextColor = (
color: keyof typeof IRC_FMT_COLOR_MAP,
str: string,
): string =>
!IRC_FMT_COLOR_MAP[color]
? str
: `${IRC_FMT_COLOR_MAP[color]}${str}${IRC_FMT_RESET}`;
/**
* Apply text formatting to a string for sending.
* @param format IRC format key
* @param str Text to format
* @returns Text with control characters
*/
export const applyTextFormat = (
format: keyof typeof IRC_FMT_FORMAT_MAP,
str: string,
): string =>
!IRC_FMT_FORMAT_MAP[format]
? str
: `${IRC_FMT_FORMAT_MAP[format]}${str}${IRC_FMT_RESET}`;
const styleCheckRe = /[\x00-\x1F]/;
const backRe = /^(\d{1,2})(,(\d{1,2}))?/;
const colourKey = '\x03';
const colourRe = /\x03/g;
const colorKeys = Object.keys(IRC_FMT_COLOR_MAP);
const fmtRegex: [string, RegExp][] = Object.keys(IRC_FMT_FORMAT_MAP).map(
(key) => {
const escaped = encodeURI(
(IRC_FMT_FORMAT_MAP as Record<string, string>)[key],
).replace('%', '\\x');
return [key, new RegExp(escaped + '(.*?)(' + escaped + '|$)')];
},
);
export type IRCTextFormatWrapperFn = (
text: string,
foregroundColor?: string,
backgroundColor?: string,
textFormat?: string,
) => string;
const wrap: IRCTextFormatWrapperFn = (text, fg, bg, fmt) =>
`<format${fg ? ` color="${fg}"` : ''}${bg ? ` background="${bg}"` : ''}${
fmt ? ` text="${fmt}"` : ''
}>${text}</format>`;
/**
* Wrap regions of text that have formatting or colors with a custom wrapper, e.g. HTML.
* Defaults to a psuedo-XML style wrapper `<format color background text />`
* @param line Text with control characters
* @param wrapperFn Wrapper function to use
* @returns Text with applied wrappers
*/
export function wrapFormattedText(line: string, wrapperFn = wrap): string {
// Recheck
if (!styleCheckRe.test(line)) return line;
// split up by the irc style break character ^O
if (line.indexOf(IRC_FMT_RESET) >= 0) {
return line
.split(IRC_FMT_RESET)
.map((value) => wrapFormattedText(value, wrapperFn))
.join('');
}
let result = line;
const parseArr = result.split(colourKey);
for (let i = 0; i < parseArr.length; i++) {
const text = parseArr[i];
const match = text.match(backRe);
const colour = match && colorKeys[+match[1]];
let background = '';
if (!match || !colour) {
// ^C (no colour) ending. Escape current colour and carry on
background = '';
continue;
}
// set the background colour
// we don't override the background local var to support nesting
if (colorKeys[+match[3]]) {
background = colorKeys[+match[3]];
}
// update the parsed text result
result = result.replace(
colourKey + text,
wrapperFn(text.slice(match[0].length), colour, background),
);
}
// Matching styles (italics/bold/underline)
// if only colors were this easy...
fmtRegex.forEach(function ([style, keyregex]) {
if (result.indexOf(style) < 0) return;
result = result.replace(keyregex, (match, text) =>
wrapperFn(text, undefined, undefined, style),
);
});
// replace the reminent colour terminations and be done with it
return result.replace(colourRe, '');
}