Compare commits
5
Commits
e610f4212c
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
678b2991ad | ||
|
|
f048c1d077 | ||
|
|
1572e74ebf | ||
|
|
fc5f8572cd | ||
|
|
1101a5fc3e |
@@ -15,6 +15,7 @@ module.exports = {
|
|||||||
operators: ['admin'],
|
operators: ['admin'],
|
||||||
server: 'irc.libera.chat',
|
server: 'irc.libera.chat',
|
||||||
port: 6697,
|
port: 6697,
|
||||||
|
tls: true,
|
||||||
socket: 'ws://127.0.0.1:3000/socket/',
|
socket: 'ws://127.0.0.1:3000/socket/',
|
||||||
api: 'http://127.0.0.1:3000/api',
|
api: 'http://127.0.0.1:3000/api',
|
||||||
reconnectDelay: 10, // seconds
|
reconnectDelay: 10, // seconds
|
||||||
|
|||||||
Generated
+899
-1309
File diff suppressed because it is too large
Load Diff
+2
-6
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "schat2-clive",
|
"name": "schat2-clive",
|
||||||
"version": "1.31.1",
|
"version": "1.31.3",
|
||||||
"description": "Game host for SChat 2-powered chat sites",
|
"description": "Game host for SChat 2-powered chat sites",
|
||||||
"main": "src/app.js",
|
"main": "src/app.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -20,20 +20,16 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"better-sqlite3": "^13.0.3",
|
"better-sqlite3": "^13.0.3",
|
||||||
"bhttp": "^1.2.8",
|
"bhttp": "^1.2.8",
|
||||||
"bottleneck": "^2.19.5",
|
|
||||||
"config": "^5.0.1",
|
"config": "^5.0.1",
|
||||||
"date-fns": "^2.29.3",
|
"date-fns": "^2.29.3",
|
||||||
"html-entities": "^2.3.2",
|
"html-entities": "^2.3.2",
|
||||||
"inflect": "^0.5.0",
|
"inflect": "^0.5.0",
|
||||||
"irc": "^0.5.2",
|
|
||||||
"irc-colors": "^1.5.0",
|
"irc-colors": "^1.5.0",
|
||||||
"irc-upd": "^0.11.0",
|
"irc-framework": "^4.14.0",
|
||||||
"jsdom": "^18.1.0",
|
|
||||||
"knex": "^2.4.2",
|
"knex": "^2.4.2",
|
||||||
"linkify-it": "^3.0.3",
|
"linkify-it": "^3.0.3",
|
||||||
"markov-strings": "^3.0.1",
|
"markov-strings": "^3.0.1",
|
||||||
"mathjs": "^11.8.0",
|
"mathjs": "^11.8.0",
|
||||||
"socket.io-client": "^4.7.2",
|
|
||||||
"string-similarity": "^4.0.4",
|
"string-similarity": "^4.0.4",
|
||||||
"tensify": "^0.0.4",
|
"tensify": "^0.0.4",
|
||||||
"vm2": "^3.9.11",
|
"vm2": "^3.9.11",
|
||||||
|
|||||||
+10
-1
@@ -226,6 +226,15 @@ function onCommand(args, context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// answers are plain text and may contain regex characters, like '*batteries not included'
|
||||||
|
function getAnswerRegexp(answer) {
|
||||||
|
const pattern = answer
|
||||||
|
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||||
|
.replace(/\\[.?*]|!/g, '$&?'); // punctuation is optional, so T.S. Eliot also matches TS Eliot, and *NSYNC matches NSYNC
|
||||||
|
|
||||||
|
return new RegExp(pattern, 'i');
|
||||||
|
}
|
||||||
|
|
||||||
async function onMessage(message, context) {
|
async function onMessage(message, context) {
|
||||||
const game = games.get(context.room?.id);
|
const game = games.get(context.room?.id);
|
||||||
|
|
||||||
@@ -235,7 +244,7 @@ async function onMessage(message, context) {
|
|||||||
|
|
||||||
const { answer, fullAnswer } = game.questions[game.round];
|
const { answer, fullAnswer } = game.questions[game.round];
|
||||||
|
|
||||||
if (new RegExp(answer, 'i').test(decode(message.originalBody || message.body)) && !game.answers.has(context.user.id)) { // resolve HTML entities in case original body is not available, such as & to &
|
if (getAnswerRegexp(answer).test(decode(message.originalBody || message.body)) && !game.answers.has(context.user.id)) { // resolve HTML entities in case original body is not available, such as & to &
|
||||||
game.answers.set(context.user.id, {
|
game.answers.set(context.user.id, {
|
||||||
user: context.user,
|
user: context.user,
|
||||||
answer: message.body,
|
answer: message.body,
|
||||||
|
|||||||
+87
-27
@@ -1,10 +1,9 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const config = require('config');
|
const config = require('config');
|
||||||
const irc = require('irc');
|
const { Client } = require('irc-framework');
|
||||||
const { argv } = require('yargs');
|
const { argv } = require('yargs');
|
||||||
const logger = require('./utils/logger')();
|
const logger = require('./utils/logger')();
|
||||||
// const timers = require('timers/promises');
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
onMessage,
|
onMessage,
|
||||||
@@ -13,15 +12,22 @@ const {
|
|||||||
|
|
||||||
logger.setLevel(argv.level || 'info');
|
logger.setLevel(argv.level || 'info');
|
||||||
|
|
||||||
const client = new irc.Client(config.server, config.user.id, {
|
function reclaimNick(client, currentNick) {
|
||||||
userName: config.user.username,
|
logger.info(`Connected as '${currentNick}' because '${config.user.id}' was taken, asking NickServ to release it`);
|
||||||
realName: config.user.realName,
|
|
||||||
password: config.user.password,
|
client.say('NickServ', `GHOST ${config.user.id}`);
|
||||||
port: config.port,
|
|
||||||
secure: true,
|
// 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() {
|
async function init() {
|
||||||
|
const client = new Client();
|
||||||
|
|
||||||
const bot = {
|
const bot = {
|
||||||
client,
|
client,
|
||||||
rooms: config.channels.map((channel) => ({
|
rooms: config.channels.map((channel) => ({
|
||||||
@@ -32,35 +38,89 @@ async function init() {
|
|||||||
|
|
||||||
const games = await getGames(bot, config.user.id);
|
const games = await getGames(bot, config.user.id);
|
||||||
|
|
||||||
// registered fires again on every reconnect, so these must only be bound once
|
// client.connected is true as soon as the socket opens, not after registration as documented
|
||||||
client.addListener('message', (from, to, body) => onMessage({
|
let registered = false;
|
||||||
from,
|
|
||||||
to,
|
client.on('socket connected', () => { registered = false; });
|
||||||
recipient: /^#/.test(to) ? null : to,
|
|
||||||
body,
|
// 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',
|
type: 'message',
|
||||||
}, bot, games));
|
}, bot, games));
|
||||||
|
|
||||||
client.addListener('error', (error) => {
|
client.on('nick in use', (event) => {
|
||||||
logger.error(error);
|
// 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.addListener('registered', () => {
|
client.on('nick', (event) => event.new_nick === config.user.id && logger.info(`Reclaimed nick '${config.user.id}'`));
|
||||||
logger.info('Connected!');
|
client.on('loggedin', (event) => logger.info(`Logged in to account '${event.account}'`));
|
||||||
logger.info('Identifying with NickServ');
|
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`));
|
||||||
|
|
||||||
client.say('nickserv', `IDENTIFY ${config.user.username} ${config.user.password}`);
|
// 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) => {
|
config.channels.forEach((channel) => {
|
||||||
logger.info(`Joining ${channel}`);
|
logger.info(`Joining ${channel}`);
|
||||||
|
client.join(channel);
|
||||||
client.join(channel, () => logger.info(`Joined ${channel}`));
|
|
||||||
|
|
||||||
if (config.greeting) {
|
|
||||||
client.say(channel, config.greeting);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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;
|
module.exports = init;
|
||||||
|
|||||||
Reference in New Issue
Block a user