Files
schat2-clive/src/irc.js
T

127 lines
3.9 KiB
JavaScript
Executable File

'use strict';
const config = require('config');
const { Client } = require('irc-framework');
const { argv } = require('yargs');
const logger = require('./utils/logger')();
const {
onMessage,
getGames,
} = require('./play');
logger.setLevel(argv.level || 'info');
function reclaimNick(client, currentNick) {
logger.info(`Connected as '${currentNick}' because '${config.user.id}' was taken, asking NickServ to release it`);
client.say('NickServ', `GHOST ${config.user.id}`);
// NickServ disconnects the stale session, give it a moment before taking the nick back
setTimeout(() => {
if (client.connected && client.user.nick !== config.user.id) {
client.changeNick(config.user.id);
}
}, 3000);
}
async function init() {
const client = new Client();
const bot = {
client,
rooms: config.channels.map((channel) => ({
id: channel,
name: channel,
})),
};
const games = await getGames(bot, config.user.id);
// client.connected is true as soon as the socket opens, not after registration as documented
let registered = false;
client.on('socket connected', () => { registered = false; });
// only plain messages, replying to notices is bad IRC bot etiquette and risks loops with services and other bots
client.on('privmsg', (event) => onMessage({
from: event.nick,
to: event.target,
recipient: /^#/.test(event.target) ? null : event.target,
body: event.message,
type: 'message',
}, bot, games));
client.on('nick in use', (event) => {
// registration stalls until we pick another nick
if (!registered) {
logger.warn(`Nick '${event.nick}' is in use, trying '${event.nick}_'`);
client.changeNick(`${event.nick}_`);
return;
}
logger.warn(`Nick '${event.nick}' is still in use: ${event.reason}`);
});
client.on('nick', (event) => event.new_nick === config.user.id && logger.info(`Reclaimed nick '${config.user.id}'`));
client.on('loggedin', (event) => logger.info(`Logged in to account '${event.account}'`));
client.on('sasl failed', (event) => logger.error(`SASL authentication failed: ${event.reason} ${event.message || ''}`));
client.on('irc error', (event) => logger.error(`IRC error: ${JSON.stringify(event)}`));
client.on('kick', (event) => event.kicked === client.user.nick && logger.warn(`Kicked from ${event.channel} by ${event.nick}: ${event.message}`));
client.on('reconnecting', (event) => logger.warn(`Disconnected, reconnect attempt ${event.attempt} in ${Math.round(event.wait / 1000)} seconds`));
// the library only auto-reconnects after a successful registration, cover failed (initial) connections too
client.on('close', () => {
logger.error(`Connection closed, reconnecting in ${config.reconnectDelay} seconds`);
setTimeout(() => client.connect(), config.reconnectDelay * 1000);
});
client.on('join', (event) => {
if (event.nick !== client.user.nick) {
return;
}
logger.info(`Joined ${event.channel}`);
if (config.greeting) {
client.say(event.channel, config.greeting);
}
});
// fires again on every reconnect
client.on('registered', (event) => {
registered = true;
// client.user.nick is only updated after this listener runs, use the nick from the event
logger.info(`Connected to ${config.server} as '${event.nick}'`);
if (event.nick !== config.user.id && config.user.password) {
reclaimNick(client, event.nick);
}
config.channels.forEach((channel) => {
logger.info(`Joining ${channel}`);
client.join(channel);
});
});
client.connect({
host: config.server,
port: config.port,
tls: config.tls,
nick: config.user.id,
username: config.user.username,
gecos: config.user.realName,
// SASL logs in before registration completes, so channels requiring an identified user can be joined straight away
account: config.user.password
? { account: config.user.username, password: config.user.password }
: {},
auto_reconnect: true,
auto_reconnect_max_retries: Infinity,
auto_reconnect_max_wait: 60000,
});
}
module.exports = init;