schat2-clive/src/play.js

271 lines
7.2 KiB
JavaScript
Executable File
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
const config = require('config');
const fs = require('fs').promises;
const logger = require('simple-node-logger').createSimpleLogger();
const { argv } = require('yargs');
// const timers = require('timers/promises');
const getLeaders = require('./utils/get-leaders');
const style = require('./utils/style');
logger.setLevel(argv.level || 'info');
const points = {};
async function initPoints(identifier) {
try {
const pointsFile = await fs.readFile(`./points-${identifier}.json`, 'utf-8');
Object.assign(points, JSON.parse(pointsFile));
} catch (error) {
if (error.code === 'ENOENT') {
logger.info('Creating new points file');
await fs.writeFile(`./points-${identifier}.json`, '{}');
await initPoints(identifier);
}
}
}
async function setPoints(identifier, defaultKey, user, value, { mode = 'add', key } = {}) {
const gameKey = key || defaultKey;
if (!user) {
logger.warn(`Failed to set ${gameKey} points for missing user`);
return;
}
const userKey = `${user.id}:${user.username}`;
if (!points[gameKey]) {
points[gameKey] = {};
}
if (mode === 'add') {
points[gameKey][userKey] = (points[gameKey][userKey] || 0) + value;
}
if (mode === 'set') {
points[gameKey][userKey] = value;
}
await fs.writeFile(`./points-${identifier}.json`, JSON.stringify(points, null, 4));
}
function getPoints(game, rawUsername, { user, room, command }) {
const username = rawUsername?.replace(new RegExp(`^${config.usernamePrefix}`), '');
const gamePoints = points[command] || points[game.key];
const userPoints = username
? Object.entries(gamePoints || {}).find(([identifier]) => identifier.split(':')[1] === username)?.[1]
: gamePoints?.[`${user?.id}:${user?.username}`];
game.sendMessage(`${username ? `${style.bold(username)} has` : 'You have'} scored ${style.bold(userPoints || 'no')} points in ${game.name}, ${config.usernamePrefix}${user.username}`, room.id);
}
function getLeaderboard(game, { user, room, command }) {
const leaderboard = points[command] || points[game.key];
if (!leaderboard || Object.keys(leaderboard).length === 0) {
game.sendMessage(`No points scored in ${game.name} yet!`, room.id);
return;
}
game.sendMessage(`The top ${Math.min(Object.keys(leaderboard).length, 10)} ${style.italic(game.name)} players are: ${getLeaders(leaderboard, user, { ping: false, limit: 10 })}`, room.id);
}
/* eslint-disable no-irregular-whitespace */
function styleCommands(rawBody) {
const styledCommands = rawBody.replaceAll(new RegExp(`${config.prefix}\\w+`, 'g'), (match) => (match.includes('game') ? `${style.cyan(config.prefix)}${style.italic(style.grey(match.slice(1)))}` : style.cyan(match)));
const styledSubcommands = styledCommands.replaceAll(/:\w+/g, (match) => style.green(match));
return styledSubcommands.replaceAll(/(?<!^)\[[\w\d|-]+\]/g, (match) => {
// using a zero-width space so numerical values don't mess up the color code
const value = match.slice(1, -1).replace(/[|-]/, (delimiter) => `${style.grey(delimiter)}`);
return `${style.grey('[')}${value}${style.grey(']')}`;
});
}
/* eslint-enable no-irregular-whitespace */
function capitalize(string) {
return `${string.slice(0, 1).toUpperCase()}${string.slice(1)}`;
}
function curateMessageBody(rawBody, game, key, options) {
const body = options?.styleCommands ? styleCommands(rawBody) : rawBody;
if (options?.label === false || config.labels === false) {
return body;
}
const label = typeof options?.label === 'string'
? capitalize(options.label)
: game.name || capitalize(key);
return `${style.grey(`[${label}]`)} ${body}`;
}
async function getGames(bot, identifier) {
await initPoints(identifier);
const games = config.games.reduce((acc, key) => {
const game = require(`./games/${key.game || key}`); // eslint-disable-line global-require, import/no-dynamic-require
const sendMessage = (body, roomId, options, recipient) => {
const curatedBody = curateMessageBody(body, game, key, options);
if (config.platform === 'irc') {
bot.client.say(/^#/.test(roomId) ? roomId : recipient, curatedBody);
}
if (config.platform === 'schat') {
bot.socket.transmit('message', {
roomId,
recipient: options?.type === 'whisper' || !roomId ? recipient : null,
type: options?.type || 'message',
body: curatedBody,
style: { ...config.style, ...options?.style },
});
}
};
const setGamePoints = (user, score = 1, options) => setPoints(identifier, key, user, score, options);
const curatedGame = {
...game,
...(key.game && key),
name: game.name || key,
key,
sendMessage,
setPoints: setGamePoints,
};
if (game.onStart) {
game.onStart({ ...curatedGame, bot, logger });
}
return {
...acc,
[key]: curatedGame,
...game.commands?.reduce((commandAcc, command) => ({ ...commandAcc, [command]: curatedGame }), {}),
};
}, {});
return games;
}
function getMessageUser(message, bot) {
if (config.platform === 'irc') {
return {
username: message.from,
id: message.from,
prefixedUsername: `${config.usernamePrefix}${message.from}`,
};
}
if (config.platform === 'schat') {
const user = bot.users[message.userId] || message.user;
if (user) {
return {
...user,
prefixedUsername: `${config.usernamePrefix}${user.username}`,
};
}
}
return null;
}
function getMessageRoom(message, bot) {
if (config.platform === 'irc') {
return {
id: message.to,
name: message.to,
};
}
if (config.platform === 'schat') {
return bot.rooms[message.roomId];
}
return null;
}
function onMessage(message, bot, games) {
const body = message.originalBody || message.body;
const [, command, subcommand] = body?.match(new RegExp(`^${config.prefix}([\\w>]+)(?:\\:(\\w+))?`)) || [];
const user = getMessageUser(message, bot);
const room = getMessageRoom(message, bot);
if (command) {
const args = body.split(/\s+/).slice(1);
const game = games[command];
if (['leaderboard', 'lead', 'leader', 'leaders', 'scoreboard', 'best'].includes(subcommand) && games[command]) {
getLeaderboard(games[command], { user, room, command });
return;
}
if (['points', 'score'].includes(subcommand) && games[command]) {
getPoints(games[command], args[0], { user, room, command });
return;
}
if (game && game.help && ['help', 'commands'].includes(subcommand)) {
game.sendMessage(game.help, room.id, { styleCommands: true });
if (game.blockHelp !== false) {
return;
}
}
if (game && game.onCommand) {
if (user) {
user.points = points[game.key]?.[`${user.id}:${user.username}`] || 0;
}
game.onCommand(args, {
...game,
command,
subcommand,
bot,
message,
user,
room,
points: points[game.key] || {},
games,
logger,
});
}
}
if (message.type === 'message' && user?.username !== config.user.username) {
Object.values(Object.fromEntries(Object.values(games).map((game) => [game.key, game]))).forEach((game) => game.onMessage?.(message, {
...game,
bot,
message,
containsCommand: command,
containsSubcommand: subcommand,
user: user && {
...user,
points: points[game.key]?.[`${user.id}:${user.username}`] || 0,
},
room,
logger,
}));
}
}
module.exports = {
onMessage,
getGames,
getLeaderboard,
getPoints,
setPoints,
initPoints,
};