modernirc
Version:
IRC library for creating modern IRC bots
239 lines (235 loc) • 7.79 kB
JavaScript
import process from 'node:process'
/**
* Initializes all the required events for the automatic behaviour of the bot
* @param bot {object} Bot object
* @param options {object} Bot options
*/
export function initEvents(bot, options) {
if (options.uplink.autoPong) {
bot.on('ping', ({ params }) => {
bot.send('pong', params)
})
}
bot.on('clientinfo', () => {
bot.send('clientinfo', [
'ACTION',
'CLIENTINFO',
'FINGER',
'PING',
'SOURCE',
'TIME',
'USERINFO',
'VERSION',
])
})
bot.on('finger', () => {
bot.send('finger', [
'ModernIRCBot',
`v${process.env.npm_package_version}`,
])
})
bot.on('source', () => {
bot.send('source', [
'https://codeberg.org/ChakSoft/modernirc.git',
])
})
bot.on('time', () => {
bot.send('time', [
new Date().toISOString(),
])
})
bot.on('version', () => {
bot.send('version', [
'ModernIRCBot',
`v${process.env.npm_package_version}`,
])
})
bot.on('userinfo', () => {
bot.send('userinfo', [
bot.identity.username,
`(${bot.identity.realname})`,
])
})
bot.on('error', (err) => {
options.logger.error(err.message ?? err.params.join(' '))
})
bot.on('end', () => {
options.logger.info('connection terminated')
})
bot.on('message', async (message) => {
const eventName = `on${message.command[0].toUpperCase()}${message.command.substring(1).toLowerCase()}`
for (let i = 0; i < bot.loadedModules.length; i++) {
const { runner } = bot.loadedModules[i]
if (typeof runner[eventName] === 'function') {
await runner[eventName](message)
}
}
})
bot.on('privmsg', async (message) => {
for (let i = 0; i < bot.loadedModules.length; i++) {
const { runner, moduleOptions } = bot.loadedModules[i]
if (typeof moduleOptions.commandPrefix === 'string' && message.message.startsWith(moduleOptions.commandPrefix)) {
if (typeof runner.onModuleCommand === 'function') {
const moduleCommand = message.message.split(' ')[0].substring(1) // we remove the command prefix
const moduleArgs = message.message.split(' ').slice(1)
await runner.onModuleCommand(moduleCommand, moduleArgs, message)
}
}
}
})
bot.on('serverconfig', ({ params }) => {
for (let i = 0; i < params.length; i++) {
const [name, value] = params[i].split('=')
bot.serverConfig[name] = value
}
// We treat particular things about the prefixes to ensure the bot is understanding the symbols
// and the modes available on this server
//
// The resulting object is something like { 'q': '~', '~': 'q', 'a': '&', '&': 'a' ... }
// to ensure the bot is able to convert from one another.
//
// Technically, modes are :
// - q: owner
// - a: admin
// - o: operator
// - h: half-operator
// - v: voiced
if (typeof bot.serverConfig.PREFIX === 'string') {
const prefixMatch = bot.serverConfig.PREFIX?.match(/\((?<modes>\w+)\)(?<symbols>.*)/)
if (!prefixMatch?.groups) {
options.logger.warning('server prefix malformed: modes will not be auto-detected')
return
}
const { modes, symbols } = prefixMatch.groups
bot.serverConfig.PREFIX = modes.split('').reduce((agg, cur, currentIndex) => {
return {
...agg,
[cur]: symbols[currentIndex],
[symbols[currentIndex]]: cur,
}
}, {})
bot.serverConfig.PREFIX.modes = modes.split('')
bot.serverConfig.PREFIX.symbols = symbols.split('')
}
if (typeof bot.serverConfig.CHANTYPES === 'string') {
bot.serverConfig.CHANTYPES = bot.serverConfig.CHANTYPES.split('')
}
})
bot.on('notice', (notice) => {
if (options.identity.password && notice.message.includes('IDENTIFY') && notice.prefix.nickname.toLowerCase() === 'nickserv') {
// we're detecting if nickserv sent a message to invite the bot to identify itself
bot.message('NickServ', `IDENTIFY ${options.identity.password}`)
}
})
bot.on('privmsg', (privmsg) => {
if (options.identity.password && privmsg.params[0] === options.identity.nickname && privmsg.message.includes('IDENTIFY') && privmsg.prefix.nickname.toLowerCase() === 'nickserv') {
bot.message('NickServ', `IDENTIFY ${options.identity.password}`)
} else if (privmsg.message.startsWith('\x01ACTION')) {
// the message is a CTCP action from an user
bot.emit('action', {
...privmsg,
message: privmsg.message.replace('\x01ACTION ', ''),
})
}
})
bot.on('endofmotd', () => {
// at the end of message of the day, we should be able to join the specified channels
bot.send('join', [options.autojoin.map(({ name }) => name).join(','), options.autojoin.map(({ key }) => key).join(',')])
})
bot.on('join', (message) => {
if (message.self) {
// this join message is the answer to the bot joining the channel
// just after this message will come the names list of the users in the channel
bot.joinedChannels[message.params[0]] = {
name: message.params[0],
users: [],
}
} else {
bot.joinedChannels[message.params[0]].users.push({
nickname: message.prefix.nickname,
mode: '',
})
}
})
bot.on('part', (part) => {
const chan = bot.joinedChannels[part.params[0]]
if (!chan) {
return
}
const userIndex = chan.users.findIndex((user) => user.nickname === part.prefix.nickname)
if (userIndex === -1) {
return
}
chan.users.splice(userIndex, 1)
})
bot.on('kick', (kick) => {
const chan = bot.joinedChannels[kick.params[0]]
if (!chan) {
return
}
const userIndex = chan.users.findIndex((user) => user.nickname === kick.params[1])
if (userIndex === -1) {
return
}
chan.users.splice(userIndex, 1)
})
bot.on('namreply', (nr) => {
const chan = bot.joinedChannels[nr.params[2]]
if (!chan) {
return
}
const users = nr.message.split(' ')
for (let i = 0; i < users.length; i++) {
const user = users[i]
if (bot.serverConfig.PREFIX?.symbols.includes(user[0])) {
chan.users.push({
nickname: user.substring(1),
mode: bot.serverConfig.PREFIX[user[0]],
})
} else {
chan.users.push({
nickname: user,
mode: '',
})
}
}
})
bot.on('mode', (mode) => {
const [channelName, modepattern, target, ...args] = mode.params
const chan = bot.joinedChannels[channelName]
if (!chan) {
return
}
const modificators = { added: [], removed: [] }
let currentModificator = null
for (let i = 0; i < modepattern.length; i++) {
if (modepattern[i] === '+') {
currentModificator = 'added'
} else if (modepattern[i] === '-') {
currentModificator = 'removed'
} else if (currentModificator !== null) {
modificators[currentModificator].push(modepattern[i])
}
}
if (target) {
const user = chan.users.find(({ nickname }) => nickname === target)
if (!user) {
return
}
for (let i = 0; i< modificators.added.length; i++) {
user.mode += modificators.added[i]
}
for (let i = 0; i < modificators.removed.length; i++) {
user.mode = user.mode.replace(modificators.removed[i], '')
}
}
})
bot.on('quit', (quit) => {
Object.keys(bot.joinedChannels).forEach((chan) => {
const userIndex = bot.joinedChannels[chan].users.findIndex((({ nickname }) => nickname === quit.prefix.nickname))
if (userIndex !== -1) {
bot.joinedChannels[chan].users.splice(userIndex, 1)
}
})
})
}